GSAP Astro Animation Video
GSAP Image Transition Demo
The video above shows a transition effect built with Astro. Here are the key principles I discovered while implementing this (using OSMO sample code as reference, so I won't include the complete code):
Core Principles
1. Astro automatically executes scripts in <script>
tags after DOM loading
Astro automatically compiles TypeScript within components and evaluates it with defer by default. This means you don't need to wrap your code with document.addEventListener('DOMContentLoaded', () => {}).
<script is:inline>
// This behaves like a regular script tag and may execute out of order with DOM
</script>
Using is:inline makes the script behave like a traditional script tag, potentially causing timing issues with DOM manipulation.
2. Astro doesn't reapply stylesheets when JavaScript modifies components after initial DOM generation
When applying styles to elements created in Astro components, you must explicitly set all necessary styles. Don't assume styles will be automatically applied. Only the animated portions get re-rendered.
const pixel = document.createElement('div');
pixel.classList.add('pixelated-image-card__pixel');
pixel.style.width = `${pixelSize}px`;
pixel.style.height = `${pixelSize}px`;
pixel.style.top = `${row * pixelSize}px`;
pixel.style.left = `${col * pixelSize}px`;
pixel.style.position = 'absolute';
pixel.style.backgroundColor = 'orange';
pixel.style.display = 'none';
Detailed style specifications like this are essential to prevent bugs.
3. Component <style>
tag namespacing is scoped to individual components
This differs from Next.js behavior. For readability, I use Tailwind CSS and only write GSAP-override styles in <style>
tags. This works because Tailwind CSS is evaluated first, then overridden by styles in the <style>
tag. While components naturally avoid naming conflicts due to scoped execution, GSAP also manipulates DOM properties via CSS, so using different naming conventions between Tailwind and custom styles can lead to property name typos. I almost introduced a bug by writing display-none in Tailwind instead of hidden.
4. Astro seamlessly supports JavaScript data attributes like <div data-pixelated-image-reveal-grid="">
To avoid confusion with Tailwind CSS classes, it's cleaner to use data attributes instead of class names and query them from TypeScript using data attribute selectors.
<div data-pixelated-image-reveal-grid=""
class="w-full h-full absolute top-0 left-0 z-20">
<div class="pixelated-image-card__pixel"></div>
</div>
This approach separates concerns: use classes where appropriate and specify styles in the style section where needed.
const pixel = document.createElement('div');
pixel.classList.add('pixelated-image-card__pixel');
Since I'm creating elements with classes via JavaScript, I use class names here. Including empty elements helps with early detection of stylesheet errors and prevents unnecessary null errors.
5. Code within Astro <script>
tags defaults to TypeScript
The type inference is quite good, so you don't need to be overly conscious of typing. However, TypeScript will throw errors when calling methods on DOM elements that might be null. For small components where you know the DOM exists:
const activeCard = card.querySelector('[data-pixelated-image-reveal-active]') as HTMLDivElement;
Using as for casting is acceptable when the HTML structure makes the element's existence obvious.
activeCard!.style.display = isActive ? 'block' : 'none';
Using the ! operator in such cases is also reasonable.
Conclusion
These are the key considerations I've encountered so far. I'll likely discover more as I continue development and may write follow-up posts.
The ability to use GSAP in this manner showcases Astro's flexibility and is one of its strengths. While I'm familiar with Next.js and could provide Next.js examples for reference, for libraries like GSAP that have stable APIs, Astro's component-scoped namespacing and direct DOM manipulation capabilities offer significant advantages.
This post demonstrates how Astro's architecture makes it particularly well-suited for animation libraries and direct DOM manipulation scenarios.