---
URL: "/zh-CN/blog/2025-01/html_smooth_behavior.html"
LLMS_URL: "/zh-CN/blog/2025-01/html_smooth_behavior.md"
lastUpdated: true
commentabled: true
recommended: true
title: "在前端开发中实现平滑滚动动画的技术"
description: "在前端开发中实现平滑滚动动画的技术"
date:

pageClass: "blog-page-class"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: 在前端开发中实现平滑滚动动画的技术
description: 在前端开发中实现平滑滚动动画的技术
date: 2025-01-15 15:18:00
pageClass: blog-page-class
---

# 在前端开发中实现平滑滚动动画的技术 #

在现代Web开发中，用户体验是我们关注的核心之一。页面滚动是最基本的交互行为之一，而平滑滚动（smooth scrolling）则为用户提供了更为流畅的视觉效果。在本文中，我们将从前端开发的角度，分析如何使用JavaScript来实现平滑滚动动画。

## 滚动的基本原理 ##

在浏览器中，用户滚动页面时，通常通过鼠标滚轮、键盘按键或者触摸屏的滑动来触发滚动行为。浏览器本身会根据用户的操作自动调整页面的位置。通常，这种滚动是瞬时的，没有过渡效果。为了改善用户体验，我们可以使用平滑滚动技术，模拟出动画效果。

## 实现平滑滚动的基本方法 ##

在实现平滑滚动时，最常见的方法是使用 requestAnimationFrame 来更新滚动位置，并通过数学公式来平滑滚动的过渡。这里我们使用一个经典的缓动函数——easeInOutQuad，这种缓动方式可以让滚动的开始和结束都更加平滑。

### Math.easeInOutQuad 函数 ###

这是一个实现缓动效果的函数，通过指定的时间和距离，计算出滚动的过渡值：

```js
Math.easeInOutQuad = function(t, b, c, d) {
  t /= d / 2;
  if (t < 1) {
    return c / 2 * t * t + b;
  }
  t--;
  return -c / 2 * (t * (t - 2) - 1) + b;
}
```

该函数的参数解释：

- t：当前时间点
- b：初始位置
- c：变化的距离
- d：总持续时间

通过该函数，我们可以得到一个平滑的动画效果，即滚动不会直接跳跃到目标位置，而是会随着时间逐渐过渡。

### requestAnimationFrame：浏览器内置的动画优化方法 ###

requestAnimationFrame 是浏览器为动画提供的一个优化接口，它可以确保动画在屏幕刷新时进行更新，从而提高性能，避免卡顿现象。在滚动动画中，我们利用 requestAnimationFrame 来不断更新滚动位置。

```javascript
var requestAnimFrame = (function() {
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) };
})();
```

该函数的作用是判断浏览器是否支持 requestAnimationFrame，如果支持则使用它来进行动画更新，否则使用 setTimeout 来模拟。

## 实现平滑滚动功能 ##

根据上述的缓动函数和动画更新机制，我们可以编写一个 scrollTo 函数来实现页面的平滑滚动效果。

```javascript
/**
 * 平滑滚动到指定位置
 * @param {number} to 目标滚动位置
 * @param {number} duration 动画持续时间，默认为500ms
 * @param {Function} callback 动画完成后的回调函数
 */
export function scrollTo(to, duration, callback) {
  const start = position(); // 获取当前滚动位置
  const change = to - start; // 计算目标位置与当前滚动位置的差值
  const increment = 20; // 动画帧间隔时间（ms）
  let currentTime = 0; // 当前动画时间
  duration = (typeof (duration) === 'undefined') ? 500 : duration; // 默认持续时间500ms

  var animateScroll = function() {
    currentTime += increment; // 增加动画时间
    var val = Math.easeInOutQuad(currentTime, start, change, duration); // 计算当前滚动位置
    move(val); // 执行滚动
    if (currentTime < duration) {
      requestAnimFrame(animateScroll); // 如果动画还未结束，继续执行
    } else {
      if (callback && typeof (callback) === 'function') {
        callback(); // 动画结束后执行回调函数
      }
    }
  }

  animateScroll(); // 开始动画
}

/**
 * 获取当前滚动位置
 */
function position() {
  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop;
}

/**
 * 设置滚动位置
 * @param {number} amount 滚动的目标位置
 */
function move(amount) {
  document.documentElement.scrollTop = amount;
  document.body.parentNode.scrollTop = amount;
  document.body.scrollTop = amount;
}
```

