Skip to content

Commit 5a412cb

Browse files
committed
perf: optimize website animations and transitions for better performance (#331)
# Performance Optimization for Website Animations and Effects This PR improves website performance by optimizing animations and visual effects across multiple components: - Simplified text shadows and reduced shadow complexity for better rendering performance - Replaced continuous animations with static effects where appropriate - Optimized the CodeOverlay component by: - Reducing transition time from 0.3s to 0.2s - Replacing complex custom scroll animation with native smooth scrolling - Removing unnecessary animation keyframes - Simplifying hover effects while maintaining visual appeal - Enhanced TestimonialCarousel performance with: - GPU acceleration via `will-change` and `transform: translateZ(0)` - Using `translate3d` for smoother animations - Simplified gradients and shadows - Added proper support for users who prefer reduced motion - Improved homepage CTA cards by: - Reducing shadow complexity - Removing continuous shine animation - Adding subtle transform on hover instead These changes maintain the site's visual appeal while significantly reducing CPU/GPU load, especially on mobile devices and lower-powered systems.
1 parent ea265ed commit 5a412cb

File tree

5 files changed

+229
-224
lines changed

5 files changed

+229
-224
lines changed

pkgs/website/src/components/CodeOverlay.astro

Lines changed: 43 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
align-items: center;
3838
justify-content: center;
3939
z-index: 5;
40-
transition: opacity 0.3s ease;
40+
transition: opacity 0.2s ease;
4141
}
4242

4343
.code-overlay.hidden {
@@ -55,13 +55,9 @@
5555
font-weight: 900;
5656
color: var(--sl-color-text);
5757
margin-bottom: 1.5rem;
58-
text-shadow: 0 12px 60px var(--sl-color-bg),
59-
0 10px 50px var(--sl-color-bg),
60-
0 8px 40px var(--sl-color-bg),
61-
0 6px 30px var(--sl-color-bg),
62-
0 4px 20px var(--sl-color-bg),
63-
0 2px 12px var(--sl-color-bg),
64-
0 0 80px rgba(0, 123, 110, 1);
58+
/* Simplified text-shadow for better performance */
59+
text-shadow: 0 2px 20px rgba(0, 123, 110, 0.8),
60+
0 4px 40px var(--sl-color-bg);
6561
}
6662

6763
.overlay-list {
@@ -71,13 +67,9 @@
7167
margin-bottom: 2rem;
7268
text-align: left;
7369
font-weight: 600;
74-
text-shadow: 0 12px 60px var(--sl-color-bg),
75-
0 10px 50px var(--sl-color-bg),
76-
0 8px 40px var(--sl-color-bg),
77-
0 6px 30px var(--sl-color-bg),
78-
0 4px 20px var(--sl-color-bg),
79-
0 2px 12px var(--sl-color-bg),
80-
0 0 80px rgba(0, 123, 110, 1);
70+
/* Simplified text-shadow for better performance */
71+
text-shadow: 0 2px 20px rgba(0, 123, 110, 0.8),
72+
0 4px 40px var(--sl-color-bg);
8173
}
8274

8375
/* Base button styling following Starlight's pattern */
@@ -97,28 +89,16 @@
9789
padding: 0.9375rem 1.25rem;
9890
text-decoration: none;
9991

100-
/* Glow and animation effects */
92+
/* Static glow effect - no continuous animation for better performance */
10193
box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4),
102-
0 0 60px rgba(0, 123, 110, 0.25);
103-
animation: pulse-glow 2s ease-in-out infinite;
94+
0 0 40px rgba(0, 123, 110, 0.2);
10495
position: relative;
10596
overflow: hidden;
10697
pointer-events: auto;
10798

