Skip to content

Commit 78fe776

Browse files
authored
perf(utils/dom): use passive event listeners where possible (#2419)
1 parent 903bfa9 commit 78fe776

File tree

6 files changed

+95
-60
lines changed

6 files changed

+95
-60
lines changed

src/components/carousel/carousel.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const TransitionEndEvents = {
3939
transition: 'transitionend'
4040
}
4141

42+
const EventOptions = { passive: true, capture: false }
43+
4244
// Return the browser specific transitionEnd event name
4345
function getTransisionEndEvent (el) {
4446
for (const name in TransitionEndEvents) {
@@ -300,7 +302,7 @@ export default {
300302
/* istanbul ignore if: transition events cant be tested in JSDOM */
301303
if (this.transitionEndEvent) {
302304
const events = this.transitionEndEvent.split(/\s+/)
303-
events.forEach(evt => eventOff(currentSlide, evt, onceTransEnd))
305+
events.forEach(evt => eventOff(currentSlide, evt, onceTransEnd, EventOptions))
304306
}
305307
this._animationTimeout = null
306308
removeClass(nextSlide, dirClass)
@@ -322,7 +324,7 @@ export default {
322324
/* istanbul ignore if: transition events cant be tested in JSDOM */
323325
if (this.transitionEndEvent) {
324326
const events = this.transitionEndEvent.split(/\s+/)
325-
events.forEach(event => eventOn(currentSlide, event, onceTransEnd))
327+
events.forEach(event => eventOn(currentSlide, event, onceTransEnd, EventOptions))
326328
}
327329
// Fallback to setTimeout
328330
this._animationTimeout = setTimeout(onceTransEnd, TRANS_DURATION)
@@ -527,14 +529,14 @@ export default {
527529
}
528530
// Touch support event handlers for environment
529531
if (!this.noTouch && hasTouchSupport) /* istanbul ignore next: JSDOM doesn't support touch events */ {
530-
// Attach appropriate listeners
532+
// Attach appropriate listeners (passsive mode)
531533
if (hasPointerEvent) {
532-
on.pointerdown = this.touchStart
533-
on.pointerup = this.touchEnd
534+
on['&pointerdown'] = this.touchStart
535+
on['&pointerup'] = this.touchEnd
534536
} else {
535-
on.touchstart = this.touchStart
536-
on.touchmove = this.touchMove
537-
on.touchend = this.touchEnd
537+
on['&touchstart'] = this.touchStart
538+
on['&touchmove'] = this.touchMove
539+
on['&touchend'] = this.touchEnd
538540
}
539541
}
540542

src/components/image/img-lazy.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import BImg from './img'
22
import { getBCR, eventOn, eventOff } from '../../utils/dom'
3+
34
const THROTTLE = 100
5+
const EventOptions = { passive: true, capture: false }
46

57
// @vue/component
68
export default {
@@ -156,16 +158,16 @@ export default {
156158
const root = window
157159
if (on) {
158160
eventOn(this.$el, 'load', this.checkView)
159-
eventOn(root, 'scroll', this.onScroll)
160-
eventOn(root, 'resize', this.onScroll)
161-
eventOn(root, 'orientationchange', this.onScroll)
162-
eventOn(document, 'transitionend', this.onScroll)
161+
eventOn(root, 'scroll', this.onScroll, EventOptions)
162+
eventOn(root, 'resize', this.onScroll, EventOptions)
163+
eventOn(root, 'orientationchange', this.onScroll, EventOptions)
164+
eventOn(document, 'transitionend', this.onScroll, EventOptions)
163165
} else {
164166
eventOff(this.$el, 'load', this.checkView)
165-
eventOff(root, 'scroll', this.onScroll)
166-
eventOff(root, 'resize', this.onScroll)
167-
eventOff(root, 'orientationchange', this.onScroll)
168-
eventOff(document, 'transitionend', this.onScroll)
167+
eventOff(root, 'scroll', this.onScroll, EventOptions)
168+
eventOff(root, 'resize', this.onScroll, EventOptions)
169+
eventOff(root, 'orientationchange', this.onScroll, EventOptions)
170+
eventOff(document, 'transitionend', this.onScroll, EventOptions)
169171
}
170172
},
171173
checkView () /* istanbul ignore next: can't test getBoundingClientRect in JSDOM */ {

src/directives/scrollspy/scrollspy.class.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,20 @@ const TransitionEndEvents = [
7676
'oTransitionEnd'
7777
]
7878

79+
// Options for events
80+
const EventOptions = { passive: true, capture: false }
81+
7982
/*
8083
* Utility Methods
8184
*/
8285

8386
// Better var type detection
84-
/* istanbul ignore next: not easy to test */
85-
function toType (obj) {
87+
function toType (obj) /* istanbul ignore next: not easy to test */ {
8688
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
8789
}
8890

8991
// Check config properties for expected types
90-
/* istanbul ignore next: not easy to test */
91-
function typeCheckConfig (componentName, config, configTypes) {
92+
function typeCheckConfig (componentName, config, configTypes) /* istanbul ignore next: not easy to test */ {
9293
for (const property in configTypes) {
9394
if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
9495
const expectedTypes = configTypes[property]
@@ -114,7 +115,7 @@ function typeCheckConfig (componentName, config, configTypes) {
114115
*/
115116

116117
/* istanbul ignore next: not easy to test */
117-
class ScrollSpy {
118+
class ScrollSpy /* istanbul ignore next: not easy to test */ {
118119
constructor (element, config, $root) {
119120
// The element we activate links in
120121
this.$el = element
@@ -189,13 +190,13 @@ class ScrollSpy {
189190
listen () {
190191
const scroller = this.getScroller()
191192
if (scroller && scroller.tagName !== 'BODY') {
192-
eventOn(scroller, 'scroll', this)
193+
eventOn(scroller, 'scroll', this, EventOptions)
193194
}
194-
eventOn(window, 'scroll', this)
195-
eventOn(window, 'resize', this)
196-
eventOn(window, 'orientationchange', this)
195+
eventOn(window, 'scroll', this, EventOptions)
196+
eventOn(window, 'resize', this, EventOptions)
197+
eventOn(window, 'orientationchange', this, EventOptions)
197198
TransitionEndEvents.forEach(evtName => {
198-
eventOn(window, evtName, this)
199+
eventOn(window, evtName, this, EventOptions)
199200
})
200201
this.setObservers(true)
201202
// Scedule a refresh
@@ -206,13 +207,13 @@ class ScrollSpy {
206207
const scroller = this.getScroller()
207208
this.setObservers(false)
208209
if (scroller && scroller.tagName !== 'BODY') {
209-
eventOff(scroller, 'scroll', this)
210+
eventOff(scroller, 'scroll', this, EventOptions)
210211
}
211-
eventOff(window, 'scroll', this)
212-
eventOff(window, 'resize', this)
213-
eventOff(window, 'orientationchange', this)
212+
eventOff(window, 'scroll', this, EventOptions)
213+
eventOff(window, 'resize', this, EventOptions)
214+
eventOff(window, 'orientationchange', this, EventOptions)
214215
TransitionEndEvents.forEach(evtName => {
215-
eventOff(window, evtName, this)
216+
eventOff(window, evtName, this, EventOptions)
216217
})
217218
}
218219

src/directives/scrollspy/scrollspy.js

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44

55
import ScrollSpy from './scrollspy.class'
66
import { keys } from '../../utils/object'
7-
8-
const inBrowser = typeof window !== 'undefined'
9-
const isServer = !inBrowser
7+
import { isServer } from '../../utils/env'
108

119
// Key we use to store our Instance
1210
const BVSS = '__BV_ScrollSpy__'
1311

1412
// Generate config from bindings
15-
/* istanbul ignore next: not easy to test */
16-
function makeConfig (binding) {
13+
function makeConfig (binding) /* istanbul ignore next: not easy to test */ {
1714
const config = {}
1815

1916
// If Argument, assume element ID
@@ -51,8 +48,7 @@ function makeConfig (binding) {
5148
return config
5249
}
5350

54-
/* istanbul ignore next: not easy to test */
55-
function addBVSS (el, binding, vnode) {
51+
function addBVSS (el, binding, vnode) /* istanbul ignore next: not easy to test */ {
5652
if (isServer) {
5753
return
5854
}
@@ -65,8 +61,7 @@ function addBVSS (el, binding, vnode) {
6561
return el[BVSS]
6662
}
6763

68-
/* istanbul ignore next: not easy to test */
69-
function removeBVSS (el) {
64+
function removeBVSS (el) /* istanbul ignore next: not easy to test */ {
7065
if (el[BVSS]) {
7166
el[BVSS].dispose()
7267
el[BVSS] = null
@@ -77,21 +72,20 @@ function removeBVSS (el) {
7772
* Export our directive
7873
*/
7974

80-
/* istanbul ignore next: not easy to test */
8175
export default {
82-
bind (el, binding, vnode) {
76+
bind (el, binding, vnode) /* istanbul ignore next: not easy to test */ {
8377
addBVSS(el, binding, vnode)
8478
},
85-
inserted (el, binding, vnode) {
79+
inserted (el, binding, vnode) /* istanbul ignore next: not easy to test */ {
8680
addBVSS(el, binding, vnode)
8781
},
88-
update (el, binding, vnode) {
82+
update (el, binding, vnode) /* istanbul ignore next: not easy to test */ {
8983
addBVSS(el, binding, vnode)
9084
},
91-
componentUpdated (el, binding, vnode) {
85+
componentUpdated (el, binding, vnode) /* istanbul ignore next: not easy to test */ {
9286
addBVSS(el, binding, vnode)
9387
},
94-
unbind (el) {
88+
unbind (el) /* istanbul ignore next: not easy to test */ {
9589
if (isServer) {
9690
return
9791
}

src/utils/dom.js

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,52 @@
11
import { from as arrayFrom } from './array'
2+
import { inBrowser } from './env'
3+
4+
// Determine if the browser supports the option passive for events
5+
let passiveEventSupported = false
6+
/* istanbul ignore if */
7+
if (inBrowser) {
8+
try {
9+
var options = {
10+
get passive () {
11+
// This function will be called when the browser
12+
// attempts to access the passive property.
13+
passiveEventSupported = true
14+
}
15+
}
16+
window.addEventListener('test', options, options)
17+
window.removeEventListener('test', options, options)
18+
} catch (err) {
19+
passiveEventSupported = false
20+
}
21+
}
22+
23+
// Normalize event options based on support of passive option
24+
function parseEventOptions (options) {
25+
let useCapture = false
26+
if (options) {
27+
if (typeof options === 'object') {
28+
// eslint-disable-next-line no-unneeded-ternary
29+
useCapture = options.useCapture ? true : false
30+
} else {
31+
useCapture = options
32+
}
33+
}
34+
return passiveEventSupported ? options : useCapture
35+
}
36+
37+
// Attach an event listener to an element
38+
export const eventOn = (el, evtName, handler, options) => {
39+
if (el && el.addEventListener) {
40+
el.addEventListener(evtName, handler, parseEventOptions(options))
41+
}
42+
}
43+
44+
// Remove an event listener from an element
45+
export const eventOff = (el, evtName, handler, options) => {
46+
if (el && el.removeEventListener) {
47+
el.removeEventListener(evtName, handler, parseEventOptions(options))
48+
}
49+
}
250

351
// Determine if an element is an HTML Element
452
export const isElement = el => {
@@ -230,17 +278,3 @@ export const position = el => {
230278
left: offsetSelf.left - parentOffset.left - parseFloat(getCS(el).marginLeft)
231279
}
232280
}
233-
234-
// Attach an event listener to an element
235-
export const eventOn = (el, evtName, handler, options) => {
236-
if (el && el.addEventListener) {
237-
el.addEventListener(evtName, handler, options)
238-
}
239-
}
240-
241-
// Remove an event listener from an element
242-
export const eventOff = (el, evtName, handler, options) => {
243-
if (el && el.removeEventListener) {
244-
el.removeEventListener(evtName, handler, options)
245-
}
246-
}

src/utils/env.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
export const inBrowser = typeof document !== 'undefined' && typeof window !== 'undefined'
44

5+
export const isServer = !inBrowser
6+
57
export const hasTouchSupport = inBrowser && ('ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0)
68

79
export const hasPointerEvent = inBrowser && Boolean(window.PointerEvent || window.MSPointerEvent)

0 commit comments

Comments
 (0)