过渡动画使 UI 更富有表现力并且易于使用。如何使用 React 快速的实现一个 Transition 过渡动画组件?

基本实现

实现一个基础的 CSS 过渡动画组件,通过切换 CSS 样式实现简单的动画效果,也就是通过添加或移除某个 class 样式。因此需要给 Transition 组件添加一个 toggleClass 属性,标识要切换的 class 样式,再添加一个 action 属性实现样式切换,action 为 true 时添加 toggleClass 到动画元素上,action 为 false 时移除 toggleClass。

安装 classnames 插件:

npm install classnames --save-dev

classnames 是一个简单的JavaScript实用程序,用于有条件地将 className 连接在一起。

在 components 目录下新建一个 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,在 JSX 中,使用 camelCase(小驼峰命名)来定义属性的名称,使用大括号“{}”嵌入任何有效的 JavaScript 表达式

如:

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

等价于:

const element = <h1>Hello, Josh Perez</h1>;

注意:

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex。

另外,在 React 中,props.children

包含组件所有的子节点,即组件的开始标签和结束标签之间的内容(与 Vue 中 slot 插槽相似)。例如:

<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-types 实现运行时类型检查。

注意:

prop-types 是一个运行时类型检查工具,也是 create-react-app 脚手架默认配置的运行时类型检查工具,使用时直接引入即可,无需安装。

完整的 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

现在,可以使用我们的 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 动画库。接下来,实现在 Transition 组件中使用 Animate.css 实现强大的 CSS3 动画。

由于 Animate.css 动画在进入动画和离开动画通常使用两个效果相反的 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 的接口。动画通常可以设置延迟时间,播放时长,播放次数等属性。因此,需要给 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 的功能已经相当丰富,可以很精细的控制 CSS3 动画。

优化

这一步,我们需要针对 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 生命周期的介绍请移步组件生命周期

react-dom 提供了可在 React 应用中使用的 DOM 方法。

获取兼容性的 animationend 事件和 transitionend 事件。不同的浏览器要求使用不同的前缀,因为火狐和IE都已经支持了这两个事件,因此,只需针对 webkit 内核浏览器进行兼容的 webkitTransitionEnd 事件检测。检测函数代码如下:

/**
* 浏览器兼容事件检测函数
*
* @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 实现一个 Transition 过渡动画组件的更多相关文章

  1. 如何基于 React 封装一个组件

    如何基于 React 封装一个组件 前言 很多小伙伴在第一次尝试封装组件时会和我一样碰到许多问题,比如人家的组件会有 color 属性,我们在使用组件时传入组件文档中说明的属性值如 primary , ...

  2. 12 react 基础 的 css 过渡动画 及 动画效果 及 使用 react-transition-group 实现动画

    一. 过渡动画 # index.js import React from 'react';import ReactDOM from 'react-dom';import App from './app ...

  3. transition过渡动画

    过渡动画必须写在<transition></transition>标签内,配合其他标签使用. 例子: <transition name="fade" ...

  4. 基于iview 封装一个vue 表格分页组件

    iview 是一个支持中大型项目的后台管理系统ui组件库,相对于一个后台管理系统的表格来说分页十分常见的 iview是一个基于vue的ui组件库,其中的iview-admin是一个已经为我们搭好的后天 ...

  5. 基于antd封装一个高可用form组件 减少cv代码导致的bug

    引言 在开发中台过程中 我们的原型中有很多表单,antd有表单组件,但是粒度比较细,就单纯组件而言,无可厚非,但是在开发过程中,可能会造成代码不够聚合,有些表单公共逻辑无法提取,copy paste比 ...

  6. 用js触发CSS3-transition过渡动画

    用js触发CSS3-transition过渡动画 经过这几天的工作,让我进一步的了解到CSS3的强大,原本许多需要js才能实现的动画效果,现在通过CSS3就能轻易实现了,但是CSS3也有自身的不足,例 ...

  7. React文档(五)组件和props

    组件可以让你将UI分割成独立的,可复用的模块,然后考虑将每个模块彼此隔离.从概念上理解,组件就像js中的函数.他们接受随意的输入(被称为props)然后返回React元素来描述屏幕上应该出现什么. 函 ...

  8. RSuite 一个基于 React.js 的 Web 组件库

    RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...

  9. 067——VUE中vue-router之使用transition设置酷炫的路由组件过渡动画效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

随机推荐

  1. numpy 索引和切片

    一.取行 1.单行 数组[index, :] # 取第index+1行 例子 import numpy as np arr1 = np.arange(0, 24).reshape(4, 6) # 取第 ...

  2. Linux下利用Ant调用Jmeter脚本生成HTML测试报告

    今天我们学习如何利用Ant调用Jmeter脚本,并将生成的 jtl 文件转换为 HTML 格式的测试报告. 准备工作 需要在Linux上提前安装好 JDK. Jmeter 和 Ant. 1,JDK(可 ...

  3. Mac 查看端口占用情况及杀死进程

    查看端口占用情况命令 sudo lsof -i :9000 冒号后面就是你需要查看的端口号. TheEternityZhang-MacBook:project zhtony$ sudo lsof -i ...

  4. Perl语言入门(中文版)(第6版) 东南大学出版社

    第一章简介 问题与答案 这本书适合你吗? 为何有这么多的脚注? 关于习题和解答? 习题前标的数字是什么意思? 如果我是Perl讲师? “Perl”这个词表示什么意思? Larry为什么要创造Perl? ...

  5. Node.js 官方文档中文版

    这目录也是醉了 . 列出跟没列出没两样

  6. GP工作室—Alpha版本发布1

    目录 GP工作室-Alpha版本发布1 一.简介 1.1作业要求 1.2团队成员 二.软件下载安装说明 五.项目总结 @(Gold Point团队の项目计划) GP工作室-Alpha版本发布1 一.简 ...

  7. Python的小括号( )、中括号[ ]和大括号{}分别代表什么?

    python语言最常见的括号有三种,分别是:小括号( ).中括号[ ]和大括号也叫做花括号{ },分别用来代表不同的python基本内置数据类型. 1.python中的小括号( ):代表tuple元组 ...

  8. php--->注册模式

    注册模式 什么是注册树模式? 注册树模式当然也叫注册模式,注册器模式.注册树模式通过将对象实例注册到一棵全局的对象树上,需要的时候从对象树上采摘的模式设计方法. 优点:单例模式解决的是如何在整个项目中 ...

  9. 对于n!的快速质因数分解

    N!的阶乘的质因数分解 对于N的阶乘 比如8! 我们要算其中一个质因数出现次数 我们注意到 8!=1 2 3 4 5 6 7 8 1 1 1 1 2的倍数出现的次数8/2=4 1 1 4的倍数出现的次 ...

  10. nginx之历史回溯

    前言 nginx是一个web应用及反向代理工具,由一名俄罗斯程序员(Igor)发明的:NGINX是一个免费的,开源的高性能HTTP服务器和反向代理,以及IMAP / POP3代理服务器. NGINX以 ...