有接触过前端的小伙伴都知道,在不同的前端框架中使用框架的方式都大同小异。那么今天我们就来说说前端框架“React如何实现一个Transition过渡动画组件?”这个问题吧!
一、基本实现
我们在实现基础的过度动画组件,需要通过切换CSS样式实现简单的动画效果。
首先我们安装 classnames 插件:
npm install classnames --save-dev
而且 classnaems 是一个简单的JavaScript实用程序,用于有条件地将 classnames
连接一起。那么我们将 component
目录新建一个 Transition
文件夹,并在文件夹中新建一个 Transition.jsx
文件,代码如下:
import React from 'react'
import classnames from 'classnames'
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
render() {
const { children } = this.props
const transition = (
<div
className={
classnames({
transition: true
})
}
style={
{
position: 'relative',
overflow: 'hidden'
}
}
>
<div
className={
classnames({
'transition-wrapper': true
})
}
>
{ children }
</div>
</div>
)
return transition
}
}
export default Transition
在文件中我们通过使用小驼峰来定义属性定义名称;而且我们在文件中使用 JSX 语法,我们来看看如下案例代码:
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
这个代码等价如下这串代码:
const element = <h1>Hello, Josh Perez</h1>;
当然在使用 JSX 语法的时候我们还是要注意的,因为在 JSX 语法中会更接近 JavaScript
而不是 html
,所以在 React DOM 中我们使用 小驼峰来进行命名,而不使用 html
属性名称约定。
除此之外在 React 中的 props.children
包含组件所有的子节点,即组件开始标签和结束标签之间的内容,如下案例所示:
<Button>默认按钮</Button>
在 Button
组件中获取 props.children
,就可以得到字符串“默认按钮”。
那么接下来我们在 Transition 文件夹下新建一个 index.js
,导出 Transition
组件,代码如下所示:
import Transition from './Transition.jsx'
export { Transition }
export default Transition
完成之后,在 Transition.jsx
文件中为组件添加 props
检查并设置 action
默认值,代码如下所示:
import PropTypes from 'prop-types'
const propTypes = {
/** 执行动画 */
action: PropTypes.bool,
/** 切换的css动画的class名称 */
toggleClass: PropTypes.string
}
const defaultProps = {
action: false
}
这时候我们使用 prop-ty.pes实现运行时类型检查。但是需要注意的是 prop-ty.pes是一个运行时;类型检查工具,我们来看看完整的 Transition 组件代码如下所示:
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const propTypes = {
/** 执行动画 */
action: PropTypes.bool,
/** 切换的css动画的class名称 */
toggleClass: PropTypes.string
}
const defaultProps = {
action: false
}
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
static propTypes = propTypes
static defaultProps = defaultProps
render() {
const {
className,
action,
toggleClass,
children
} = this.props
const transition = (
<div
className={
classnames({
transition: true
})
}
style={
{
position: 'relative',
overflow: 'hidden'
}
}
>
<div
className={
classnames({
'transition-wrapper': true,
[className]: className,
[toggleClass]: action && toggleClass
})
}
>
{ children }
</div>
</div>
)
return transition
}
}
export default Transition
CSS代码如下所示:
.fade {
transition: opacity 0.15s linear;
}
.fade:not(.show) {
opacity: 0;
}
JS代码如下所示:
import React from 'react';
import Transition from './Transition';
class Anime extends React.Component {
constructor (props) {
super(props)
this.state = {
action: true
}
}
render () {
const btnText = this.state.action ? '淡出' : '淡入'
return (
<div>
<Transition
className="fade"
toggleClass="show"
action={ this.state.action }
>
淡入淡出
</Transition>
<button
style={{ marginTop: '20px' }}
onClick={() => this.setState({ action: !this.state.action })}
>
{ btnText }
</button>
</div>
)
}
}
这样子我们就只需要在需要使用动画的地方来进行使用 Anime
组件就可以了。
二、实现Animate.css兼容
我们都知道 Animate.css
是一款强大的预设 CSS3 动画库。由于在进入动画和离开动画通常在使用这两个效果相反的 class
样式,所以我们需要给我们的 Transition
组件添加 enterClass
和 leaveClass
两个属性来实现动画的切换,代码如下所示:
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const propTypes = {
/** 执行动画 */
action: PropTypes.bool,
/** 切换的css动画的class名称 */
toggleClass: PropTypes.string,
/** 进入动画的class名称,存在 toggleClass 时无效 */
enterClass: PropTypes.string,
/** 离开动画的class名称,存在 toggleClass 时无效 */
leaveClass: PropTypes.string
}
const defaultProps = {
action: false
}
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
static propTypes = propTypes
static defaultProps = defaultProps
render() {
const {
className,
action,
toggleClass,
enterClass,
leaveClass,
children
} = this.props
return (
<div
className={
classnames({
transition: true
})
}
style={
{
position: 'relative',
overflow: 'hidden'
}
}
>
<div
className={
classnames({
'transition-wrapper': true,
[className]: className,
[toggleClass]: action && toggleClass,
[enterClass]: !toggleClass && action && enterClass,
[leaveClass]: !toggleClass && !action && leaveClass,
})
}
>
{ children }
</div>
</div>
)
}
}
export default Transition
当然我们还是要注意一下,由于 toggleClass
适用于那些进入动画与离开动画切换相同 class
样式的情况,而且 enterClass
和 leaveClass
使用那些进入动画和离开动画切换不同的 class
样式的情况,所以,他们和 toggleClass
不能共存。
那么我们接下来就尝试下加入Animate.css
后的 Transition
组件,代码如下所示:
import React from 'react';
import 'animate.css';
class Anime extends React.Component {
constructor (props) {
super(props)
this.state = {
action: true
}
}
render () {
return (
<div>
<Transition
className="animated"
enterClass="bounceInLeft"
leaveClass="bounceOutLeft"
action={ this.state.action }
>
弹入弹出
</Transition>
<utton
style={{ marginTop: '20px' }}
onClick={() => this.setState({ action: !this.state.action })}
>
{ this.state.action ? '弹出' : '弹入' }
</utton>
</div>
)
}
}
三、功能扩展
通过上面的方法实现之后我们知道 Transition
组件是可以适用在很多的场景中的,但是功能不是很丰富,所以就需要扩展 Transition
的接口。首先我们来添加 props
属性,并设置默认值,代码如下所示:
const propTypes = {
...,
/** 动画延迟执行时间 */
delay: PropTypes.string,
/** 动画执行时间长度 */
duration: PropTypes.string,
/** 动画执行次数,只在执行 CSS3 动画时有效 */
count: PropTypes.number,
/** 动画缓动函数 */
easing: PropTypes.oneOf([
'linear',
'ease',
'ease-in',
'ease-out',
'ease-in-out'
]),
/** 是否强制轮流反向播放动画,count 为 1 时无效 */
reverse: PropTypes.bool
}
const defaultProps = {
count: 1,
reverse: false
}
根据 props 设置样式,代码如下所示:
// 动画样式
const styleText = (() => {
let style = {}
// 设置延迟时长
if (delay) {
style.transitionDelay = delay
style.animationDelay = delay
}
// 设置播放时长
if (duration) {
style.transitionDuration = duration
style.animationDuration = duration
}
// 设置播放次数
if (count) {
style.animationIterationCount = count
}
// 设置缓动函数
if (easing) {
style.transitionTimingFunction = easing
style.animationTimingFunction = easing
}
// 设置动画方向
if (reverse) {
style.animationDirection = 'alternate'
}
return style
})()
完整代码如下所示:
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const propTypes = {
/** 执行动画 */
action: PropTypes.bool,
/** 切换的css动画的class名称 */
toggleClass: PropTypes.string,
/** 进入动画的class名称,存在 toggleClass 时无效 */
enterClass: PropTypes.string,
/** 离开动画的class名称,存在 toggleClass 时无效 */
leaveClass: PropTypes.string,
/** 动画延迟执行时间 */
delay: PropTypes.string,
/** 动画执行时间长度 */
duration: PropTypes.string,
/** 动画执行次数,只在执行 CSS3 动画时有效 */
count: PropTypes.number,
/** 动画缓动函数 */
easing: PropTypes.oneOf([
'linear',
'ease',
'ease-in',
'ease-out',
'ease-in-out'
]),
/** 是否强制轮流反向播放动画,count 为 1 时无效 */
reverse: PropTypes.bool
}
const defaultProps = {
action: false,
count: 1,
reverse: false
}
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
static propTypes = propTypes
static defaultProps = defaultProps
render() {
const {
className,
action,
toggleClass,
enterClass,
leaveClass,
delay,
duration,
count,
easing,
reverse,
children
} = this.props
// 动画样式
const styleText = (() => {
let style = {}
// 设置延迟时长
if (delay) {
style.transitionDelay = delay
style.animationDelay = delay
}
// 设置播放时长
if (duration) {
style.transitionDuration = duration
style.animationDuration = duration
}
// 设置播放次数
if (count) {
style.animationIterationCount = count
}
// 设置缓动函数
if (easing) {
style.transitionTimingFunction = easing
style.animationTimingFunction = easing
}
// 设置动画方向
if (reverse) {
style.animationDirection = 'alternate'
}
return style
})()
return (
<div
className={
classnames({
transition: true
})
}
style={
{
position: 'relative',
overflow: 'hidden'
}
}
>
<div
className={
classnames({
'transition-wrapper': true,
[className]: className,
[toggleClass]: action && toggleClass,
[enterClass]: !toggleClass && action && enterClass,
[leaveClass]: !toggleClass && !action && leaveClass,
})
}
style={ styleText }
>
{ children }
</div>
</div>
)
}
}
export default Transition
在这里我们来看下相关的 Transition 增加的属性:
- delay:规定在动画开始之前的延迟。
- duration:规定完成动画所花费的时间,以秒或毫秒计。
- count:规定动画应该播放的次数。
- easing:规定动画的速度曲线。
- reverse:规定是否应该轮流反向播放动画。
四、优化
那么接下来我们来对 Transition 来进行一个优化,我们主要的是动画监听、卸载组件以及其他的相关兼容问题。那么我们在代码中添加 props 属性,并且设置默认值。代码如下所示:
const propTypes = {
...,
/** 动画结束的回调 */
onEnd: PropTypes.func,
/** 离开动画结束时卸载元素 */
exist: PropTypes.bool
}
const defaultProps = {
...,
reverse: false,
exist: false
}
接下来进行动画结束监听的事件代码:
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
...
onEnd = e => {
const { onEnd, action, exist } = this.props
if (onEnd) {
onEnd(e)
}
// 卸载 DOM 元素
if (!action && exist) {
const node = e.target.parentNode
node.parentNode.removeChild(node)
}
}
/**
* 对动画结束事件 onEnd 回调的处理函数
*
* @param {string} type - 事件解绑定类型: add - 绑定事件,remove - 移除事件绑定
*/
handleEndListener (type = 'add') {
const el = ReactDOM.findDOMNode(this).querySelector('.transition-wrapper')
const events = ['animationend', 'transitionend']
events.forEach(ev => {
el[`${type}EventListener`](ev, this.onEnd, false)
})
}
componentDidMount () {
this.handleEndListener()
}
componentWillUnmount () {
const { action, exist } = this.props
if (!action && exist) {
this.handleEndListener('remove')
}
}
render () {
...
}
}
在代码中我们可以知道使用到 componentDidMount 和 componentWillUnmount 这两个生命周期函数。
react-dom 中还为我们提供了可以在 React 应用中使用的 DOM 方法。我们通过获取兼容性 animationend 和transitionend 事件。检验函数方法的代码如下所示:
/**
* 浏览器兼容事件检测函数
*
* @param {node} el - 触发事件的 DOM 元素
* @param {array} events - 可能的事件类型
* @returns {*}
*/
const whichEvent = (el, events) => {
const len = events.length
for (var i = 0; i < len; i++) {
if (el.style[i]) {
return events[i];
}
}
}
修改 handleEndListener 函数代码如下所示:
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
...
/**
* 对动画结束事件 onEnd 回调的处理函数
*
* @param {string} type - 事件解绑定类型: add - 绑定事件,remove - 移除事件绑定
*/
handleEndListener (type = 'add') {
const el = ReactDOM.findDOMNode(this).querySelector('.transition-wrapper')
const events = ['AnimationEnd', 'TransitionEnd']
events.forEach(ev => {
const eventType = whichEvent(el, [ev.toLowerCase(), `webkit${ev}`])
el[`${type}EventListener`](eventType, this.onEnd, false)
})
}
...
}
那么到这里之后我们就完成了整个 Transition 组件的开发,相关完整代码如下所示:
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import ReactDOM from 'react-dom'
const propTypes = {
/** 执行动画 */
action: PropTypes.bool,
/** 切换的css动画的class名称 */
toggleClass: PropTypes.string,
/** 进入动画的class名称,存在 toggleClass 时无效 */
enterClass: PropTypes.string,
/** 离开动画的class名称,存在 toggleClass 时无效 */
leaveClass: PropTypes.string,
/** 动画延迟执行时间 */
delay: PropTypes.string,
/** 动画执行时间长度 */
duration: PropTypes.string,
/** 动画执行次数,只在执行 CSS3 动画时有效 */
count: PropTypes.number,
/** 动画缓动函数 */
easing: PropTypes.oneOf([
'linear',
'ease',
'ease-in',
'ease-out',
'ease-in-out'
]),
/** 是否强制轮流反向播放动画,count 为 1 时无效 */
reverse: PropTypes.bool,
/** 动画结束的回调 */
onEnd: PropTypes.func,
/** 离开动画结束时卸载元素 */
exist: PropTypes.bool
}
const defaultProps = {
action: false,
count: 1,
reverse: false,
exist: false
}
/**
* 浏览器兼容事件检测函数
*
* @param {node} el - 触发事件的 DOM 元素
* @param {array} events - 可能的事件类型
* @returns {*}
*/
const whichEvent = (el, events) => {
const len = events.length
for (var i = 0; i < len; i++) {
if (el.style[i]) {
return events[i];
}
}
}
/**
* css过渡动画组件
*
* @visibleName Transition 过渡动画
*/
class Transition extends React.Component {
static propTypes = propTypes
static defaultProps = defaultProps
onEnd = e => {
const { onEnd, action, exist } = this.props
if (onEnd) {
onEnd(e)
}
// 卸载 DOM 元素
if (!action && exist) {
const node = e.target.parentNode
node.parentNode.removeChild(node)
}
}
/**
* 对动画结束事件 onEnd 回调的处理函数
*
* @param {string} type - 事件解绑定类型: add - 绑定事件,remove - 移除事件绑定
*/
handleEndListener (type = 'add') {
const el = ReactDOM.findDOMNode(this).querySelector('.transition-wrapper')
const events = ['AnimationEnd', 'TransitionEnd']
events.forEach(ev => {
const eventType = whichEvent(el, [ev.toLowerCase(), `webkit${ev}`])
el[`${type}EventListener`](eventType, this.onEnd, false)
})
}
componentDidMount () {
this.handleEndListener()
}
componentWillUnmount() {
const { action, exist } = this.props
if (!action && exist) {
this.handleEndListener('remove')
}
}
render () {
const {
className,
action,
toggleClass,
enterClass,
leaveClass,
delay,
duration,
count,
easing,
reverse,
children
} = this.props
// 动画样式
const styleText = (() => {
let style = {}
// 设置延迟时长
if (delay) {
style.transitionDelay = delay
style.animationDelay = delay
}
// 设置播放时长
if (duration) {
style.transitionDuration = duration
style.animationDuration = duration
}
// 设置播放次数
if (count) {
style.animationIterationCount = count
}
// 设置缓动函数
if (easing) {
style.transitionTimingFunction = easing
style.animationTimingFunction = easing
}
// 设置动画方向
if (reverse) {
style.animationDirection = 'alternate'
}
return style
})()
const transition = (
<div
className={
classnames({
transition: true
})
}
style={
{
position: 'relative',
overflow: 'hidden'
}
}
>
<div
className={
classnames({
'transition-wrapper': true,
[className]: className,
[toggleClass]: action && toggleClass,
[enterClass]: !toggleClass && action && enterClass,
[leaveClass]: !toggleClass && !action && leaveClass,
})
}
style={ styleText }
>
{ children }
</div>
</div>
)
return transition
}
}
export default Transition
总结:
以上就是有关于“ React如何实现一个Transition过渡动画组件?”这个问题的相关内容,如果你有其他更好的方法和方式也可以和大家一同分享你的看法,更多有关于 React 这个框架的内容我们都可以在W3Cschool中进行搜索和了解。