Skip to content

Commit c8b118b

Browse files
committed
feat: enhance performance by adding lazy loading and async decoding to images in PersonaBadge and AnimatedTestimonials components
1 parent cce1831 commit c8b118b

File tree

3 files changed

+90
-13
lines changed

3 files changed

+90
-13
lines changed

src/app/agency/page.tsx

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
'use client'
1+
"use client"
22

3-
import { useState } from 'react'
3+
import { useEffect, useRef, useState } from 'react'
44
import { AppSidebar } from '@/components/app-sidebar'
55
import { cn } from '@/lib/utils'
66
import {
@@ -38,17 +38,12 @@ export default function AgencyPage() {
3838
onClick={() => handlePersonaClick(persona)}
3939
className="group relative bg-gradient-to-br from-neutral-50 to-neutral-100 dark:from-neutral-800 dark:to-neutral-900 aspect-square rounded-xl hover:from-neutral-100 hover:to-neutral-200 dark:hover:from-neutral-700 dark:hover:to-neutral-800 transition-all duration-300 cursor-pointer border border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600 hover:shadow-lg overflow-hidden"
4040
>
41-
{/* Video Background */}
42-
<video
41+
{/* Video Background (lazy) */}
42+
<LazyAutoplayVideo
4343
className="absolute inset-0 w-full h-full object-cover rounded-xl"
44-
autoPlay
45-
loop
46-
muted
47-
playsInline
44+
src={persona.videoSrc}
4845
poster={persona.imageSrc}
49-
>
50-
<source src={persona.videoSrc} type="video/mp4" />
51-
</video>
46+
/>
5247

5348
{/* Mobile overlay */}
5449
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent rounded-xl md:hidden" />
@@ -113,6 +108,7 @@ export default function AgencyPage() {
113108
loop
114109
muted
115110
playsInline
111+
preload="metadata"
116112
poster={selectedPersona.imageSrc}
117113
>
118114
<source src={selectedPersona.videoSrc} type="video/mp4" />
@@ -204,4 +200,81 @@ export default function AgencyPage() {
204200
</Dialog>
205201
</div>
206202
)
207-
}
203+
}
204+
205+
// Lazy-load and autoplay mp4 only when in viewport
206+
function LazyAutoplayVideo({ src, poster, className }: { src: string; poster: string; className?: string }) {
207+
const ref = useRef<HTMLDivElement | null>(null)
208+
const videoRef = useRef<HTMLVideoElement | null>(null)
209+
const [inView, setInView] = useState(false)
210+
211+
useEffect(() => {
212+
const el = ref.current
213+
if (!el) return
214+
215+
const reducedMotion = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
216+
if (reducedMotion) return // Respect user preference; keep image poster
217+
218+
const observer = new IntersectionObserver(
219+
(entries) => {
220+
for (const e of entries) {
221+
if (e.isIntersecting) {
222+
setInView(true)
223+
} else {
224+
setInView(false)
225+
}
226+
}
227+
},
228+
{ root: null, threshold: 0.35 }
229+
)
230+
231+
observer.observe(el)
232+
return () => observer.disconnect()
233+
}, [])
234+
235+
useEffect(() => {
236+
const v = videoRef.current
237+
if (!v) return
238+
if (inView) {
239+
// Try to play when available
240+
const play = async () => {
241+
try {
242+
await v.play()
243+
} catch {
244+
// Autoplay may be blocked; ignore
245+
}
246+
}
247+
play()
248+
} else {
249+
v.pause()
250+
}
251+
}, [inView])
252+
253+
return (
254+
<div ref={ref} className={className}>
255+
{inView ? (
256+
<video
257+
ref={videoRef}
258+
className="w-full h-full object-cover"
259+
autoPlay
260+
loop
261+
muted
262+
playsInline
263+
preload="auto"
264+
poster={poster}
265+
>
266+
<source src={src} type="video/mp4" />
267+
</video>
268+
) : (
269+
// Poster-only until in view (no network fetch for video)
270+
<img
271+
src={poster}
272+
alt=""
273+
loading="lazy"
274+
decoding="async"
275+
className="w-full h-full object-cover"
276+
/>
277+
)}
278+
</div>
279+
)
280+
}

src/components/persona-badge.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export function PersonaBadge({
2222
className="w-full h-full object-cover"
2323
src={imageSrc}
2424
alt="Persona avatar"
25+
loading="lazy"
26+
decoding="async"
2527
/>
2628
</div>
2729
)
28-
}
30+
}

src/components/ui/animated-testimonials.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export const AnimatedTestimonials = ({
8585
width={500}
8686
height={500}
8787
draggable={false}
88+
loading="lazy"
89+
decoding="async"
8890
className="h-full w-full rounded-3xl object-cover object-center"
8991
/>
9092
</motion.div>

0 commit comments

Comments
 (0)