11import { BasePattern } from "@patternslib/patternslib/src/core/basepattern" ;
22import dom from "../../core/dom" ;
3+ import events from "../../core/events" ;
34import Parser from "../../core/parser" ;
4- import registry from "@patternslib/patternslib/src /core/registry" ;
5+ import registry from "../.. /core/registry" ;
56import utils from "../../core/utils" ;
67
78export const parser = new Parser ( "bumper" ) ;
@@ -15,121 +16,236 @@ class Pattern extends BasePattern {
1516 static name = "bumper" ;
1617 static trigger = ".pat-bumper" ;
1718 static parser = parser ;
19+ ticking = false ;
1820
1921 async init ( ) {
20- // Based on: https://davidwalsh.name/detect-sticky
21-
2222 this . target_element = this . options . selector
2323 ? document . querySelector ( this . options . selector )
2424 : this . el ;
2525
2626 // wait for next repaint for things to settle.
2727 // e.g. CSS applied for injected content.
2828 await utils . timeout ( 1 ) ;
29- this . _init ( ) ;
30- }
3129
32- _init ( ) {
33- const scroll_container_y = dom . find_scroll_container (
34- this . el . parentElement ,
35- "y" ,
36- null
37- ) ;
38- const scroll_container_x = dom . find_scroll_container (
39- this . el . parentElement ,
40- "x" ,
41- null
42- ) ;
43-
44- const pos = {
30+ const parent_el = this . el . parentElement ;
31+ this . container_x = dom . find_scroll_container ( parent_el , "x" , null ) ;
32+ this . container_y = dom . find_scroll_container ( parent_el , "y" , null ) ;
33+
34+ // Viewport dimensions
35+ this . dim_viewport = {
36+ top :
37+ 0 +
38+ dom . get_css_value ( document . body , "margin-top" , true ) +
39+ dom . get_css_value ( document . body , "padding-top" , true ) ,
40+ left :
41+ 0 +
42+ dom . get_css_value ( document . body , "margin-left" , true ) +
43+ dom . get_css_value ( document . body , "padding-left" , true ) ,
44+ } ;
45+ this . dim_viewport . right =
46+ document . documentElement . clientWidth -
47+ dom . get_css_value ( document . body , "margin-right" , true ) -
48+ dom . get_css_value ( document . body , "padding-right" , true ) ;
49+ this . dim_viewport . bottom =
50+ document . documentElement . clientHeight -
51+ dom . get_css_value ( document . body , "margin-bottom" , true ) -
52+ dom . get_css_value ( document . body , "padding-bottom" , true ) ;
53+
54+ this . dim_element = {
4555 top : dom . get_css_value ( this . el , "top" , true ) ,
4656 right : dom . get_css_value ( this . el , "right" , true ) ,
4757 bottom : dom . get_css_value ( this . el , "bottom" , true ) ,
4858 left : dom . get_css_value ( this . el , "left" , true ) ,
59+ margin_top : dom . get_css_value ( this . el , "margin-top" , true ) ,
60+ margin_bottom : dom . get_css_value ( this . el , "margin-bottom" , true ) ,
61+ margin_right : dom . get_css_value ( this . el , "margin-right" , true ) ,
62+ margin_left : dom . get_css_value ( this . el , "margin-left" , true ) ,
4963 } ;
50- const intersection_observer_config = {
51- threshold : [ 1 , 0.99 , 0.97 , 0.96 , 0.95 , 0.94 , 0.93 , 0.92 , 0.91 , 0.9 ] ,
52- // add margin as inverted sticky positions.
53- rootMargin : `${ - pos . top - 1 } px ${ - pos . right - 1 } px ${ - pos . bottom - 1 } px ${ - pos . left - 1 } px` , // prettier-ignore
54- } ;
5564
56- const observer_y = new IntersectionObserver (
57- this . _intersection_observer_callback . bind ( this ) ,
58- {
59- ...intersection_observer_config ,
60- root : scroll_container_y ,
61- }
62- ) ;
63- observer_y . observe ( this . el ) ;
64-
65- if ( scroll_container_x !== scroll_container_y ) {
66- const observer_x = new IntersectionObserver (
67- this . _intersection_observer_callback . bind ( this ) ,
68- {
69- ...intersection_observer_config ,
70- root : scroll_container_x ,
65+ this . dim_container_x = this . container_x
66+ ? {
67+ border_top_width : dom . get_css_value ( this . container_x , "border-top-width" , true ) , // prettier-ignore
68+ border_left_width : dom . get_css_value ( this . container_x , "border-left-width" , true ) , // prettier-ignore
69+ padding_top : dom . get_css_value ( this . container_x , "padding-top" , true ) , // prettier-ignore
70+ padding_right : dom . get_css_value ( this . container_x , "padding-right" , true ) , // prettier-ignore
71+ padding_bottom : dom . get_css_value ( this . container_x , "padding-bottom" , true ) , // prettier-ignore
72+ padding_left : dom . get_css_value ( this . container_x , "padding-left" , true ) , // prettier-ignore
73+ }
74+ : { } ;
75+
76+ this . dim_container_y = this . container_y
77+ ? {
78+ border_top_width : dom . get_css_value ( this . container_y , "border-top-width" , true ) , // prettier-ignore
79+ border_left_width : dom . get_css_value ( this . container_y , "border-left-width" , true ) , // prettier-ignore
80+ padding_top : dom . get_css_value ( this . container_y , "padding-top" , true ) , // prettier-ignore
81+ padding_right : dom . get_css_value ( this . container_y , "padding-right" , true ) , // prettier-ignore
82+ padding_bottom : dom . get_css_value ( this . container_y , "padding-bottom" , true ) , // prettier-ignore
83+ padding_left : dom . get_css_value ( this . container_y , "padding-left" , true ) , // prettier-ignore
84+ }
85+ : { } ;
86+
87+ const containers = new Set ( [ this . container_x , this . container_y ] ) ;
88+ for ( const container of containers ) {
89+ events . add_event_listener (
90+ container || document ,
91+ "scroll" ,
92+ "pat_bumper__scroll" ,
93+ async ( ) => {
94+ if ( ! this . ticking ) {
95+ this . ticking = true ;
96+ await utils . animation_frame ( ) ;
97+ this . set_bumping_classes ( ) ;
98+ this . ticking = false ;
99+ }
71100 }
72101 ) ;
73- observer_x . observe ( this . el ) ;
74102 }
103+ this . set_bumping_classes ( ) ;
75104 }
76105
77- _intersection_observer_callback ( entries ) {
78- const el = this . target_element ;
79- for ( const entry of entries ) {
80- if ( entry . intersectionRatio < 1 ) {
81- if ( this . options . bump . add ) {
82- el . classList . add ( this . options . bump . add ) ;
83- }
84- if ( this . options . bump . remove ) {
85- el . classList . remove ( this . options . bump . remove ) ;
86- }
106+ /**
107+ * Get the container position values.
108+ *
109+ * @param {DOMElement } container - The container element.
110+ * @param {Objcet } dimensions - The dimension Object of the container,
111+ * which were initialized in the init method.
112+ *
113+ * @returns {Object } The position values.
114+ */
115+ _get_container_positions ( container , dimensions ) {
116+ if ( ! container ) {
117+ // No container = document.body
118+ return this . dim_viewport ;
119+ }
87120
88- const root = entry . rootBounds ;
89- if ( ! root ) {
90- // No root found - e.g. CSS not fully applied when scroll
91- // container was searched - as can happen as a corner case
92- // after injecting content and initializing this pattern in
93- // the same repaint cycle.
94- // This is actually prevented by the 1ms timeout in the
95- // init method.
96- return ;
97- }
98- const bounds = entry . boundingClientRect ;
121+ // Bounds are dynamic, so we cannot cache them.
122+ const bounds = container . getBoundingClientRect ( ) ;
99123
100- if ( bounds . left <= root . left ) {
101- el . classList . add ( "bumped-left" ) ;
102- } else {
103- el . classList . remove ( "bumped-left" ) ;
104- }
105- if ( bounds . top <= root . top ) {
106- el . classList . add ( "bumped-top" ) ;
107- } else {
108- el . classList . remove ( "bumped-top" ) ;
109- }
110- if ( bounds . right >= root . right ) {
111- el . classList . add ( "bumped-right" ) ;
112- } else {
113- el . classList . remove ( "bumped-right" ) ;
114- }
115- if ( bounds . bottom >= root . bottom ) {
116- el . classList . add ( "bumped-bottom" ) ;
117- } else {
118- el . classList . remove ( "bumped-bottom" ) ;
119- }
120- } else {
121- if ( this . options . unbump . add ) {
122- el . classList . add ( this . options . unbump . add ) ;
123- }
124- if ( this . options . unbump . remove ) {
125- el . classList . remove ( this . options . unbump . remove ) ;
126- }
127- el . classList . remove ( "bumped-left" ) ;
128- el . classList . remove ( "bumped-top" ) ;
129- el . classList . remove ( "bumped-right" ) ;
130- el . classList . remove ( "bumped-bottom" ) ;
131- }
124+ const left =
125+ bounds . left +
126+ dimensions . border_left_width +
127+ dimensions . padding_left ; // prettier-ignore
128+ const top =
129+ bounds . top +
130+ dimensions . border_top_width +
131+ dimensions . padding_top ; // prettier-ignore
132+
133+ const right =
134+ bounds . left +
135+ dimensions . border_left_width +
136+ container . clientWidth -
137+ dimensions . padding_right ;
138+
139+ const bottom =
140+ bounds . top +
141+ dimensions . border_top_width +
142+ container . clientHeight -
143+ dimensions . padding_bottom ;
144+
145+ return {
146+ top : Math . round ( top ) ,
147+ right : Math . round ( right ) ,
148+ bottom : Math . round ( bottom ) ,
149+ left : Math . round ( left ) ,
150+ } ;
151+ }
152+
153+ /**
154+ * Get the element position values.
155+ *
156+ * @returns {Object } The position values.
157+ */
158+ _get_element_positions ( ) {
159+ const bounds = this . el . getBoundingClientRect ( ) ;
160+ return {
161+ top : Math . round (
162+ bounds . top -
163+ this . dim_element . top -
164+ this . dim_element . margin_top // prettier-ignore
165+ ) ,
166+ right : Math . round (
167+ bounds . right +
168+ this . dim_element . right +
169+ this . dim_element . margin_right // prettier-ignore
170+ ) ,
171+ bottom : Math . round (
172+ bounds . bottom +
173+ this . dim_element . bottom +
174+ this . dim_element . margin_bottom // prettier-ignore
175+ ) ,
176+ left : Math . round (
177+ bounds . left -
178+ this . dim_element . left -
179+ this . dim_element . margin_left // prettier-ignore
180+ ) ,
181+ } ;
182+ }
183+
184+ /**
185+ * Get the bumping state of the element.
186+ *
187+ * @returns {Object } The bumping state.
188+ */
189+ get_bumping_state ( ) {
190+ const pos_el = this . _get_element_positions ( ) ;
191+ const pos_x = this . _get_container_positions ( this . container_x , this . dim_container_x ) ; // prettier-ignore
192+ const pos_y = this . _get_container_positions ( this . container_y , this . dim_container_y ) ; // prettier-ignore
193+
194+ const bump_top = pos_el . top <= pos_y . top && pos_el . bottom >= pos_y . top ;
195+ const bump_right = pos_el . right >= pos_x . right && pos_el . left <= pos_x . right ;
196+ const bump_bottom = pos_el . bottom >= pos_y . bottom && pos_el . top <= pos_y . bottom ;
197+ const bump_left = pos_el . left <= pos_x . left && pos_el . right >= pos_x . left ;
198+
199+ const is_bumping = bump_top || bump_right || bump_bottom || bump_left ;
200+
201+ return {
202+ bump_top,
203+ bump_right,
204+ bump_bottom,
205+ bump_left,
206+ is_bumping,
207+ } ;
208+ }
209+
210+ /**
211+ * Set the bumping classes on the element.
212+ */
213+ set_bumping_classes ( ) {
214+ const bumping_state = this . get_bumping_state ( ) ;
215+
216+ const classes_to_add = [ ] ;
217+ const classes_to_remove = [ ] ;
218+
219+ if ( bumping_state . is_bumping ) {
220+ this . options . bump . add && classes_to_add . push ( this . options . bump . add ) ;
221+ this . options . bump . remove && classes_to_remove . push ( this . options . bump . remove ) ;
222+
223+ bumping_state . bump_top
224+ ? classes_to_add . push ( "bumped-top" )
225+ : classes_to_remove . push ( "bumped-top" ) ;
226+ bumping_state . bump_right
227+ ? classes_to_add . push ( "bumped-right" )
228+ : classes_to_remove . push ( "bumped-right" ) ;
229+ bumping_state . bump_bottom
230+ ? classes_to_add . push ( "bumped-bottom" )
231+ : classes_to_remove . push ( "bumped-bottom" ) ;
232+ bumping_state . bump_left
233+ ? classes_to_add . push ( "bumped-left" )
234+ : classes_to_remove . push ( "bumped-left" ) ;
235+ } else {
236+ this . options . unbump . add && classes_to_add . push ( this . options . unbump . add ) ;
237+ this . options . unbump . remove &&
238+ classes_to_remove . push ( this . options . unbump . remove ) ;
239+ classes_to_remove . push (
240+ "bumped-top" ,
241+ "bumped-right" ,
242+ "bumped-bottom" ,
243+ "bumped-left"
244+ ) ;
132245 }
246+
247+ this . el . classList . remove ( ...classes_to_remove ) ;
248+ this . el . classList . add ( ...classes_to_add ) ;
133249 }
134250}
135251
0 commit comments