10899
/* Smooth transitions */
109-
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
110-
box-shadow 0.5s cubic-bezier(0.4, 0, 0.2, 1);
111-
}
112-
113-
@keyframes pulse-glow {
114-
0%, 100% {
115-
box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4),
116-
0 0 60px rgba(0, 123, 110, 0.25);
117-
}
118-
50% {
119-
box-shadow: 0 8px 40px rgba(0, 123, 110, 0.55),
120-
0 0 80px rgba(0, 123, 110, 0.4);
121-
}
100+
transition: transform 0.3s ease,
101+
box-shadow 0.3s ease;
122102
}
123103

124104
.reveal-button::before {
@@ -137,10 +117,9 @@
137117

138118
.reveal-button.sl-button-primary:hover {
139119
color: var(--sl-color-black);
140-
transform: scale(1.08);
141-
box-shadow: 0 8px 32px rgba(0, 123, 110, 0.4),
142-
0 0 60px rgba(0, 123, 110, 0.25);
143-
animation: none;
120+
transform: scale(1.05);
121+
box-shadow: 0 8px 40px rgba(0, 123, 110, 0.5),
122+
0 0 50px rgba(0, 123, 110, 0.3);
144123
}
145124

146125
.reveal-button:hover::before {
@@ -251,8 +230,8 @@
251230
.reveal-button::before,
252231
.reset-overlay-button,
253232
.scroll-top-button {
254-
transition: none;
255-
animation: none;
233+
transition: none !important;
234+
animation: none !important;
256235
}
257236
}
258237

@@ -275,7 +254,6 @@
275254
min-height: 0;
276255
overflow: auto; /* The actual scroller */
277256
padding: 0;
278-
scroll-behavior: smooth;
279257
scrollbar-width: thin;
280258
scrollbar-color: var(--sl-color-accent) var(--sl-color-gray-5);
281259
}
@@ -324,10 +302,7 @@
324302
}
325303
}
326304

327-
// Global flag to track if auto-scroll animation is running
328-
let isAutoScrolling = false;
329-
330-
// Flag to track if programmatic scroll is happening (auto-scroll or scroll-to-top)
305+
// Flag to track if programmatic scroll is happening (scroll-to-top button)
331306
let isProgrammaticScroll = false;
332307

333308
// Flag to track if manual scroll event has been sent (only send once)
@@ -341,111 +316,45 @@
341316

342317
if (!button || !overlay || !scrollableContainer || !scrollableInner) return;
343318

