1
1
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern" ;
2
2
import dom from "../../core/dom" ;
3
+ import events from "../../core/events" ;
3
4
import Parser from "../../core/parser" ;
4
- import registry from "@patternslib/patternslib/src /core/registry" ;
5
+ import registry from "../.. /core/registry" ;
5
6
import utils from "../../core/utils" ;
6
7
7
8
export const parser = new Parser ( "bumper" ) ;
@@ -15,121 +16,236 @@ class Pattern extends BasePattern {
15
16
static name = "bumper" ;
16
17
static trigger = ".pat-bumper" ;
17
18
static parser = parser ;
19
+ ticking = false ;
18
20
19
21
async init ( ) {
20
- // Based on: https://davidwalsh.name/detect-sticky
21
-
22
22
this . target_element = this . options . selector
23
23
? document . querySelector ( this . options . selector )
24
24
: this . el ;
25
25
26
26
// wait for next repaint for things to settle.
27
27
// e.g. CSS applied for injected content.
28
28
await utils . timeout ( 1 ) ;
29
- this . _init ( ) ;
30
- }
31
29
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 = {
45
55
top : dom . get_css_value ( this . el , "top" , true ) ,
46
56
right : dom . get_css_value ( this . el , "right" , true ) ,
47
57
bottom : dom . get_css_value ( this . el , "bottom" , true ) ,
48
58
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 ) ,
49
63
} ;
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
- } ;
55
64
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
+ }
71
100
}
72
101
) ;
73
- observer_x . observe ( this . el ) ;
74
102
}
103
+ this . set_bumping_classes ( ) ;
75
104
}
76
105
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
+ }
87
120
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 ( ) ;
99
123
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
+ ) ;
132
245
}
246
+
247
+ this . el . classList . remove ( ...classes_to_remove ) ;
248
+ this . el . classList . add ( ...classes_to_add ) ;
133
249
}
134
250
}
135
251
0 commit comments