Files
Webklar.com/node_modules/lenis/dist/lenis-snap.mjs
Basilosaurusrex f027651f9b main repo
2025-11-24 18:09:40 +01:00

350 lines
10 KiB
JavaScript

// packages/snap/src/debounce.ts
function debounce(callback, delay) {
let timer;
return function(...args) {
let context = this;
clearTimeout(timer);
timer = setTimeout(() => {
timer = void 0;
callback.apply(context, args);
}, delay);
};
}
// packages/snap/src/element.ts
function removeParentSticky(element) {
const position = getComputedStyle(element).position;
const isSticky = position === "sticky";
if (isSticky) {
element.style.setProperty("position", "static");
element.dataset.sticky = "true";
}
if (element.offsetParent) {
removeParentSticky(element.offsetParent);
}
}
function addParentSticky(element) {
if (element?.dataset?.sticky === "true") {
element.style.removeProperty("position");
delete element.dataset.sticky;
}
if (element.offsetParent) {
addParentSticky(element.offsetParent);
}
}
function offsetTop(element, accumulator = 0) {
const top = accumulator + element.offsetTop;
if (element.offsetParent) {
return offsetTop(element.offsetParent, top);
}
return top;
}
function offsetLeft(element, accumulator = 0) {
const left = accumulator + element.offsetLeft;
if (element.offsetParent) {
return offsetLeft(element.offsetParent, left);
}
return left;
}
function scrollTop(element, accumulator = 0) {
const top = accumulator + element.scrollTop;
if (element.offsetParent) {
return scrollTop(element.offsetParent, top);
}
return top + window.scrollY;
}
function scrollLeft(element, accumulator = 0) {
const left = accumulator + element.scrollLeft;
if (element.offsetParent) {
return scrollLeft(element.offsetParent, left);
}
return left + window.scrollX;
}
var SnapElement = class {
element;
options;
align;
// @ts-ignore
rect = {};
wrapperResizeObserver;
resizeObserver;
debouncedWrapperResize;
constructor(element, {
align = ["start"],
ignoreSticky = true,
ignoreTransform = false
} = {}) {
this.element = element;
this.options = { align, ignoreSticky, ignoreTransform };
this.align = [align].flat();
this.debouncedWrapperResize = debounce(this.onWrapperResize, 500);
this.wrapperResizeObserver = new ResizeObserver(this.debouncedWrapperResize);
this.wrapperResizeObserver.observe(document.body);
this.onWrapperResize();
this.resizeObserver = new ResizeObserver(this.onResize);
this.resizeObserver.observe(this.element);
this.setRect({
width: this.element.offsetWidth,
height: this.element.offsetHeight
});
}
destroy() {
this.wrapperResizeObserver.disconnect();
this.resizeObserver.disconnect();
}
setRect({
top,
left,
width,
height,
element
} = {}) {
top = top ?? this.rect.top;
left = left ?? this.rect.left;
width = width ?? this.rect.width;
height = height ?? this.rect.height;
element = element ?? this.rect.element;
if (top === this.rect.top && left === this.rect.left && width === this.rect.width && height === this.rect.height && element === this.rect.element)
return;
this.rect.top = top;
this.rect.y = top;
this.rect.width = width;
this.rect.height = height;
this.rect.left = left;
this.rect.x = left;
this.rect.bottom = top + height;
this.rect.right = left + width;
}
onWrapperResize = () => {
let top, left;
if (this.options.ignoreSticky) removeParentSticky(this.element);
if (this.options.ignoreTransform) {
top = offsetTop(this.element);
left = offsetLeft(this.element);
} else {
const rect = this.element.getBoundingClientRect();
top = rect.top + scrollTop(this.element);
left = rect.left + scrollLeft(this.element);
}
if (this.options.ignoreSticky) addParentSticky(this.element);
this.setRect({ top, left });
};
onResize = ([entry]) => {
if (!entry?.borderBoxSize[0]) return;
const width = entry.borderBoxSize[0].inlineSize;
const height = entry.borderBoxSize[0].blockSize;
this.setRect({ width, height });
};
};
// packages/snap/src/uid.ts
var index = 0;
function uid() {
return index++;
}
// packages/snap/src/snap.ts
var Snap = class {
constructor(lenis, {
type = "proximity",
lerp,
easing,
duration,
distanceThreshold = "50%",
// useless when type is "mandatory"
debounce: debounceDelay = 500,
onSnapStart,
onSnapComplete
} = {}) {
this.lenis = lenis;
this.options = {
type,
lerp,
easing,
duration,
distanceThreshold,
debounce: debounceDelay,
onSnapStart,
onSnapComplete
};
this.onWindowResize();
window.addEventListener("resize", this.onWindowResize, false);
this.onSnapDebounced = debounce(this.onSnap, this.options.debounce);
this.lenis.on("virtual-scroll", this.onSnapDebounced);
}
options;
elements = /* @__PURE__ */ new Map();
snaps = /* @__PURE__ */ new Map();
viewport = {
width: window.innerWidth,
height: window.innerHeight
};
isStopped = false;
onSnapDebounced;
currentSnapIndex;
/**
* Destroy the snap instance
*/
destroy() {
this.lenis.off("virtual-scroll", this.onSnapDebounced);
window.removeEventListener("resize", this.onWindowResize, false);
this.elements.forEach((element) => element.destroy());
}
/**
* Start the snap after it has been stopped
*/
start() {
this.isStopped = false;
}
/**
* Stop the snap
*/
stop() {
this.isStopped = true;
}
/**
* Add a snap to the snap instance
*
* @param value The value to snap to
* @param userData User data that will be forwarded through the snap event
* @returns Unsubscribe function
*/
add(value) {
const id = uid();
this.snaps.set(id, { value });
return () => this.snaps.delete(id);
}
/**
* Add an element to the snap instance
*
* @param element The element to add
* @param options The options for the element
* @returns Unsubscribe function
*/
addElement(element, options = {}) {
const id = uid();
this.elements.set(id, new SnapElement(element, options));
return () => this.elements.delete(id);
}
addElements(elements, options = {}) {
const map = elements.map((element) => this.addElement(element, options));
return () => {
map.forEach((remove) => {
remove();
});
};
}
onWindowResize = () => {
this.viewport.width = window.innerWidth;
this.viewport.height = window.innerHeight;
};
computeSnaps = () => {
const { isHorizontal } = this.lenis;
let snaps = [...this.snaps.values()];
this.elements.forEach(({ rect, align }) => {
let value;
align.forEach((align2) => {
if (align2 === "start") {
value = rect.top;
} else if (align2 === "center") {
value = isHorizontal ? rect.left + rect.width / 2 - this.viewport.width / 2 : rect.top + rect.height / 2 - this.viewport.height / 2;
} else if (align2 === "end") {
value = isHorizontal ? rect.left + rect.width - this.viewport.width : rect.top + rect.height - this.viewport.height;
}
if (typeof value === "number") {
snaps.push({ value: Math.ceil(value) });
}
});
});
snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value));
return snaps;
};
previous() {
this.goTo((this.currentSnapIndex ?? 0) - 1);
}
next() {
this.goTo((this.currentSnapIndex ?? 0) + 1);
}
goTo(index2) {
const snaps = this.computeSnaps();
if (snaps.length === 0) return;
this.currentSnapIndex = Math.max(0, Math.min(index2, snaps.length - 1));
const currentSnap = snaps[this.currentSnapIndex];
if (currentSnap === void 0) return;
this.lenis.scrollTo(currentSnap.value, {
duration: this.options.duration,
easing: this.options.easing,
lerp: this.options.lerp,
lock: this.options.type === "lock",
userData: { initiator: "snap" },
onStart: () => {
this.options.onSnapStart?.({
index: this.currentSnapIndex,
...currentSnap
});
},
onComplete: () => {
this.options.onSnapComplete?.({
index: this.currentSnapIndex,
...currentSnap
});
}
});
}
get distanceThreshold() {
let distanceThreshold = Infinity;
if (this.options.type === "mandatory") return Infinity;
const { isHorizontal } = this.lenis;
const axis = isHorizontal ? "width" : "height";
if (typeof this.options.distanceThreshold === "string" && this.options.distanceThreshold.endsWith("%")) {
distanceThreshold = Number(this.options.distanceThreshold.replace("%", "")) / 100 * this.viewport[axis];
} else if (typeof this.options.distanceThreshold === "number") {
distanceThreshold = this.options.distanceThreshold;
} else {
distanceThreshold = this.viewport[axis];
}
return distanceThreshold;
}
onSnap = (e) => {
if (this.isStopped) return;
if (e.event.type === "touchmove") return;
if (this.options.type === "lock" && this.lenis.userData?.initiator === "snap")
return;
let { scroll, isHorizontal } = this.lenis;
const delta = isHorizontal ? e.deltaX : e.deltaY;
scroll = Math.ceil(this.lenis.scroll + delta);
const snaps = this.computeSnaps();
if (snaps.length === 0) return;
let snapIndex;
const prevSnapIndex = snaps.findLastIndex(({ value }) => value < scroll);
const nextSnapIndex = snaps.findIndex(({ value }) => value > scroll);
if (this.options.type === "lock") {
if (delta > 0) {
snapIndex = nextSnapIndex;
} else if (delta < 0) {
snapIndex = prevSnapIndex;
}
} else {
const prevSnap = snaps[prevSnapIndex];
const distanceToPrevSnap = prevSnap ? Math.abs(scroll - prevSnap.value) : Infinity;
const nextSnap = snaps[nextSnapIndex];
const distanceToNextSnap = nextSnap ? Math.abs(scroll - nextSnap.value) : Infinity;
snapIndex = distanceToPrevSnap < distanceToNextSnap ? prevSnapIndex : nextSnapIndex;
}
if (snapIndex === void 0) return;
if (snapIndex === -1) return;
snapIndex = Math.max(0, Math.min(snapIndex, snaps.length - 1));
const snap = snaps[snapIndex];
const distance = Math.abs(scroll - snap.value);
if (distance <= this.distanceThreshold) {
this.goTo(snapIndex);
}
};
resize() {
this.elements.forEach((element) => element.onWrapperResize());
}
};
export {
Snap as default
};
//# sourceMappingURL=lenis-snap.mjs.map