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

实现类似参考网站的平滑滚动追赶动画效果(JavaScript)

实现类似参考网站的平滑滚动追赶动画效果(JavaScript)

嘿,我完全懂你想要的那种滚动质感——就是页面滚动起来像丝滑的流体,元素动画会慢悠悠“追赶”你的滚动节奏,而不是硬邦邦地瞬间到位对吧?你提到的scroll-behavior: smooth确实只能搞定锚点跳转的平滑,完全做不出这种带物理缓动的“追赶”效果。结合你贴的代码,我来帮你改造出这个效果~

核心原理拆解

这种“追赶式”丝滑滚动,本质是模拟物理惯性的缓动动画:我们不直接用浏览器原生的即时滚动,而是自定义两个变量:

  • targetScrollY:用户想要滚动到的目标位置(由滚轮/触摸事件触发更新)
  • currentScrollY:页面当前的实际滚动位置(通过requestAnimationFrame逐帧向targetScrollY靠近,中间加入缓动系数)

通过这种“追近”的逻辑,就会产生元素慢慢跟上滚动节奏的丝滑感,同时你的图片切割动画也会跟着这个缓动后的滚动位置变化,不再生硬。

代码改造步骤

我会基于你现有的代码结构,一步步调整出效果:

1. 先调整CSS:禁用原生滚动

首先要阻止浏览器的原生滚动,避免和我们自定义的滚动逻辑冲突:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Unica One', sans-serif;
  color: #111;
  z-index: 1;
}

body {
  overflow: hidden; /* 禁用原生滚动,完全由JS控制 */
  height: 100vh;
}

header {
  width: 100%;
  height: 100vh;
  background-image: url("Images/Banner(1).png");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  flex-wrap: wrap;
  position: absolute;
}

.parallax {
  min-height: 1000px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  position: relative;
}

.parallax.subTopic::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
}

.parallax h1 {
  position: absolute;
  color: white;
}

/* 把主内容容器设为相对定位,我们通过transform移动它来模拟滚动 */
.pagecontainer {
  position: relative;
  width: 100%;
  height: 100vh;
  overflow: visible;
}

2. 重写JavaScript:核心滚动逻辑+适配你的现有功能

这部分是重点,我们要把原来依赖原生scrollY的逻辑,改成基于自定义缓动滚动位置的逻辑:

// 核心滚动变量:目标位置、当前位置、缓动系数(越小追赶越慢,0.1-0.2最自然)
let targetScrollY = 0;
let currentScrollY = 0;
const friction = 0.15;

// 页面元素引用
const historyContent = document.getElementsByClassName("historyLayout");
const parallaxContent = document.querySelectorAll('section .parallax');
const pageContainer = document.querySelector('.pagecontainer');
const navLinks = document.querySelectorAll('.nav-link');

// 计算页面总高度(用于限制滚动边界,避免滚出页面)
function getTotalPageHeight() {
  return Math.max(
    document.body.scrollHeight,
    document.body.offsetHeight,
    document.documentElement.clientHeight,
    document.documentElement.scrollHeight,
    document.documentElement.offsetHeight
  );
}

// 监听滚轮事件:只更新目标滚动位置,不直接滚动
window.addEventListener('wheel', (e) => {
  e.preventDefault(); // 阻止原生滚动
  const delta = e.deltaY;
  // 根据滚轮方向调整目标位置(向下滚deltaY为正,所以加delta)
  targetScrollY += delta;
  // 限制滚动边界:不能小于0,不能超过页面总高度-视口高度
  const maxScroll = getTotalPageHeight() - window.innerHeight;
  targetScrollY = Math.max(0, Math.min(targetScrollY, maxScroll));
}, { passive: false });

