滚动驱动的图片序列动画只会播放第一个动画
我想在同一页上放置“多条基于滚动驱动的图片序列动画”。我尝试了很多次,但始终找不到解决办法。
无论我怎么尝试,只有最上面的一个在工作,其他的都是空白。你能帮我解决吗?
我的页面结构是这样的:
=========== 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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。