MST

星途 面试题库

面试题:JavaScript 实现复杂文档滚动动画的性能优化

假设在一个包含大量元素的网页中,需要实现多个不同元素在文档滚动到特定位置时触发不同的动画效果,比如有的元素是从底部滑入,有的是旋转进入等。在实现过程中如何进行性能优化,避免滚动卡顿?请详细阐述优化思路,并给出关键代码示例。
27.3万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

优化思路

  1. 节流与防抖
    • 原理:在滚动事件触发时,频繁执行动画触发逻辑会导致性能问题。节流(throttle)是指在一定时间间隔内,无论滚动事件触发多么频繁,只执行一次动画触发逻辑。防抖(debounce)是指在滚动停止一定时间后,才执行动画触发逻辑。这样可以减少不必要的计算和动画触发次数。
    • 使用场景:节流适用于需要按一定频率触发动画的场景,防抖适用于在滚动停止后执行动画的场景。
  2. 使用 requestAnimationFrame
    • 原理requestAnimationFrame 是浏览器提供的一个方法,它会在浏览器下一次重绘之前调用传入的回调函数。与 setTimeoutsetInterval 相比,requestAnimationFrame 能够根据浏览器的刷新频率来执行动画,从而实现更流畅的动画效果。因为它是在浏览器重绘之前执行,所以可以更好地利用浏览器的渲染机制,减少卡顿。
    • 使用场景:在执行动画效果时,使用 requestAnimationFrame 来触发动画,而不是使用 setTimeoutsetInterval
  3. 提前计算元素位置
    • 原理:在页面加载时,提前计算好每个需要触发动画的元素在视口内的特定位置(比如元素顶部距离文档顶部的距离等)。这样在滚动事件触发时,直接读取预先计算好的值进行比较,而不需要每次都重新计算元素位置,减少计算量。
    • 使用场景:在页面初始化阶段,遍历所有需要触发动画的元素,计算并存储它们的相关位置信息。
  4. 硬件加速
    • 原理:通过 transformopacity 属性来实现动画,这两个属性在大多数浏览器中可以触发硬件加速,从而提高动画的渲染性能。因为硬件加速利用了 GPU 的处理能力,相比 CPU 处理图形计算更高效。
    • 使用场景:在定义动画效果时,尽量使用 transform(如 translateY 实现从底部滑入,rotate 实现旋转等)和 opacity 属性来实现动画,避免使用 lefttop 等会触发重排的属性。

关键代码示例

  1. 使用节流
    • JavaScript 代码
function throttle(func, delay) {
    let timer = null;
    return function() {
        if (!timer) {
            func.apply(this, arguments);
            timer = setTimeout(() => {
                timer = null;
            }, delay);
        }
    };
}

// 假设我们有一个处理动画触发的函数
function handleAnimation() {
    // 这里写动画触发逻辑,比如检查元素是否进入视口并触发动画
}

// 为滚动事件添加节流处理
window.addEventListener('scroll', throttle(handleAnimation, 200));
  1. 使用 requestAnimationFrame 实现动画
    • HTML 代码
<div id="element1" class="animate-on-scroll">从底部滑入元素</div>
  • CSS 代码
.animate-on-scroll {
    opacity: 0;
    transform: translateY(50px);
    transition: opacity 0.5s ease, transform 0.5s ease;
}

.animate-on-scroll.visible {
    opacity: 1;
    transform: translateY(0);
}
  • JavaScript 代码
function checkElementInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

function animateElements() {
    const elements = document.querySelectorAll('.animate-on-scroll');
    elements.forEach((element) => {
        if (checkElementInViewport(element) &&!element.classList.contains('visible')) {
            element.classList.add('visible');
        }
    });
}

function loop() {
    animateElements();
    requestAnimationFrame(loop);
}

requestAnimationFrame(loop);
  1. 提前计算元素位置
    • JavaScript 代码
// 提前计算元素位置
const elements = document.querySelectorAll('.animate-on-scroll');
const elementPositions = [];
elements.forEach((element) => {
    const rect = element.getBoundingClientRect();
    elementPositions.push(rect.top + window.pageYOffset);
});

function handleAnimation() {
    const scrollTop = window.pageYOffset;
    elements.forEach((element, index) => {
        if (scrollTop >= elementPositions[index] &&!element.classList.contains('visible')) {
            element.classList.add('visible');
        }
    });
}

window.addEventListener('scroll', throttle(handleAnimation, 200));