实现类Apple滚动动画后,如何让Canvas完成动画后滚出屏幕?
实现Apple风格滚动动画并让Canvas在动画结束后滚出屏幕
问题分析
当前通过fixed定位的Canvas实现了滚动帧动画,但fixed定位会让Canvas始终固定在视口,无法随页面滚动移出;切换为absolute/relative定位时,Canvas会直接上滚,导致帧动画的滚动关联逻辑失效——这是因为非fixed定位元素会随页面滚动,原有的scrollTop与帧索引的映射逻辑无法正常触发逐帧切换。
解决方案思路
我们可以根据滚动进度动态控制Canvas的位置表现:
- 动画未完成时,保持fixed定位确保帧动画正常触发;
- 动画完成后,通过修改定位方式或transform值,让Canvas随页面滚动移出视口。
方案一:动态切换定位方式(融入页面流)
修改CSS
添加过渡效果让定位切换更平滑,同时为absolute定位提供参考父级:
html { height: 100vh; } body { height: 500vh; background: #fff; position: relative; /* 为absolute定位的Canvas提供定位参考 */ } canvas { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 200vw; max-height: 200vh; transition: top 0.3s ease; /* 保证位置变化平滑 */ } .canvas-absolute { position: absolute; top: 100vh; /* 动画完成后初始位置在视口底部 */ transform: translate(-50%, 0); }
修改JS
在滚动事件中判断是否到达最后一帧,切换Canvas的定位类:
/*eslint-env es6*/ const html = document.documentElement; const canvas = document.getElementById("hero-lightpass"); const context = canvas.getContext("2d"); const frameCount = 63; const currentFrame = index => ( `https://antiviral-semaphore.000webhostapp.com/TestExport/JPG/Test${index.toString().padStart(4, '0')}.jpg` ) const preloadImages = () => { for (let i = 1; i < frameCount; i++) { const img = new Image(); img.src = currentFrame(i); } }; const img = new Image() img.src = currentFrame(1); canvas.width=2230; canvas.height=559; img.onload=function(){ context.drawImage(img, 0, 0); } const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0); } window.addEventListener('scroll', () => { const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.ceil(scrollFraction * frameCount) ); // 判断是否完成动画 if (frameIndex === frameCount - 1) { canvas.classList.add('canvas-absolute'); // 计算动画完成后的滚动偏移,让Canvas随页面上移 const excessScroll = scrollTop - (maxScrollTop * (frameCount - 1) / frameCount); canvas.style.top = `${100 - (excessScroll / window.innerHeight) * 100}vh`; } else { canvas.classList.remove('canvas-absolute'); } requestAnimationFrame(() => updateImage(frameIndex + 1)) }); preloadImages()
方案二:用transform模拟滚动移出(保持fixed定位)
无需切换定位,直接通过修改transform: translateY()的值,让Canvas在动画完成后随滚动上移:
修改CSS
html { height: 100vh; } body { height: 500vh; background: #fff; } canvas { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 200vw; max-height: 200vh; transition: transform 0.1s ease; /* 保证偏移平滑 */ }
修改JS
/*eslint-env es6*/ const html = document.documentElement; const canvas = document.getElementById("hero-lightpass"); const context = canvas.getContext("2d"); const frameCount = 63; const currentFrame = index => ( `https://antiviral-semaphore.000webhostapp.com/TestExport/JPG/Test${index.toString().padStart(4, '0')}.jpg` ) const preloadImages = () => { for (let i = 1; i < frameCount; i++) { const img = new Image(); img.src = currentFrame(i); } }; const img = new Image() img.src = currentFrame(1); canvas.width=2230; canvas.height=559; img.onload=function(){ context.drawImage(img, 0, 0); } const updateImage = index => { img.src = currentFrame(index); context.drawImage(img, 0, 0); } window.addEventListener('scroll', () => { const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.ceil(scrollFraction * frameCount) ); let transformVal = 'translate(-50%, -50%)'; if (frameIndex === frameCount - 1) { // 计算动画完成后的滚动比例,让Canvas上移出视口 const excessFraction = (scrollFraction - (frameCount - 1)/frameCount) / (1 - (frameCount - 1)/frameCount); const translateY = -50 - (excessFraction * 150); // 150vh为完全移出的距离,可按需调整 transformVal = `translate(-50%, ${translateY}%)`; } canvas.style.transform = transformVal; requestAnimationFrame(() => updateImage(frameIndex + 1)) }); preloadImages()
关键说明
- 方案一通过切换定位让Canvas融入页面流,滚动逻辑更贴合原生页面行为;
- 方案二更简洁,通过transform模拟滚动,避免定位切换可能带来的布局抖动;
- 可根据需求调整过渡时长、移出速度等参数,匹配Apple风格的平滑动画质感。
内容的提问来源于stack exchange,提问作者NeeeeeT
相关产品推荐
相关产品推荐

