11import Vue from '../../utils/vue'
2- import { BImg } from './img'
32import { getComponentConfig } from '../../utils/config'
4- import { getBCR , eventOn , eventOff } from '../../utils/dom'
53import { hasIntersectionObserverSupport } from '../../utils/env'
4+ import { VBVisible } from '../../directives/visible'
5+ import { BImg } from './img'
66
77const NAME = 'BImgLazy'
88
9- const THROTTLE = 100
10- const EVENT_OPTIONS = { passive : true , capture : false }
11-
129export const props = {
1310 src : {
1411 type : String ,
@@ -81,24 +78,23 @@ export const props = {
8178 default : false
8279 } ,
8380 offset : {
81+ // Distance away from viewport (in pixels) before being
82+ // considered "visible"
8483 type : [ Number , String ] ,
8584 default : 360
86- } ,
87- throttle : {
88- type : [ Number , String ] ,
89- default : THROTTLE
9085 }
9186}
9287
9388// @vue /component
9489export const BImgLazy = /*#__PURE__*/ Vue . extend ( {
9590 name : NAME ,
91+ directives : {
92+ bVisible : VBVisible
93+ } ,
9694 props,
9795 data ( ) {
9896 return {
99- isShown : false ,
100- scrollTimeout : null ,
101- observer : null
97+ isShown : this . show
10298 }
10399 } ,
104100 computed : {
@@ -118,121 +114,66 @@ export const BImgLazy = /*#__PURE__*/ Vue.extend({
118114 watch : {
119115 show ( newVal , oldVal ) {
120116 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 )
125123 }
126124 }
127125 } ,
128126 isShown ( newVal , oldVal ) {
129127 if ( newVal !== oldVal ) {
130128 // Update synched show prop
131- this . $emit ( 'update:show' , newVal )
129+ this . updateShowProp ( )
132130 }
133131 }
134132 } ,
135- created ( ) {
136- this . isShown = this . show
137- } ,
138133 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
155136 } ,
156137 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 )
188140 } ,
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 ) {
191145 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 )
224146 }
225147 }
226148 } ,
227149 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+
228168 return h ( BImg , {
169+ directives,
229170 props : {
230171 // Computed value props
231172 src : this . computedSrc ,
232173 blank : this . computedBlank ,
233174 width : this . computedWidth ,
234175 height : this . computedHeight ,
235- // Passthough props
176+ // Passthrough props
236177 alt : this . alt ,
237178 blankColor : this . blankColor ,
238179 fluid : this . fluid ,
0 commit comments