Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions components/AnimationInitializer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ function getElement(layerId: string): HTMLElement | null {
return document.querySelector(`[data-layer-id="${layerId}"]`);
}

/**
* Whether a mutated node lives inside a Swiper slider. Swiper reshuffles
* existing slide nodes on transition/loop (childList mutations), which must
* not trigger an animation rebind — real new content arrives via the
* ITEMS_INJECTED_EVENT path instead.
*/
function isInsideSlider(node: Node | null): boolean {
const el = node instanceof Element ? node : node?.parentElement ?? null;
return !!el?.closest('[data-slider-id], .swiper-wrapper');
}

/**
* Pre-paint each tween's `from` state so the element rests in its intended
* initial appearance before the trigger fires. CSS via generateInitialAnimationCSS()
Expand Down Expand Up @@ -712,6 +723,10 @@ export default function AnimationInitializer({ layers, injectInitialCSS }: Anima
const remountObserver = new MutationObserver(mutations => {
for (const m of mutations) {
if (m.type !== 'childList') continue;
// Skip Swiper's internal slide reshuffling (loop/transition) so a
// slide change doesn't rebind animations and reset user-toggled
// click/hover states (e.g. an open nav menu) elsewhere on the page.
if (isInsideSlider(m.target)) continue;
for (const n of m.addedNodes) {
if (containsTrackedId(n)) {
scheduleRebind();
Expand Down