// 适配锚点链接:点击导航时更新目标滚动位置
navLinks.forEach(link => {
  link.addEventListener('click', (e) => {
    e.preventDefault();
    const targetId = link.getAttribute('href');
    const targetElement = document.querySelector(targetId);
    if (targetElement) {
      targetScrollY = targetElement.offsetTop;
      // 同样限制边界
      const maxScroll = getTotalPageHeight() - window.innerHeight;
      targetScrollY = Math.max(0, Math.min(targetScrollY, maxScroll));
    }
  });
});

// 你的图片切割函数:修改为依赖自定义的currentScrollY
let cutcounter = 0;
function cuttingImages() {
  let section1 = historyContent[cutcounter];
  if (!section1) return;
  let parallax2 = parallaxContent[cutcounter + 1];
  let section2 = historyContent[cutcounter + 1];
  if (!parallax2 || !section2) return;

  // 基于自定义滚动位置计算元素相对位置
  const image1Top = section1.offsetTop - currentScrollY;
  const section2Top = parallax2.offsetTop - currentScrollY;
  let progress = (image1Top - section2Top) / -966;
  progress = Math.max(0, Math.min(1, progress));
  
  section1.style.clipPath = `inset(0 0 ${(100 - progress * 100)}% 0)`;
  section2.style.clipPath = `inset(${progress * 100}% 0 0 0)`;

  // 原有的计数器逻辑保留
  if (progress === 0 && cutcounter === 0) {
    cutcounter++;
  }
  if (progress === 1 && cutcounter > 0) {
    cutcounter--;
  }
}

// 你的历史布局固定定位逻辑:适配currentScrollY
function updateHistoryLayoutPosition() {
  if (historyContent.length === 0) return;
  const prehistoricContainer = historyContent[0];
  const containerRect = prehistoricContainer.getBoundingClientRect();
  const containerMiddle = (containerRect.top - containerRect.bottom) / 2;
  const containerHalf = containerRect.top - containerMiddle;

  if (containerHalf <= window.innerHeight / 2) {
    for (const content of historyContent) {
      content.style.position = "fixed";
      content.style.top = "0";
      content.style.left = "0";
      content.style.right = "0";
      content.style.bottom = "0";
      if ((currentScrollY - containerHalf - 1000) <= window.innerHeight / 2) {
        content.style.position = "absolute";
      }
    }
  }
}

// 逐帧更新的核心动画循环
function animateScroll() {
  // 让当前位置向目标位置缓动靠近
  currentScrollY += (targetScrollY - currentScrollY) * friction;
  // 当差距极小时直接对齐,避免无限接近
  if (Math.abs(targetScrollY - currentScrollY) < 0.1) {
    currentScrollY = targetScrollY;
  }

  // 通过transform移动页面容器(GPU加速,更丝滑)
  pageContainer.style.transform = `translateY(${-currentScrollY}px)`;

  // 同步更新你的图片切割和布局定位
  updateHistoryLayoutPosition();
  cuttingImages();

  // 持续下一帧动画
  requestAnimationFrame(animateScroll);
}

// 启动动画循环
requestAnimationFrame(animateScroll);

关键细节说明

  1. 为什么用transform而不是scrollTop?
    transform的变化会触发GPU硬件加速,比直接修改scrollTop更丝滑,减少页面卡顿,尤其是在有大量动画元素的时候。

  2. 缓动系数friction怎么调?

    • 0.1:滚动追赶速度慢,粘性强,适合偏文艺的慢节奏页面
    • 0.15:平衡型,接近你参考网站的感觉
    • 0.2:追赶速度快,丝滑但粘性弱,适合偏活泼的页面
  3. 触摸设备适配(可选)
    如果要支持移动端触摸滚动,只需要监听touchstart、touchmove、touchend事件,记录触摸起始位置和移动距离,然后更新targetScrollY,逻辑和滚轮事件类似,你可以自己扩展。

最后

这样修改之后,你应该就能得到那种丝滑的滚动追赶效果了,你的图片切割动画也会跟着滚动节奏慢慢变化,不再生硬。你可以根据自己的页面风格,调整缓动系数、边界计算这些参数,直到达到你想要的质感~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:17:59