Skip to content

Commit dc912e8

Browse files
authored
Merge pull request #1094 from Patternslib/pat-bumper-regressions
Pat bumper regressions
2 parents a6ffe12 + e66b987 commit dc912e8

File tree

4 files changed

+379
-287
lines changed

4 files changed

+379
-287
lines changed

src/pat/bumper/_bumper.scss

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/pat/bumper/bumper.js

Lines changed: 208 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
22
import dom from "../../core/dom";
3+
import events from "../../core/events";
34
import Parser from "../../core/parser";
4-
import registry from "@patternslib/patternslib/src/core/registry";
5+
import registry from "../../core/registry";
56
import utils from "../../core/utils";
67

78
export 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

Comments
 (0)