滚动驱动的图片序列动画只会播放第一个动画

前端开发 2026-07-09

我想在同一页上放置“多条基于滚动驱动的图片序列动画”。我尝试了很多次,但始终找不到解决办法。

无论我怎么尝试,只有最上面的一个在工作,其他的都是空白。你能帮我解决吗?

我的页面结构是这样的:

=========== header ===========
--------------------------------------
---   image sequence animation A   ---
--------------------------------------
<
--------------------------------------
---   image sequence animation B   ---
--------------------------------------
<
--------------------------------------
---   image sequence animation C   ---
--------------------------------------
<
============ footer ============

原始演示页面在这里:这里

演示页面的代码:

html

<div class="hero">
    <h1>Apple Sequence Animation</h1>
    <p>Scroll down to admire the magic.</p>
</div>

<div class="hero-sequence">
    <div class="sticky-element">
        <div class="sequence-element">
            <canvas width="1336" height="786"></canvas>
        </div>
    </div>
</div>

<footer>
    You are awesome!
</footer>

css

body {
    margin: 0;
    font-family: -apple-system, sans-serif;
    color: #1d1d1f;
}

h1 {
    font-size: 40px;
    margin: 0 0 10px;
}

p {
    font-size: 22px;
}

.hero {
    padding: 40px;
    text-align: center;
}

.hero-sequence {
    height: 430vh;
}

.sticky-element {
    position: sticky;
    top: 0;
    height: 100vh;
    min-height: 1033px;
    overflow: hidden;
    display: grid;
    place-items: center;
}

.sequence-element {
    width: 1336px;
    height: 786px;
}

footer {
    background: #f5f5f7;
    font-weight: 700;
    font-size: 40px;
    height: 400px;
    display: grid;
    place-items: center;
}

javascript

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const heroSequence = document.querySelector('.hero-sequence')
const images = []
const frameCount = 87

const prepareImages = () => {
    for (var i = 0; i < frameCount; i++) {
      const image = new Image()
      image.src = `https://spharian.vercel.app/lab/apple-sequence-animation/images/${i}.jpg`
      images.push(image)

      if (i === 0) {
        images[i].onload = () => drawImage(0)
      }
    }
}

const drawImage = frameIndex => {
    ctx.drawImage(images[frameIndex], 0, 0)
}

prepareImages()

window.addEventListener('scroll', () => {
    const scrollTop = document.documentElement.scrollTop - heroSequence.offsetTop
    const maxScrollTop = heroSequence.scrollHeight - window.innerHeight
    const scrollFraction = scrollTop / maxScrollTop
    const frameIndex = Math.max(0, Math.min(frameCount - 1, Math.ceil(scrollFraction * frameCount)))

    images[frameIndex].onload = () => drawImage(frameIndex)
    requestAnimationFrame(() => drawImage(frameIndex))
})

解决方案

这个问题在于 querySelector 只会抓取第一个匹配的元素,因此每个动画只共用一个画布和一个滚动位置。我通过使用 querySelectorAll 并为每个动画独立初始化来修复它。

另外也写在一个文件里,因为这样省了我把所有东西分成不同文件的时间,不过如果你愿意,也可以手动把它们分开。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Apple Sequence Animation</title>



  <style>
    body {
      margin: 0;
      font-family: -apple-system, sans-serif;
      color: #1d1d1f;
    }

    h1 {
      font-size: 40px;
      margin: 0 0 10px;
    }

    p {
      font-size: 22px;
    }

    .hero {
      padding: 40px;
      text-align: center;
    }

    .hero-sequence {
      height: 430vh;
    }

    .sticky-element {
      position: sticky;
      top: 0;
      height: 100vh;
      overflow: hidden;
      display: grid;
      place-items: center;
      background: #000;
    }

    .sequence-element {
      width: 1336px;
      height: 786px;
      max-width: 100vw;
    }

    canvas {
      width: 100%;
      height: 100%;
    }

    footer {
      background: #f5f5f7;
      font-weight: 700;
      font-size: 40px;
      height: 400px;
      display: grid;
      place-items: center;
    }
  </style>
</head>
<body>

  <div class="hero">
    <h1>Apple Sequence Animation</h1>
    <p>Scroll down to admire the magic.</p>
  </div>

  <!-- Animation A -->
  <div class="hero-sequence">
    <div class="sticky-element">
      <div class="sequence-element">
        <canvas width="1336" height="786"></canvas>
      </div>
    </div>
  </div>

  <!-- Animation B -->
  <div class="hero-sequence">
    <div class="sticky-element">
      <div class="sequence-element">
        <canvas width="1336" height="786"></canvas>
      </div>
    </div>
  </div>

  <!-- Animation C -->
  <div class="hero-sequence">
    <div class="sticky-element">
      <div class="sequence-element">
        <canvas width="1336" height="786"></canvas>
      </div>
    </div>
  </div>

  <footer>You are awesome!</footer>

  <script>
    // Source - https://stackoverflow.com/q/79931688
    // Posted by MaanBek Yi
    // Retrieved 2026-04-25, License - CC BY-SA 4.0

    const frameCount = 87

    function initSequence(heroSequence, imageUrlFn) {
      const canvas = heroSequence.querySelector('canvas')
      const ctx = canvas.getContext('2d')
      const images = []

      const drawImage = frameIndex => {
        const img = images[frameIndex]
        if (img && img.complete && img.naturalWidth > 0) {
          ctx.drawImage(img, 0, 0)
        }
      }

      for (let i = 0; i < frameCount; i++) {
        const image = new Image()
        image.src = imageUrlFn(i)
        images.push(image)
        if (i === 0) {
          image.onload = () => drawImage(0)
        }
      }

      return function onScroll() {
        const scrollTop = document.documentElement.scrollTop - heroSequence.offsetTop
        const maxScrollTop = heroSequence.scrollHeight - window.innerHeight
        const scrollFraction = scrollTop / maxScrollTop
        const frameIndex = Math.max(0, Math.min(frameCount - 1, Math.ceil(scrollFraction * frameCount)))

        const img = images[frameIndex]
        if (img.complete) {
          requestAnimationFrame(() => drawImage(frameIndex))
        } else {
          img.onload = () => drawImage(frameIndex)
        }
      }
    }

    const sequences = document.querySelectorAll('.hero-sequence')

    // Replace each URL function with the actual image set for A, B, C.
    const handlers = [
      initSequence(sequences[0], i => `https://spharian.vercel.app/lab/apple-sequence-animation/images/${i}.jpg`),
      initSequence(sequences[1], i => `https://spharian.vercel.app/lab/apple-sequence-animation/images/${i}.jpg`),
      initSequence(sequences[2], i => `https://spharian.vercel.app/lab/apple-sequence-animation/images/${i}.jpg`),
    ]

    window.addEventListener('scroll', () => {
      handlers.forEach(handler => handler())
    })
  </script>
</body>
</html>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章