Skip to content

Commit 5e8e9b2

Browse files
committed
feat: lazy load blog images with animations
1 parent 3528284 commit 5e8e9b2

1 file changed

Lines changed: 66 additions & 1 deletion

File tree

src/pages/blog/index.astro

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ const posts = (await getCollection("blog")).sort(
9797
gap: 0.25rem;
9898
}
9999
}
100+
101+
ul li {
102+
opacity: 0;
103+
transform: translateY(30px);
104+
}
105+
ul li.visible {
106+
animation: puzzleClick 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
107+
}
108+
@keyframes puzzleClick {
109+
0% {
110+
opacity: 0;
111+
transform: translateY(30px);
112+
}
113+
50% {
114+
opacity: 1;
115+
transform: translateY(-8px);
116+
}
117+
100% {
118+
opacity: 1;
119+
transform: translateY(0);
120+
}
121+
}
100122
</style>
101123
</head>
102124
<body>
@@ -109,7 +131,13 @@ const posts = (await getCollection("blog")).sort(
109131
<li>
110132
<a href={`/blog/${post.id}/`}>
111133
{post.data.heroImage && (
112-
<Image src={post.data.heroImage} alt="" class="thumbnail" />
134+
<Image
135+
src={post.data.heroImage}
136+
alt=""
137+
class="thumbnail"
138+
loading="lazy"
139+
decoding="async"
140+
/>
113141
)}
114142
<div class="content">
115143
<div class="header-row">
@@ -128,5 +156,42 @@ const posts = (await getCollection("blog")).sort(
128156
</section>
129157
</main>
130158
<Footer />
159+
<script>
160+
const init = () => {
161+
const observer = new IntersectionObserver(
162+
(entries) => {
163+
const visibleEntries = entries
164+
.filter((entry) => entry.isIntersecting)
165+
.sort(
166+
(a, b) => a.boundingClientRect.top - b.boundingClientRect.top,
167+
);
168+
169+
const delayPerItem = Math.min(
170+
100,
171+
300 / Math.max(visibleEntries.length, 1),
172+
);
173+
174+
visibleEntries.forEach((entry, index) => {
175+
const item = entry.target as HTMLElement;
176+
item.style.animationDelay = `${index * delayPerItem}ms`;
177+
item.classList.add("visible");
178+
observer.unobserve(entry.target);
179+
});
180+
},
181+
{ rootMargin: "50px", threshold: 0.1 },
182+
);
183+
184+
document
185+
.querySelectorAll("ul li")
186+
.forEach((item) => observer.observe(item));
187+
};
188+
189+
// Run when idle or after 100ms max
190+
if ("requestIdleCallback" in window) {
191+
requestIdleCallback(init, { timeout: 100 });
192+
} else {
193+
setTimeout(init, 0);
194+
}
195+
</script>
131196
</body>
132197
</html>

0 commit comments

Comments
 (0)