前端动画跳针优化

动画跳针的问题通常是因为 JavaScript 主线程被阻塞,无法及时处理动画帧。React Fiber 架构让我们有更多的精细控制渲染过程,有助于优化动画。以下是三种优化方法:

  1. 使用 requestAnimationFrame:

    window.requestAnimationFrame(callback) 方法告诉浏览器你希望执行一个动画,让浏览器在下一次重绘之前调用指定的回调函数更新动画。

class AnimationComponent extends React.Component {
    animationFrameId = null;

    performAnimation = () => {
        // 动画逻辑
        // ...

        // 请求浏览器在下一次重绘之前调用 performAnimation 更新动画
        this.animationFrameId = window.requestAnimationFrame(this.performAnimation);
    }

    componentDidMount() {
        this.performAnimation();
    }

    componentWillUnmount() {
        window.cancelAnimationFrame(this.animationFrameId);
    }

    render() {
        // render 组件内容
    }
}

在这个例子中,我们用 requestAnimationFrame 来调度动画的每一帧,从而保证动画在每一次浏览器重绘前都能得到更新。

componentWillUnmount 中调用 window.cancelAnimationFrame 是为了防止组件卸载后动画继续运行导致的内存泄漏。

  1. 把长时间运行的任务移到 Web Workers 中处理:

    Web Workers 让我们可以在后台线程中运行任务,不阻塞主线程。这对于长时间运行的任务特别有用,可以防止这些任务阻塞主线程,影响到动画的流畅度。

const worker = new Worker('/my-task-worker.js') // 导入 web worker

class AnimationComponent extends React.Component {
  state = {
    myTaskResult: null,
  }
  
  handleWorkerMessage = (event) => {
    // 从worker获得计算结果
    this.setState({myTaskResult: event.data});
  }

  componentDidMount() {
    worker.addEventListener('message', this.handleWorkerMessage);
    
    // 向 worker 发送数据,启动耗时任务
    worker.postMessage('start');
  }

  componentWillUnmount() {
    worker.removeEventListener('message', this.handleWorkerMessage);
    worker.terminate(); // 终止 worker
  }

  render() {
    // 使用从 worker 中获得的结果渲染组件
  }
}

3.结合 react fiber 进行处理 利用Web Workers和React Concurrent Mode来优化长时间运行操作
首先启用 React Concurrent Mode,需要使用 React 的实验性版本并在你的主组件中使用 createRoot API 来初始化你的 React 树。

以下是如何进行的示例代码:

import ReactDOM from 'react-dom'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

使用 ReactDOM.createRoot 替代了 ReactDOM.render 来创建你的应用。

这样,你就启用了 Concurrent Mode,此时,React 可以开始在后台预加载和预渲染组件,而不会影响到当前的用户界面。

然后你就可以在你的应用中使用 Concurrent Mode 的特性了,比如 Suspense,useTransition,useDeferredValue 等。

需要注意的是,Concurrent Mode 仍然是 React 的实验性特性,可能会有一些未知的副作用和问题,所以在正式生产环境中使用前,需要进行充分的测试。

示例代码

React Concurrent Mode。

import React, { useRef, useState, useEffect, Suspense } from "react";
import { unstable_createResource } from "react-cache";
import Workforce from "workforce";

// 创建一个Web Worker
const worker = new Workforce('./myComputeWorker.js');

// 创建一个React Cache Resource
const myComputeResource = unstable_createResource((input) =>
  worker.run('myCompute', input)  // 在 worker 上运行 myCompute 函数
);

const ListItem = () => {
  const ref = useRef();
  const [inView, setInView] = useState(false);
  const [data, setData] = useState(0);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => setInView(entry.isIntersecting),
      { rootMargin: "100px" }
    );
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    if (inView) {
      setData(Date.now());  // 更新数据,触发重新计算
    }
  }, [inView]);

  return (
    <div ref={ref}>
      <Suspense fallback={<div>Loading...</div>)}>  // 在计算完成前显示 fallback
        <Animate data={data} />
      </Suspense>
    </div>
  );
};

const Animate = ({ data }) => {
  const result = myComputeResource.read(data);  // 读取计算结果
  return <div>{result}</div>;
};

export default ListItem;

在myComputeWorker.js文件中:

//myComputeWorker.js
self.addEventListener('message', ({ data }) => {
  let sum = 0;
  for (let i = 0; i < Math.pow(data, 7); i++) {
    sum += i;
  }
  self.postMessage(sum);
});

在上面的代码中,我们首先创建了一个Web Worker来进行长时间的计算。然后,我们创建了一个React Cache Resource,并指定在Web Worker上运行myCompute函数。

之后,我们在ListItem组件中设置了一个交互观察者(IntersectionObserver)来检查我们的组件何时进入视图。当组件进入视图时,我们更新数据,该操作将触发Web Worker上的计算,并将结果存储在React Cache Resource中。

最后,我们将数据传递给Animate组件,该组件从React Cache Resource中读取计算结果,并在完成计算之前显示加载状态。

第一种是requestAnimationFrame方法,用于确保每一帧的动画都能在浏览器重绘前得到更新,帮助浏览器更有效地控制动画的流畅性;第二种是使用Web Workers把长时间运行的任务移到后台线程中,从而防止它们阻塞主线程,影响动画的流畅度;第三种是使用Web Workers和React Concurrent Mode来优化长时间运行操作的例子。在不阻塞主线程的同时保证了良好的用户体验。

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小纯洁w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值