1
1
import Vue from '../../utils/vue'
2
- import { BImg } from './img'
3
2
import { getComponentConfig } from '../../utils/config'
4
- import { getBCR , eventOn , eventOff } from '../../utils/dom'
5
3
import { hasIntersectionObserverSupport } from '../../utils/env'
4
+ import { VBVisible } from '../../directives/visible'
5
+ import { BImg } from './img'
6
6
7
7
const NAME = 'BImgLazy'
8
8
9
- const THROTTLE = 100
10
- const EVENT_OPTIONS = { passive : true , capture : false }
11
-
12
9
export const props = {
13
10
src : {
14
11
type : String ,
@@ -81,24 +78,23 @@ export const props = {
81
78
default : false
82
79
} ,
83
80
offset : {
81
+ // Distance away from viewport (in pixels) before being
82
+ // considered "visible"
84
83
type : [ Number , String ] ,
85
84
default : 360
86
- } ,
87
- throttle : {
88
- type : [ Number , String ] ,
89
- default : THROTTLE
90
85
}
91
86
}
92
87
93
88
// @vue /component
94
89
export const BImgLazy = /*#__PURE__*/ Vue . extend ( {
95
90
name : NAME ,
91
+ directives : {
92
+ bVisible : VBVisible
93
+ } ,
96
94
props,
97
95
data ( ) {
98
96
return {
99
- isShown : false ,
100
- scrollTimeout : null ,
101
- observer : null
97
+ isShown : this . show
102
98
}
103
99
} ,
104
100
computed : {
@@ -118,121 +114,66 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({
118
114
watch : {
119
115
show ( newVal , oldVal ) {
120
116
if ( newVal !== oldVal ) {
121
- this . isShown = newVal
122
- if ( ! newVal ) {
123
- // Make sure listeners are re-enabled if img is force set to blank
124
- this . setListeners ( true )
117
+ // If IntersectionObserver support is not available, image is always shown
118
+ const visible = hasIntersectionObserverSupport ? newVal : true
119
+ this . isShown = visible
120
+ if ( visible !== newVal ) {
121
+ // Ensure the show prop is synced (when no IntersectionObserver)
122
+ this . $nextTick ( this . updateShowProp )
125
123
}
126
124
}
127
125
} ,
128
126
isShown ( newVal , oldVal ) {
129
127
if ( newVal !== oldVal ) {
130
128
// Update synched show prop
131
- this . $emit ( 'update:show' , newVal )
129
+ this . updateShowProp ( )
132
130
}
133
131
}
134
132
} ,
135
- created ( ) {
136
- this . isShown = this . show
137
- } ,
138
133
mounted ( ) {
139
- if ( this . isShown ) {
140
- this . setListeners ( false )
141
- } else {
142
- this . setListeners ( true )
143
- }
144
- } ,
145
- activated ( ) /* istanbul ignore next */ {
146
- if ( ! this . isShown ) {
147
- this . setListeners ( true )
148
- }
149
- } ,
150
- deactivated ( ) /* istanbul ignore next */ {
151
- this . setListeners ( false )
152
- } ,
153
- beforeDestroy ( ) {
154
- this . setListeners ( false )
134
+ // If IntersectionObserver is not available, image is always shown
135
+ this . isShown = hasIntersectionObserverSupport ? this . show : true
155
136
} ,
156
137
methods : {
157
- setListeners ( on ) {
158
- if ( this . scrollTimeout ) {
159
- clearTimeout ( this . scrollTimeout )
160
- this . scrollTimeout = null
161
- }
162
- /* istanbul ignore next: JSDOM doen't support IntersectionObserver */
163
- if ( this . observer ) {
164
- this . observer . unobserve ( this . $el )
165
- this . observer . disconnect ( )
166
- this . observer = null
167
- }
168
- const winEvts = [ 'scroll' , 'resize' , 'orientationchange' ]
169
- winEvts . forEach ( evt => eventOff ( window , evt , this . onScroll , EVENT_OPTIONS ) )
170
- eventOff ( this . $el , 'load' , this . checkView , EVENT_OPTIONS )
171
- eventOff ( document , 'transitionend' , this . onScroll , EVENT_OPTIONS )
172
- if ( on ) {
173
- /* istanbul ignore if: JSDOM doen't support IntersectionObserver */
174
- if ( hasIntersectionObserverSupport ) {
175
- this . observer = new IntersectionObserver ( this . doShow , {
176
- root : null , // viewport
177
- rootMargin : `${ parseInt ( this . offset , 10 ) || 0 } px` ,
178
- threshold : 0 // percent intersection
179
- } )
180
- this . observer . observe ( this . $el )
181
- } else {
182
- // Fallback to scroll/etc events
183
- winEvts . forEach ( evt => eventOn ( window , evt , this . onScroll , EVENT_OPTIONS ) )
184
- eventOn ( this . $el , 'load' , this . checkView , EVENT_OPTIONS )
185
- eventOn ( document , 'transitionend' , this . onScroll , EVENT_OPTIONS )
186
- }
187
- }
138
+ updateShowProp ( ) {
139
+ this . $emit ( 'update:show' , this . isShown )
188
140
} ,
189
- doShow ( entries ) {
190
- if ( entries && ( entries [ 0 ] . isIntersecting || entries [ 0 ] . intersectionRatio > 0.0 ) ) {
141
+ doShow ( visible ) {
142
+ // If IntersectionObserver is not supported, the callback
143
+ // will be called with `null` rather than `true` or `false`
144
+ if ( ( visible || visible === null ) && ! this . isShown ) {
191
145
this . isShown = true
192
- this . setListeners ( false )
193
- }
194
- } ,
195
- checkView ( ) {
196
- // check bounding box + offset to see if we should show
197
- /* istanbul ignore next: should rarely occur */
198
- if ( this . isShown ) {
199
- this . setListeners ( false )
200
- return
201
- }
202
- const offset = parseInt ( this . offset , 10 ) || 0
203
- const docElement = document . documentElement
204
- const view = {
205
- l : 0 - offset ,
206
- t : 0 - offset ,
207
- b : docElement . clientHeight + offset ,
208
- r : docElement . clientWidth + offset
209
- }
210
- // JSDOM Doesn't support BCR, but we fake it in the tests
211
- const box = getBCR ( this . $el )
212
- if ( box . right >= view . l && box . bottom >= view . t && box . left <= view . r && box . top <= view . b ) {
213
- // image is in view (or about to be in view)
214
- this . doShow ( [ { isIntersecting : true } ] )
215
- }
216
- } ,
217
- onScroll ( ) {
218
- /* istanbul ignore if: should rarely occur */
219
- if ( this . isShown ) {
220
- this . setListeners ( false )
221
- } else {
222
- clearTimeout ( this . scrollTimeout )
223
- this . scrollTimeout = setTimeout ( this . checkView , parseInt ( this . throttle , 10 ) || THROTTLE )
224
146
}
225
147
}
226
148
} ,
227
149
render ( h ) {
150
+ const directives = [ ]
151
+ if ( ! this . isShown ) {
152
+ // We only add the visible directive if we are not shown
153
+ directives . push ( {
154
+ // Visible directive will silently do nothing if
155
+ // IntersectionObserver is not supported
156
+ name : 'b-visible' ,
157
+ // Value expects a callback (passed one arg of `visible` = `true` or `false`)
158
+ value : this . doShow ,
159
+ modifiers : {
160
+ // Root margin from viewport
161
+ [ `${ parseInt ( this . offset , 10 ) || 0 } ` ] : true ,
162
+ // Once the image is shown, stop observing
163
+ once : true
164
+ }
165
+ } )
166
+ }
167
+
228
168
return h ( BImg , {
169
+ directives,
229
170
props : {
230
171
// Computed value props
231
172
src : this . computedSrc ,
232
173
blank : this . computedBlank ,
233
174
width : this . computedWidth ,
234
175
height : this . computedHeight ,
235
- // Passthough props
176
+ // Passthrough props
236
177
alt : this . alt ,
237
178
blankColor : this . blankColor ,
238
179
fluid : this . fluid ,
0 commit comments