现在,用户对于前端页面的要求已经不能满足于实现功能,更要有颜值,有趣味。除了整体 UI 的美观,在合适的地方添加合适的动画效果往往比静态页面更具有表现力,达到更自然的效果。比如,一个简单的 loading 动画或者页面切换效果不仅能缓解用户的等待情绪,甚至通过使用品牌 logo 等形式,默默达到品牌宣传的效果。
React 作为最近几年比较流行的前端开发框架,提出了虚拟 DOM 概念,所有 DOM 的变化都先发生在虚拟 DOM 上,通过 DOM diff 来分析网页的实际变化,然后反映在真实 DOM 上,从而极大地提升网页性能。然而,在动画实现方面,React 作为框架并不会直接给组件提供动画效果,需要开发者自行实现,而传统 web 动画大多数都通过直接操作实际 DOM 元素来实现,这在 React 中显然是不被提倡的。那么,在 React 中动画都是如何实现的呢?
所有动画的本质都是连续修改 DOM 元素的一个或者多个属性,使其产生连贯的变化效果,从而形成动画。在 React 中实现动画本质上与传统 web 动画一样,仍然是两种方式: 通过 css3 动画实现和通过 js 修改元素属性。只不过在具体实现时,要更为符合 React 的框架特性,可以概括为几类:
- 基于定时器或 requestAnimationFrame(RAF) 的间隔动画;
- 基于 css3 的简单动画;
- React 动画插件 CssTransitionGroup;
- 结合 hook 实现复杂动画;
- 其他第三方动画库。
一、基于定时器或 RAF 的间隔动画
最早,动画的实现都是依靠定时器 setInterval
, setTimeout
或者 requestAnimationFrame
(RAF) 直接修改 DOM 元素的属性。不熟悉 React 特性的开发者可能会习惯性地通过 ref
或者 findDOMNode()
获取真实的 DOM 节点,直接修改其样式。然而,通过 ref
直接获取真实 DOM 并对其操作是是不被提倡使用,应当尽量避免这种操作。
因此,我们需要将定时器或者 RAF 等方法与 DOM 节点属性通过 state
联系起来。首先,需要提取出与变化样式相关的属性,替换为 state
,然后在合适的生命周期函数中添加定时器或者 requestAnimationFrame
不断修改 state
,触发组件更新,从而实现动画效果。
示例
以一个进度条为例,代码如下所示:
// 使用requestAnimationFrame改变state import React, { Component } from 'react'; export default class Progress extends Component { constructor(props) { super(props); this.state = { percent: 10 }; } increase = () => { const percent = this.state.percent; const targetPercent = percent >= 90 ? 100 : percent + 10; const speed = (targetPercent - percent) / 400; let start = null; const animate = timestamp => { if (!start) start = timestamp; const progress = timestamp - start; const currentProgress = Math.min(parseInt(speed * progress + percent, 10), targetPercent); this.setState({ percent: currentProgress }); if (currentProgress < targetPercent) { window.requestAnimationFrame(animate); } }; window.requestAnimationFrame(animate); } decrease = () => { const percent = this.state.percent; const targetPercent = percent < 10 ? 0 : percent - 10; const speed = (percent - targetPercent) / 400; let start = null; const animate = timestamp => { if (!start) start = timestamp; const progress = timestamp - start; const currentProgress = Math.max(parseInt(percent - speed * progress, 10), targetPercent); this.setState({ percent: currentProgress }); if (currentProgress > targetPercent) { window.requestAnimationFrame(animate); } }; window.requestAnimationFrame(animate); } render() { const { percent } = this.state; return ( <div> <div className="progress"> <div className="progress-wrapper" > <div className="progress-inner" style = {{width: `${percent}%`}} ></div> </div> <div className="progress-info" >{percent}%</div> </div> <div className="btns"> <button onClick={this.decrease}>-</button> <button onClick={this.increase}>+</button> </div> </div> ); } }
内容版权声明:除非注明,否则皆为本站原创文章。