Skip to content

Commit 85d8b08

Browse files
keithamuskoddsson
andcommitted
feat: add prefer-observers-over-events rule
Co-authored-by: Kristján Oddsson <[email protected]>
1 parent c24afd0 commit 85d8b08

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Prever Observers over Events
2+
3+
Some events, such as `scroll` and `resize` have traditionally caused performance issues on web pages, as they are high frequency events, firing many times per second as the user interacts with the page viewport.
4+
5+
## Scroll vs IntersectionObserver
6+
7+
Typically `scroll` events are used to determine if an element is intersecting a viewport, which can result in expensive operations such as layout calculations, which has a detrimental affect on UI performance. Recent developments in web standards have introduced the `IntersectionObserver`, which is a more performant mechanism for determining if an element is intersecting the viewport.
8+
9+
```js
10+
// bad, expensive, error-prone code to determine if the element is in the viewport;
11+
window.addEventListener('scroll', () => {
12+
const isIntersecting = checkIfElementIntersects(element.getBoundingClientRect(), window.innerHeight, document.clientHeight)
13+
element.classList.toggle('intersecting', isIntersecting)
14+
})
15+
16+
// good - more performant and less error-prone
17+
const observer = new IntersectionObserver(entries => {
18+
for(const {target, isIntersecting} of entries) {
19+
target.classList.toggle(target, isIntersecting)
20+
}
21+
})
22+
observer.observe(element)
23+
```
24+
25+
## Resize vs ResizeObserver
26+
27+
Similarly, `resize` events are typically used to determine if the viewport is smaller or larger than a certain size, similar to CSS media break points. Similar to the `IntersectionObserver` the `ResizeObserver` also exists as a more performant mechanism for observing changes to the viewport size.
28+
29+
```js
30+
// bad, low-performing code to determine if the element is less than 500px large
31+
window.addEventListener('resize', () => {
32+
element.classList.toggle('size-small', element.getBoundingClientRect().width < 500)
33+
})
34+
35+
// good - more performant, only fires when the actual elements change size
36+
const observer = new ResizeObserver(entries => {
37+
for(const {target, contentRect} of entries) {
38+
target.classList.toggle('size-small', contentRect.width < 500)
39+
}
40+
})
41+
observer.observe(element)
42+
```
43+
44+
## See Also
45+
46+
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
47+
https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const observerMap = {
2+
scroll: 'IntersectionObserver',
3+
resize: 'ResizeObserver'
4+
}
5+
module.exports = {
6+
meta: {
7+
docs: {},
8+
fixable: 'code'
9+
},
10+
11+
create(context) {
12+
return {
13+
['CallExpression[callee.property.name="addEventListener"]']: function(node) {
14+
const [name] = node.arguments
15+
if (name.type !== 'Literal') return
16+
if (!(name.value in observerMap)) return
17+
context.report({
18+
node,
19+
message: `Avoid using "${name.value}" event listener. Consider using ${observerMap[name.value]} instead`
20+
})
21+
}
22+
}
23+
}
24+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const rule = require('../lib/rules/prefer-observers-over-events')
2+
const RuleTester = require('eslint').RuleTester
3+
4+
const ruleTester = new RuleTester()
5+
6+
ruleTester.run('prefer-observers-over-events', rule, {
7+
valid: [
8+
{
9+
code: 'document.addEventListener("touchstart", function(event) {})'
10+
}
11+
],
12+
invalid: [
13+
{
14+
code: 'document.addEventListener("scroll", function(event) {})',
15+
errors: [
16+
{
17+
message: 'Avoid using "scroll" event listener. Consider using IntersectionObserver instead',
18+
type: 'CallExpression'
19+
}
20+
]
21+
},
22+
{
23+
code: 'document.addEventListener("resize", function(event) {})',
24+
errors: [
25+
{
26+
message: 'Avoid using "resize" event listener. Consider using ResizeObserver instead',
27+
type: 'CallExpression'
28+
}
29+
]
30+
}
31+
]
32+
})

0 commit comments

Comments
 (0)