344-
button.addEventListener('click', async () => {
319+
button.addEventListener('click', () => {
345320
// Track custom event in Plausible
346321
if (typeof window.plausible !== 'undefined') {
347322
window.plausible('home:reveal-code');
348323
}
349324

350-
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
351-
352-
// Helper to animate scroll with custom duration (cancellable on user interaction)
353-
const animateScroll = (element: HTMLElement, targetScroll: number, duration: number) => {
354-
return new Promise<void>((resolve) => {
355-
const start = element.scrollTop;
356-
const distance = targetScroll - start;
357-
const startTime = performance.now();
358-
let animationId: number | null = null;
359-
let cancelled = false;
360-
361-
// Cancel animation if user manually scrolls
362-
const cancelAnimation = () => {
363-
cancelled = true;
364-
isAutoScrolling = false;
365-
if (animationId !== null) {
366-
cancelAnimationFrame(animationId);
367-
}
368-
cleanup();
369-
resolve();
370-
};
371-
372-
// Listen for user scroll interactions
373-
const handleWheel = () => cancelAnimation();
374-
const handleTouch = () => cancelAnimation();
375-
const handleClick = () => cancelAnimation();
376-
const handleKeydown = (e: KeyboardEvent) => {
377-
if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(e.key)) {
378-
cancelAnimation();
379-
}
380-
};
381-
382-
element.addEventListener('wheel', handleWheel, { passive: true });
383-
element.addEventListener('touchstart', handleTouch, { passive: true });
384-
element.addEventListener('click', handleClick);
385-
element.addEventListener('keydown', handleKeydown);
386-
387-
const cleanup = () => {
388-
element.removeEventListener('wheel', handleWheel);
389-
element.removeEventListener('touchstart', handleTouch);
390-
element.removeEventListener('click', handleClick);
391-
element.removeEventListener('keydown', handleKeydown);
392-
};
393-
394-
const scroll = (currentTime: number) => {
395-
if (cancelled) return;
396-
397-
const elapsed = currentTime - startTime;
398-
const progress = Math.min(elapsed / duration, 1);
399-
400-
// Ease-in-out quadratic for gentle smoothing
401-
const eased = progress < 0.5
402-
? 2 * progress * progress
403-
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
404-
405-
element.scrollTop = start + distance * eased;
406-
407-
if (progress < 1) {
408-
animationId = requestAnimationFrame(scroll);
409-
} else {
410-
cleanup();
411-
isAutoScrolling = false;
412-
resolve();
413-
}
414-
};
415-
416-
animationId = requestAnimationFrame(scroll);
417-
});
418-
};
419-
420-
// 1. Enable scrolling and start animation first
421-
isAutoScrolling = true;
422325
scrollableContainer.classList.add('scrollable-enabled');
423-
const scrollPromise = animateScroll(scrollableInner, scrollableInner.scrollHeight, 3000);
326+
overlay.style.opacity = '0';
424327

425-
// Small delay so scroll starts accelerating before overlay fades
426-
await wait(100);
328+
// Custom 3-second scroll animation
329+
const startScroll = scrollableInner.scrollTop;
330+
const targetScroll = scrollableInner.scrollHeight;
331+
const distance = targetScroll - startScroll;
332+
const duration = 3000; // 3 seconds
333+
const startTime = performance.now();
427334

428-
// 2. Fade out overlay while scroll is already in motion
429-
overlay.style.opacity = '0';
335+
function easeOutCubic(t: number) {
336+
return 1 - Math.pow(1 - t, 3);
337+
}
430338

431-
// Wait for overlay fade, then hide it
432-
await wait(300);
433-
overlay.classList.add('hidden');
339+
function animateScroll(currentTime: number) {
340+
const elapsed = currentTime - startTime;
341+
const progress = Math.min(elapsed / duration, 1);
342+
const easedProgress = easeOutCubic(progress);
434343

435-
// 3. Wait for scroll to complete
436-
await scrollPromise;
344+
scrollableInner.scrollTop = startScroll + (distance * easedProgress);
437345

438-
// 4. Show reset button after scroll completes
439-
const resetButton = document.querySelector('.reset-overlay-button') as HTMLElement;
440-
if (resetButton) {
441-
resetButton.style.display = 'block';
346+
if (progress < 1) {
347+
requestAnimationFrame(animateScroll);
348+
}
442349
}
443350

444-
// 5. Trigger scroll-to-top button visibility check now that animation is done
445-
const scrollTopButton = document.querySelector('.scroll-top-button') as HTMLElement;
446-
if (scrollTopButton && scrollableInner.scrollTop > 50) {
447-
scrollTopButton.style.display = 'block';
448-
}
351+
requestAnimationFrame(animateScroll);
352+
353+
setTimeout(() => overlay.classList.add('hidden'), 200);
354+
setTimeout(() => {
355+
const resetButton = document.querySelector('.reset-overlay-button') as HTMLElement;
356+
if (resetButton) resetButton.style.display = 'block';
357+
}, 2000);
449358
});
450359
};
451360

@@ -457,12 +366,6 @@
457366

458367
// Show/hide button based on scroll position
459368
const updateButtonVisibility = () => {
460-
// Don't show button during auto-scroll animation
461-
if (isAutoScrolling) {
462-
button.style.display = 'none';
463-
return;
464-
}
465-
466369
// Track manual scroll event (only once, and only if not programmatic)
467370
if (!hasTrackedManualScroll && !isProgrammaticScroll) {
468371
hasTrackedManualScroll = true;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script is:inline>
2+
// Slow scroll configuration - change DURATION to adjust speed
3+
const DURATION = 3000; // milliseconds for scroll animation
4+
5+
function setupSlowScroll() {
6+
const scrollable = document.querySelector('.scrollable-inner');
7+
console.log('SlowScroll: scrollable element found:', !!scrollable);
8+
if (!scrollable) return;
9+
10+
let animationId = null;
11+
let startY = scrollable.scrollTop;
12+
let targetY = scrollable.scrollTop;
13+
let startTime = null;
14+
15+
function easeOutCubic(t) {
16+
return 1 - Math.pow(1 - t, 3);
17+
}
18+
19+
function animate(currentTime) {
20+
if (!startTime) startTime = currentTime;
21+
const elapsed = currentTime - startTime;
22+
const progress = Math.min(elapsed / DURATION, 1);
23+
24+
const easedProgress = easeOutCubic(progress);
25+
scrollable.scrollTop = startY + (targetY - startY) * easedProgress;
26+
27+
if (progress < 1) {
28+
animationId = requestAnimationFrame(animate);
29+
} else {
30+
animationId = null;
31+
startY = scrollable.scrollTop;
32+
}
33+
}
34+
35+
scrollable.addEventListener('wheel', (e) => {
36+
console.log('SlowScroll: wheel event captured');
37+
e.preventDefault();
38+
e.stopPropagation();
39+
40+
targetY += e.deltaY;
41+
targetY = Math.max(0, Math.min(targetY, scrollable.scrollHeight - scrollable.clientHeight));
42+
43+
if (animationId === null) {
44+
startY = scrollable.scrollTop;
45+
startTime = null;
46+
animationId = requestAnimationFrame(animate);
47+
}
48+
}, { passive: false, capture: true });
49+
50+
console.log('SlowScroll: wheel listener attached');
51+
}
52+
53+
if (document.readyState === 'loading') {
54+
document.addEventListener('DOMContentLoaded', setupSlowScroll);
55+
} else {
56+
setupSlowScroll();
57+
}
58+
</script>

pkgs/website/src/components/TestimonialCarousel.astro

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,29 +94,29 @@ const duplicatedTestimonials = [...testimonials, ...testimonials];
9494
gap: 2rem;
9595
animation: scroll-left 120s linear infinite;
9696
width: fit-content;
97+
/* Performance optimizations */
98+
will-change: transform;
99+
transform: translateZ(0); /* Force GPU acceleration */
97100
}
98101

99102
@keyframes scroll-left {
100103
0% {
101-
transform: translateX(0);
104+
transform: translate3d(0, 0, 0);
102105
}
103106
100% {
104-
transform: translateX(-50%);
107+
transform: translate3d(-50%, 0, 0);
105108
}
106109
}
107110

108111
.testimonial-card {
109112
min-width: 400px;
110113
max-width: 400px;
111114
padding: 1.5rem;
112-
background: linear-gradient(
113-
135deg,
114-
color-mix(in srgb, var(--sl-color-accent-high) 4%, transparent) 0%,
115-
color-mix(in srgb, var(--sl-color-accent-high) 1%, transparent) 100%
116-
);
117-
border: 1px solid color-mix(in srgb, var(--sl-color-accent-high) 16%, transparent);
115+
/* Simplified gradient for better performance */
116+
background: rgba(0, 123, 110, 0.04);
117+
border: 1px solid rgba(0, 123, 110, 0.16);
118118
border-radius: 8px;
119-
box-shadow: 0 4px 12px color-mix(in srgb, var(--sl-color-accent-high) 3%, transparent);
119+
box-shadow: 0 4px 12px rgba(0, 123, 110, 0.03);
120120
flex-shrink: 0;
121121
display: flex;
122122
flex-direction: column;
@@ -181,4 +181,12 @@ const duplicatedTestimonials = [...testimonials, ...testimonials];
181181
font-size: 0.9rem;
182182
}
183183
}
184+
185+
/* Disable animation for users who prefer reduced motion */
186+
@media (prefers-reduced-motion: reduce) {
187+
.testimonial-track {
188+
animation: none !important;
189+
will-change: auto !important;
190+
}
191+
}
184192
</style>

0 commit comments

Comments
 (0)