Motion+

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.

Vue

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.