The correct way
If you look at the Astro documentation on passing server variables to the client, you will see this:
---
// This is copied from their website
const foregroundColor = "rgb(221 243 228)";
const backgroundColor = "rgb(24 121 78)";
const message = "Astro is awesome!";
---
<style define:vars={{ textColor: foregroundColor, backgroundColor }}>
h1 {
background-color: var(--backgroundColor);
color: var(--textColor);
}
</style>
<script define:vars={{ message }}>
alert(message);
</script>
I use this on my website, specifically on my graduation post. When the page is visited, a confetti effect should appear.
---
const { graduation = false, duration = 15 } = Astro.props;
---
<script
src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"
></script>
<!-- https://github.com/catdad/canvas-confetti --><!-- https://www.kirilv.com/canvas-confetti/ -->
<script lang="javascript" define:vars={{ graduation, duration }}>
const duration_in_sec = duration * 1000;
const gradColors = ["#B3A369", "#003057", "#FFFFFF"];
const animationEnd = Date.now() + duration_in_sec;
...
We can make 2 observations here:
- the variables passed are constants
- this is incredibly boring.
The wrong way (which is much more powerful)
Lets try something a bit more fun, and take a look at the click animation code (desktop only).
On some pages, I disable this functionality as it gets in the way (trying to select numbers, dropdowns, etc). Therefore I previsouly thought this pattern would work
// BaseLayout.astro
---
...
type Props = {
title: string;
description: string;
invert?: boolean;
enableAnimation?: boolean;
};
const { title, description, invert, enableAnimation = true } = Astro.props;
---
...
<script define:vars={{ enableAnimation }}>
import { playAnimation, burst, bubbles } from "~/utils/clickAnim";
if (enableAnimation && window.navigator.maxTouchPoints < 2) {
const prevAnim: HTMLElement = document.querySelector(
"[data-name='mojs-shape']",
) as HTMLElement;
document.body.addEventListener("click", (e) => {
burst.tune({ x: e.pageX, y: e.pageY }).generate().replay();
bubbles.tune({ x: e.pageX, y: e.pageY }).generate().replay();
playAnimation(e);
setTimeout(() => {
prevAnim.style.transform = "scale(0)";
}, 900);
});
}
</script>
...
Sensible, right? No. This yields an
Uncaught SyntaxError: Cannot use import statement outside a module
This is due to an import statement in the ~/utils/clickAnim
file, import { Shape } from "@mojs/core";
.
So we may ask ourselves, how does one resolve this? A pull request? Astro recommendations?
Just inject the variables in your HTML.
<span data-animEnable style="display: none;">{enableAnimation}</span>
<script>
import { playAnimation, burst, bubbles } from "~/utils/clickAnim";
const enableAnimation =
document.querySelectorAll("[data-animEnable]")[0].textContent;
...
This HiGhLY aDvAncEd mEthOd lets Astro bundle the script, instead of having it inline (like the correct method does).
I find this simpler, and less of a hassle. Obviously do not pass anything sensitive like this, but I enjoy my workaround. Beware, your variables are now strings, so evaluate them.
Written on: