1+ gocek = ( {
2+
3+ /* ---
4+ | MAIN
5+ --- */
6+
7+ init ( ) {
8+
9+ //elements container - collect any starting elements
10+ this . els = Array . from ( document . querySelectorAll ( '*:not(img).lazy, img.lazy[data-src]:not(.viewed)' ) ) . map ( el => this . register_el ( el , 1 ) ) ;
11+
12+ //custom events conatiner
13+ this . evts = { } ;
14+
15+ //on scroll end (on window), handle any elements that come into view. For imgs, load them...
16+ addEventListener ( 'scroll' , ( ) => {
17+ clearTimeout ( this . scroll_timeout ) ;
18+ this . scroll_timeout = setTimeout ( ( ) => {
19+ this . els . forEach ( el => {
20+
21+ let
22+ old_state = el . getAttribute ( 'data-gocek-state' ) ,
23+ new_state = this . is_visible ( el , el . matches ( '.completely' ) ) ? 'visible' : 'hidden' ;
24+
25+ //...state - if old state same as new state, ignore
26+ if ( old_state == new_state ) return ;
27+ el . setAttribute ( 'data-gocek-state' , new_state ) ;
28+
29+ //...if visible and unloaded image, load now
30+ if ( new_state == 'visible' && el . matches ( 'img:not(.viewed)' ) ) {
31+ el . src = el . dataset . src ;
32+ el . classList . add ( 'viewed' ) ;
33+ el . classList . remove ( 'loading' ) ;
34+ }
35+
36+ //...any callbacks registered on this element?
37+ let cb = el [ 'gocek_on_' + new_state + '_cb' ] ;
38+ if ( cb ) cb ( 'gocek_' + new_state , el ) ;
39+ if ( el [ 'gocek_on_' + new_state + '_once' ] ) delete cb ;
40+
41+ } ) ;
42+ } , 250 ) ;
43+ } ) ;
44+ window . dispatchEvent ( new CustomEvent ( 'scroll' ) ) ;
45+
46+ //return API
47+ return this ;
48+
49+ } ,
50+
51+ /* ---
52+ | ON... - register event to fire when element(s) become(s) scrolls into or out of view. Args:
53+ | @on (str) - which event, either 'visible' or 'hidden'
54+ | @el (str; obj) - a selector or element reference representing the element to listen for
55+ | @func (func) - the callback function
56+ | @once (bool) - if true, runs only once for given event type, not each time
57+ --- */
58+
59+ on ( on , el , func , once ) {
60+ if ( typeof el == 'string' ) el = document . querySelector ( el ) ;
61+ else if ( ! ( el instanceof HTMLElement ) ) return ;
62+ el [ 'gocek_on_' + on + '_cb' ] = func ;
63+ if ( once ) el [ 'gocek_on_' + on + '_once' ] = 1 ;
64+ } ,
65+
66+ /* ---
67+ | IS VISIBLE - return whether an element is currently visible - partially or fully. Used internally but available
68+ | on API too. Args:
69+ | @el (str; obj) - (see ::on())
70+ | @completely (bool) - if true, returns true only if completely visible, not partially
71+ --- */
72+
73+ is_visible ( el , completely ) {
74+
75+ let
76+ scrollTop = window . pageYOffset ,
77+ winHeight = window . innerHeight ,
78+ elTop = el . offsetTop ,
79+ elBottom = el . offsetTop + el . offsetHeight ;
80+
81+ if ( ! completely )
82+ return elBottom > scrollTop && elTop < scrollTop + winHeight ;
83+ else
84+ return elTop > scrollTop && elBottom < scrollTop + winHeight ;
85+
86+ } ,
87+
88+ /* ---
89+ | REGISTER ELEMENT(S) - alternate, programmatic way to register element(s) with Gocek (other way is via HTML attributes). Args:
90+ | @els (obj; arr) - reference to element, or array of elements
91+ --- */
92+
93+ register_el ( els , internal ) {
94+ els = els instanceof Array ? els : [ els ] ;
95+ for ( let i = 0 ; i < els . length ; i ++ ) {
96+ if ( els [ i ] . tagName == 'IMG' ) els [ i ] . classList . add ( 'loading' ) ;
97+ if ( internal )
98+ return els [ i ] ; //<-- no problem returning from loop; for internal use, only ever one element passed at a time
99+ else
100+ this . els . push ( els [ i ] ) ;
101+ }
102+ } ,
103+
104+ } ) . init ( ) ;
0 commit comments