Element scroll-linked animation
An example of a scroll-linked animation using a specific element's scroll position using Motion for Vue's useScroll hook.
Source code
<script setup lang="ts">
import { motion, useScroll, useMotionValue, useMotionValueEvent, useDomRef, animate } from 'motion-v'
import { type CSSProperties } from 'vue'
const containerRef = useDomRef()
const { scrollXProgress } = useScroll({ container: containerRef })
const maskImage = useScrollOverflowMask(scrollXProgress)
const left = `0%`
const right = `100%`
const leftInset = `20%`
const rightInset = `80%`
const transparent = `#0000`
const opaque = `#000`
function useScrollOverflowMask(scrollXProgress) {
const maskImage = useMotionValue(
`linear-gradient(90deg, ${opaque}, ${opaque} ${left}, ${opaque} ${rightInset}, ${transparent})`
)
useMotionValueEvent(scrollXProgress, "change", (value) => {
if (value === 0) {
animate(
maskImage,
`linear-gradient(90deg, ${opaque}, ${opaque} ${left}, ${opaque} ${rightInset}, ${transparent})`
)
} else if (value === 1) {
animate(
maskImage,
`linear-gradient(90deg, ${transparent}, ${opaque} ${leftInset}, ${opaque} ${right}, ${opaque})`
)
} else if (
scrollXProgress.getPrevious() === 0 ||
scrollXProgress.getPrevious() === 1
) {
animate(
maskImage,
`linear-gradient(90deg, ${transparent}, ${opaque} ${leftInset}, ${opaque} ${rightInset}, ${transparent})`
)
}
})
return maskImage
}
/**
* ============== Styles ================
*/
const example: CSSProperties = {
width: '100vw',
maxWidth: '400px',
position: 'relative',
}
const progress: CSSProperties = {
position: 'absolute',
top: '-65px',
left: '-15px',
transform: 'rotate(-90deg)',
}
const bg: CSSProperties = {
stroke: 'var(--layer)',
}
const circle: CSSProperties = {
strokeDashoffset: 0,
strokeWidth: '10%',
fill: 'none',
}
const indicator: CSSProperties = {
stroke: 'var(--accent)',
}
const list: CSSProperties = {
display: 'flex',
listStyle: 'none',
height: '220px',
overflowX: 'scroll',
padding: '20px 0',
flex: '0 0 600px',
margin: '0 auto',
gap: '20px',
}
const listItem: CSSProperties = {
flex: '0 0 200px',
}
</script>
<template>
<div id="example" :style="example">
<svg id="progress" width="80" height="80" viewBox="0 0 100 100" :style="progress">
<circle cx="50" cy="50" r="30" pathLength="1" class="bg" :style="bg" />
<motion.circle cx="50" cy="50" r="30" class="indicator"
:style="{ ...circle, ...indicator, pathLength: scrollXProgress }" />
</svg>
<motion.ul ref="containerRef" :style="{ ...list, maskImage }">
<li :style="{ ...listItem, background: '#ff0088' }"></li>
<li :style="{ ...listItem, background: '#dd00ee' }"></li>
<li :style="{ ...listItem, background: '#9911ff' }"></li>
<li :style="{ ...listItem, background: '#0d63f8' }"></li>
<li :style="{ ...listItem, background: '#0cdcf7' }"></li>
<li :style="{ ...listItem, background: '#4ff0b7' }"></li>
</motion.ul>
</div>
</template>
<style>
#example ::-webkit-scrollbar {
height: 5px;
width: 5px;
background: #fff3;
-webkit-border-radius: 1ex;
}
#example ::-webkit-scrollbar-thumb {
background: var(--accent);
-webkit-border-radius: 1ex;
}
#example ::-webkit-scrollbar-corner {
background: #fff3;
}
</style>Related examples
Latest in Vue
Motion+
Unlock all 400+ examples
- Source code for every Plus example.
- Provide examples direct to your agent via Motion's MCP.
- Lifetime access to new examples and APIs.