## 如何使用 scrollTo 函数 ##

假设我们希望让页面滚动到某个特定的元素位置，并且滚动动画持续500ms。我们只需要调用 scrollTo 函数并传入目标位置和动画持续时间：

```javascript
// 目标滚动位置
const targetPosition = document.querySelector('#targetElement').offsetTop;

// 执行平滑滚动，动画持续时间为500ms
scrollTo(targetPosition, 500, function() {
  console.log('滚动动画完成！');
});
```

## 为什么选择 easeInOutQuad 函数？##

easeInOutQuad 是一种常用的二次方缓动函数。它的特点是：

- 在动画开始时比较慢，逐渐加速；
- 在动画结束时减速，确保动画的平滑过渡。

这种方式避免了线性过渡的生硬感，也减少了用户感知上的跳跃感，给用户提供了更自然的滚动体验。

## 完整代码 ##

```javascript
// 二次方缓动函数：用于实现平滑滚动
Math.easeInOutQuad = function(t, b, c, d) {
  t /= d / 2; // 将时间t标准化到 [0, 1] 范围
  if (t < 1) {
    // 如果t小于1，表示动画在开始时较慢
    return c / 2 * t * t + b; // 计算并返回当前位置
  }
  t--; // 当t大于等于1时，减去1，开始减速
  return -c / 2 * (t * (t - 2) - 1) + b; // 计算并返回减速后的当前位置
}

// 为了优化动画性能，使用浏览器的requestAnimationFrame
var requestAnimFrame = (function() {
  // 检查浏览器是否支持requestAnimationFrame，如果不支持则使用setTimeout模拟
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { 
    window.setTimeout(callback, 1000 / 60); // 每帧60毫秒执行一次动画
  };
})();

/**
 * 因为检测滚动元素非常复杂，所以直接通过调整文档的滚动位置来实现滚动
 * @param {number} amount 滚动的目标位置
 */
function move(amount) {
  // 设置页面各个滚动区域的滚动位置
  document.documentElement.scrollTop = amount;  // 针对HTML元素
  document.body.parentNode.scrollTop = amount;  // 针对body的父节点（某些浏览器需要）
  document.body.scrollTop = amount;  // 针对body元素本身
}

/**
 * 获取当前页面的滚动位置
 */
function position() {
  // 返回页面当前的滚动位置，优先从document.documentElement中获取
  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop;
}

/**
 * 平滑滚动到指定位置
 * @param {number} to 目标滚动位置
 * @param {number} duration 动画持续时间，默认为500ms
 * @param {Function} callback 动画完成后的回调函数
 */
export function scrollTo(to, duration, callback) {
  const start = position(); // 获取当前滚动位置
  const change = to - start; // 计算目标位置与当前滚动位置的差值
  const increment = 20; // 每帧的时间间隔，单位：ms
  let currentTime = 0; // 当前动画时间
  duration = (typeof (duration) === 'undefined') ? 500 : duration; // 默认持续时间500ms

  // 定义动画执行的函数
  var animateScroll = function() {
    currentTime += increment; // 增加当前时间
    var val = Math.easeInOutQuad(currentTime, start, change, duration); // 通过缓动函数计算当前滚动位置
    move(val); // 移动滚动条到计算出来的位置
    if (currentTime < duration) {
      // 如果动画还未结束，继续执行
      requestAnimFrame(animateScroll);
    } else {
      // 动画结束后，执行回调函数（如果有的话）
      if (callback && typeof (callback) === 'function') {
        callback();
      }
    }
  };

  animateScroll(); // 开始动画
}
```

## 总结 ##

平滑滚动是提升前端用户体验的一个简单但有效的手段。通过 requestAnimationFrame 和缓动函数，我们可以轻松实现流畅的滚动效果，避免页面跳跃式的滚动行为。在实现时，合理使用动画时间和缓动函数，可以让动画既流畅又不会影响性能。

对于开发者来说，掌握这些技术不仅能增强产品的互动感，也能让用户感受到更加细腻的操作体验。
