Skip to content

Commit 1c28476

Browse files
Pixelclaude
authored andcommitted
Fix animation stagger delay on SPA navigation
On View Transitions navigation, elements in the viewport now appear instantly instead of getting staggered delays that caused the hero image to show before text. Below-the-fold elements still animate with stagger on scroll. Also disconnects old IntersectionObserver before creating a new one to prevent memory leaks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dfad587 commit 1c28476

1 file changed

Lines changed: 48 additions & 25 deletions

File tree

src/components/common/BasicScripts.astro

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,14 @@ import { UI } from 'astrowind:config';
172172
animationCounter: 0,
173173
elements: null,
174174

175-
start() {
176-
const selectors = [
175+
start(isNavigation) {
176+
if (this.observer) {
177+
this.observer.disconnect();
178+
}
179+
180+
this.animationCounter = 0;
181+
182+
var selectors = [
177183
'[class*=" intersect:"]',
178184
'[class*=":intersect:"]',
179185
'[class^="intersect:"]',
@@ -185,30 +191,43 @@ import { UI } from 'astrowind:config';
185191

186192
this.elements = Array.from(document.querySelectorAll(selectors.join(',')));
187193

188-
const getThreshold = (element) => {
194+
var getThreshold = function (element) {
189195
if (element.classList.contains('intersect-full')) return 0.99;
190196
if (element.classList.contains('intersect-half')) return 0.5;
191197
if (element.classList.contains('intersect-quarter')) return 0.25;
192198
return 0;
193199
};
194200

195-
this.elements.forEach((el) => {
201+
this.elements.forEach(function (el) {
196202
el.setAttribute('no-intersect', '');
197203
el._intersectionThreshold = getThreshold(el);
198204
});
199205

200-
const callback = (entries) => {
201-
entries.forEach((entry) => {
202-
requestAnimationFrame(() => {
203-
const target = entry.target;
204-
const intersectionRatio = entry.intersectionRatio;
205-
const threshold = target._intersectionThreshold;
206+
if (isNavigation) {
207+
var vh = window.innerHeight;
208+
this.elements.forEach(function (el) {
209+
var rect = el.getBoundingClientRect();
210+
if (rect.top < vh && rect.bottom > 0) {
211+
el.removeAttribute('no-intersect');
212+
el.setAttribute('data-animated', 'true');
213+
el.style.animationDuration = '0s';
214+
}
215+
});
216+
}
217+
218+
var self = this;
219+
var callback = function (entries) {
220+
entries.forEach(function (entry) {
221+
requestAnimationFrame(function () {
222+
var target = entry.target;
223+
var intersectionRatio = entry.intersectionRatio;
224+
var threshold = target._intersectionThreshold;
206225

207226
if (target.classList.contains('intersect-no-queue')) {
208227
if (entry.isIntersecting) {
209228
target.removeAttribute('no-intersect');
210229
if (target.classList.contains('intersect-once')) {
211-
this.observer.unobserve(target);
230+
self.observer.unobserve(target);
212231
}
213232
} else {
214233
target.setAttribute('no-intersect', '');
@@ -220,44 +239,48 @@ import { UI } from 'astrowind:config';
220239
if (!target.hasAttribute('data-animated')) {
221240
target.removeAttribute('no-intersect');
222241
target.setAttribute('data-animated', 'true');
242+
target.style.animationDuration = '';
223243

224-
const delay = this.animationCounter * this.delayBetweenAnimations;
225-
this.animationCounter++;
244+
var delay = self.animationCounter * self.delayBetweenAnimations;
245+
self.animationCounter++;
226246

227-
target.style.transitionDelay = `${delay}ms`;
228-
target.style.animationDelay = `${delay}ms`;
247+
target.style.transitionDelay = delay + 'ms';
248+
target.style.animationDelay = delay + 'ms';
229249

230250
if (target.classList.contains('intersect-once')) {
231-
this.observer.unobserve(target);
251+
self.observer.unobserve(target);
232252
}
233253
}
234254
} else {
235255
target.setAttribute('no-intersect', '');
236256
target.removeAttribute('data-animated');
237257
target.style.transitionDelay = '';
238258
target.style.animationDelay = '';
259+
target.style.animationDuration = '';
239260

240-
this.animationCounter = 0;
261+
self.animationCounter = 0;
241262
}
242263
});
243264
});
244265
};
245266

246-
this.observer = new IntersectionObserver(callback.bind(this), { threshold: [0, 0.25, 0.5, 0.99] });
267+
this.observer = new IntersectionObserver(callback, { threshold: [0, 0.25, 0.5, 0.99] });
247268

248-
this.elements.forEach((el) => {
249-
this.observer.observe(el);
269+
this.elements.forEach(function (el) {
270+
if (!el.hasAttribute('data-animated')) {
271+
self.observer.observe(el);
272+
}
250273
});
251274
},
252275

253-
/*
276+
/*
254277
REF: #643;
255278
We need to remove the delay to fix flickering/delay
256279
when toggling the theme. Observer only removes them
257280
after data-animated is gone (out of view).
258281
*/
259282
removeAnimationDelay() {
260-
this.elements.forEach((el) => {
283+
this.elements.forEach(function (el) {
261284
if (el.getAttribute('data-animated') === 'true') {
262285
el.style.transitionDelay = '';
263286
el.style.animationDelay = '';
@@ -266,9 +289,9 @@ import { UI } from 'astrowind:config';
266289
},
267290
};
268291

269-
Observer.start();
292+
Observer.start(false);
270293

271-
document.addEventListener('astro:after-swap', () => {
272-
Observer.start();
294+
document.addEventListener('astro:after-swap', function () {
295+
Observer.start(true);
273296
});
274297
</script>

0 commit comments

Comments
 (0)