You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现类Apple滚动动画后,如何让Canvas完成动画后滚出屏幕?

实现Apple风格滚动动画并让Canvas在动画结束后滚出屏幕

问题分析

当前通过fixed定位的Canvas实现了滚动帧动画,但fixed定位会让Canvas始终固定在视口,无法随页面滚动移出;切换为absolute/relative定位时,Canvas会直接上滚,导致帧动画的滚动关联逻辑失效——这是因为非fixed定位元素会随页面滚动,原有的scrollTop与帧索引的映射逻辑无法正常触发逐帧切换。

解决方案思路

我们可以根据滚动进度动态控制Canvas的位置表现:

  1. 动画未完成时,保持fixed定位确保帧动画正常触发;
  2. 动画完成后,通过修改定位方式或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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 02:46:22