11-01-2025 11:23 IST
Integrate Motion with Astro
- TODO: Figure out Enter and Exit Animations for Astro + Motion
Motion is fully framework agnostic, therefore it works within any JavaScript environment, including Astro. In this guide, we’ll learn how to:
- Animate on enter and exit
- Perform view/layout animations
- Animate timeline sequences
- Animate when props change
- Change HTML and CSS when an element enters or leaves the viewport
- Animate on scroll
Install
Install Motion into your Astro project.
npm install motionEnter and exit animations
To animate elements when they enter and exit the DOM, we can use Vue’s Transition component.
<template> <Transition :css="false"> <h1>Hello world</h1> </Transition></template>
<style>h1 { opacity: 0;}</style>Start by creating an onEnter function with Motion’s animate function.
<script setup>import { animate } from "motion";
async function onEnter(el, onComplete) { await animate(el, { opacity: 1 }); onComplete();}</script>You can now provide this to Transition with the @enter event.
<Transition :css="false" @enter="onEnter"></Transition>Likewise, to animate when a component exits, you can make a onLeave function.
<script setup>import { animate } from "motion";
async function onLeave(el, onComplete) { await animate(el, { opacity: 0 }); onComplete();}</script>Then pass this to the @leave event.
<Transition :css="false" @leave="onLeave"></Transition>View animations
Motion’s powerful view() function (currently in Motion+ early access) can perform full page transitions, animate between different layouts and even between different elements.
By await-ing Vue’s nextTick function within view’s update function, we ensure the view animation starts after the page has been updated.
<script setup>import { ref, nextTick } from "vue";
const isOpen = ref(false);
function openModal() { view(async () => { isOpen.value = true; await nextTick(); }).enter({ opacity: 1 });}</script>
<template> <button @click="openModal">Open modal</button> <div :style="{ display: isOpen ? 'block' : 'none' }" class="modal"></div></template>Animate timeline sequences
onEnter and onLeave are passed the element provided as the first Transition child, in this case the ul:
<Transition :css="false" @enter="onEnter" @leave="onLeave"> <ul> <li></li> <li></li> <li></li> </ul></Transition>This is useful for building timeline animations scoped to the component:
async function onEnter(el, onComplete) { const sequence = [ [el, { opacity: 1 }], [el.querySelectorAll("li"), { y: 100, opacity: 1 }], ]; await animate(sequence); onComplete();}Animate on prop change
Using Vue’s watch function and refs, we can start watching changes in props passed into the component to trigger animate calls.
<script setup>import { useTemplateRef, defineProps, watch, onWatcherCleanup } from "vue";
const header = useTemplateRef("header");
const props = defineProps({ opacity: { type: Number, default: 1 },});
watch(props, async () => { const animation = animate(header.value, { opacity: props.opacity });});</script>Add a cleanup function to ensure animations are stopped when the component’s removed.
import { animate } from "vue";
watch(props, async () => { const animation = animate(header.value, { opacity: props.opacity }); onWatcherCleanup(() => animation.stop());});Viewport detection
With Motion’s inView function, it’s possible to detect when an element enters or leaves the viewport and change its appearance accordingly.
Start by adding a ref to track visibility state:
<script setup>import { ref, useTemplateRef } from "vue";const isInView = ref(false);const container = useTemplateRef("container");</script>We can attach a "container" ref to an element in our template:
<template> <div ref="container"></div></template>We can now pass this element to inView within an onMounted lifecycle hook.
onMounted(() => { inView(container.value, () => { isInView.value = true;
return () => { isInView.value = false; }; });});Finally, we want to make sure we clean up when the element’s removed using the onUnmounted hook:
<script setup>import { ref, onMounted, onUnmounted } from "vue";import { inView } from "motion";
const isInView = ref(false);const container = useTemplateRef("container");let stopViewTracking;
onMounted(() => { stopViewTracking = inView(container.value, () => { isInView.value = true;
return () => { isInView.value = false; }; });});
onUnmounted(() => stopViewTracking());</script>With this state now changing as the element enters/leaves the viewport, we can use this isInView state to animate with CSS by swapping out classes:
<template> <div ref="container" :class="{ 'in-view': isInView }"></div></template>
<style scoped>div { background-color: blue; transition: background-color 0.5s linear;}.in-view { background-color: red;}</style>Or to render different HTML entirely:
<div ref="container"> <p v-if="isInView">Element in view</p> <p v-else>Element out of view</p></div>Scroll-triggered animations
We can also use inView to trigger the animate function itself. This can be useful if we want to trigger more complex animation sequences or animate transforms independently of each other.
onMounted(() => { stopViewTracking = inView(container.value, () => { animate(container.value, { scale: 1.2 });
return () => { animate(container.value, { scale: 1 }); }; });});Scroll-linked animations
The scroll function can be used to link animations and functions to scroll progress by using Vue’s template refs and lifecycle functions as before.
import { scroll, animate } from "motion";import { onMounted, onUnmounted } from "vue";
let stopScrollAnimation;
onMounted(() => { stopScrollAnimation = scroll( animate(container.value, { transform: ["none", "rotate(90deg)"] }), );});
onUnmounted(() => stopScrollAnimation());Next
With Motion set up in your Vue project, we recommend you follow the rest of the Quick Start guide to begin learning how to use Motion’s animate, scroll and inView functions.