React Transition Group能做什么?
就官方文档所言,其实react-transition-group并非动画库,并不会提供动画样式(如React-Motion)。react-transition-group提供了能让你更方便实现 转换过程 中过渡效果的组件,通过这些组件可以管理过渡时机、操纵DOM。
安装
# npm
npm install react-transition-group --save
# yarn
yarn add react-transition-group
基础组件 Transition
Transition 用于实现并控制单个组件的过渡效果,而且预留了多个钩子。
1. Transition 用法
下面用弹窗组件举例:
<Transition in={visible} timeout={500} mountOnEnter unmountOnExit>
{state => (
<div className={styles.popup}>
{/* 遮罩层 */}
<div
className={styles.mask}
onClick={() => onCancel()}
style={{ ...maskDefaultStyle, ...maskTransitionStyles[state] }}
/>
{/* 弹窗内容 */}
<div
className={styles.modal}
style={{
width,
...modalDefaultStyle,
...modalTransitionStyles[state],
}}
>
</div>
</div>
)}
</Transition>
Transition组件接受一个Fuction类型的children,传参为state(类型为TransitionStatus),我们主要使用其中四个状态:entering、entered、exiting 和 exited 分别代表进入和离开的过渡效果。
我们需要为实现过渡效果的元素的样式,添加一个transition值,也要为其过渡过程中的四个状态定义样式(没错,就是传统html实现过渡效果的方式😀),如下:
const duration = 400;
const maskDefaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0.5,
};
const maskTransitionStyles: Partial<Record<TransitionStatus, any>> = {
entering: { opacity: 0.5, transform: 'scale(1)' },
entered: { opacity: 0.5, transform: 'scale(1)' },
exiting: { opacity: 0, transform: 'scale(0)' },
exited: { opacity: 0, transform: 'scale(0)' },
};
然后就是将样式添加到对应元素的style属性值中。 整体代码如下
const duration = 400;
const maskDefaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
};
const maskTransitionStyles: Partial<Record<TransitionStatus, any>> = {
entering: { opacity: 0.5, transform: 'scale(1)' },
entered: { opacity: 0.5, transform: 'scale(1)' },
exiting: { opacity: 0, transform: 'scale(0)' },
exited: { opacity: 0, transform: 'scale(0)' },
};
const modalDefaultStyle = {
// transition
transition: `all ${duration}ms ease-in-out`,
opacity: 0,
transform: 'scale(0)',
};
const modalTransitionStyles: Partial<Record<TransitionStatus, any>> = {
entering: { opacity: 1, transform: 'scale(1)' },
entered: { opacity: 1, transform: 'scale(1)' },
exiting: { opacity: 0, transform: 'scale(0)' },
exited: { opacity: 0, transform: 'scale(0)' },
};
<Transition in={visible} timeout={500} mountOnEnter unmountOnExit>
{state => (
<div className={styles.popup}>
{/* 遮罩层 */}
<div
className={styles.mask}
onClick={() => onCancel()}
style={{ ...maskDefaultStyle, ...maskTransitionStyles[state] }}
/>
{/* 弹窗内容 */}
<div
className={styles.modal}
style={{
width,
...modalDefaultStyle,
...modalTransitionStyles[state],
}}
>
</div>
</div>
)}
</Transition>
2. Transition的属性
Transition 所接受的props如下表所示(粗略的写一下,详细可去看官方文档):
| props | 是否可选 | 解释 |
|---|---|---|
nodeRef | 可选 | 用于获取过渡组件的DOM元素ref。如果定义了nodeRef值,诸如onEnter等钩子的回调函数,将不会传递node。如果没有定义nodeRef,Transition组件会使用ReactDom.findDomNode来获取DOM元素 |
children | 必要 | 使用时传递一个函数类型的children,传参为state(过渡状态),返回相应样式的过渡组件 |
in | 可选 | 用于控制过渡组件的进入或离开,默认值为false |
mountOnEnter | 可选 | 是否在首次过渡组件进入时执行才挂载,也就是第一次in属性为true时挂载,默认值为false |
unmountOnExit | 可选 | 是否在过渡组件离开时执行卸载,默认值为false |
appear | 可选 | 一般情况下,Transition并不会在首次挂载时展现进入效果,即使in初始值为true。如果想要实现这种效果,需要同时将appear属性和in属性置为true |
enter | 可选 | 控制是否展示进入效果,默认值为true |
exit | 可选 | 控制是否展示离开效果,默认值为true |
timeout | 如果没有定义addEndListener,则为必要 | 可为单一的number类型值,或者为对象类型{ enter?: number, exit?: number, appear?: number },为对象时三个属性默认值都为0。单位为毫秒。注意:这里的值应与样式中的transition-duration值保持一致 |
onEnter/onEntering | 可选 | Transition组件过渡状态为entering之前/之后的钩子, 类型Function(node: HtmlElement, isAppearing: bool) -> void, isAppearing指是否首次挂载时的进入过渡,注意:当提供了nodeRef属性值时,这里的函数并不会传递node |
onEntered | 可选 | Transition组件过渡状态为entered之后的钩子, 类型Function(node: HtmlElement, isAppearing: bool) -> void, isAppearing指是否首次挂载时的进入过渡,注意:当提供了nodeRef属性值时,这里的函数并不会传递node |
onExit/onExiting | 可选 | Transition组件过渡状态为exiting之前/之后的钩子, 类型Function(node: HtmlElement) -> void,注意:当提供了nodeRef属性值时,这里的函数并不会传递node |
onExited | 可选 | Transition组件过渡状态为exited之后的钩子, 类型Function(node: HtmlElement) -> void,注意:当提供了nodeRef属性值时,这里的函数并不会传递node |
addEndListener | timeout属性与addEndListener属性必须有一个,同时有则以前者为主 | 其实timeout属性与addEndListener属性都是提供给Transition组件使用的,Transition组件需要在过渡效果结束时执行钩子。 |
// 注意:当提供了nodeRef属性值时,这里的回调函数并不会传递node
addEndListener={(node, done) => {
// use the css transitionend event to mark the finish of a transition
node.addEventListener('transitionend', done, false);
}}
注意,Transition组件的初始过渡状态是这么决定的。如下面代码(解释某些情况下
mountOnEnter为false不生效的原因)
construct() {
// ...
if (props.in) {
if (appear) {
initialStatus = EXITED;
this.appearStatus = ENTERING;
} else {
initialStatus = ENTERED;
}
} else {
if (props.unmountOnExit || props.mountOnEnter) {
initialStatus = UNMOUNTED;
} else {
initialStatus = EXITED;
}
}
this.state = { status: initialStatus };
// ...
}
CSSTransition
在Transition组件中,我们将过渡组件包装成Function类型的children,并且通过style属性来定义各个过渡状态的样式。如果想通过css来定义过渡的样式和动画,我们可以选用React Transition Group提供的另一个组件CSSTransition。
1. CSSTransition用法
现在改造一下上文的弹窗组件
<CSSTransition
classNames={{
enter: styles.enter,
enterActive: styles.enterActive,
exit: styles.exit,
exitActive: styles.exitActive,
}}
in={visible}
timeout={400}
mountOnEnter
unmountOnExit
>
<div className={styles.popup}>
{/* 遮罩层 */}
<div
className={styles.mask}
onClick={() => onCancel()}
/>
{/* 弹窗内容 */}
<div
className={styles.modal}
style={{width}}
>
</div>
</div>
</CSSTransition>
可以看到,CSSTransition中的过渡组件不再需要包装成Fuction类型,而是可以直接作为children。同时使用CSSTranstion组件相比Transition组件多了classNames属性,这个属性可以用来定义各个过渡状态的css类。注意:笔者这里使用了css module的方式处理组件的样式隔离,如代码中的styles.enter就是一个css类名。
css部分如下所示:
.enter .mask {
opacity: 0;
transform: scale(0);
}
.enterActive .mask {
opacity: 0.5;
transform: scale(1);
transition: opacity 0.4s ease-in-out;
}
.exit .mask {
opacity: 0.5;
transform: scale(1);
}
.exitActive .mask {
opacity: 0;
transform: scale(0);
transition: opacity 0.4s ease-in-out;
}
.enter .modal {
opacity: 0;
transform: scale(0);
}
.enterActive .modal {
opacity: 1;
transform: scale(1);
transition: all 0.4s ease-in-out;
}
.exit .modal {
opacity: 1;
transform: scale(1);
}
.exitActive .modal {
opacity: 0;
transform: scale(0);
transition: all 0.4s ease-in-out;
}
值得一说,CSSTransition的类名将会挂载在nodeRef关联的DOM实例上(没有定义nodeRef的话,就是通过findDOMNode获取到的DOM实例)。
2. CSSTransition的属性
CSSTransition组件直接复用了Transition组件,CSSTransition组件可以使用全部的Transition的属性。
区别:
CSSTransition的钩子时机都改成了相应的 css类名添加完成 时机,如onEnter对应enter或appear类名,onEntering对应enter-active或appear-active类名,onEntered对应enter-done或appear-done类名;CSSTransition有一个独有的属性classNames。- 在
CSSTransition中我们可以为appear过渡状态添加独立样式(appear指过度组件初次进入的时候)。appear过渡样式只能有*-appear、*-appear-active、*-appear-done等类样式决定。
| props | 是否可选 | 解释 |
|---|---|---|
classNames | 可选(默认值为空字串) | 当classNames属性值为字符串时,用作css类名的前缀。当classNames属性值为对象时,可以单独定义每个状态下的样式 { appear?: string, appearActive?: string, appearDone?: string, enter?: string, enterActive?: string, enterDone?: string, exit?: string, exitActive?: string, exitDone?: string, } |
值得注意:像
*-appear、*-appear-activecss类名会在in属性变更后,先后添加进过渡组件的DOM实例的class中,然后在过渡效果消失时删除,并添加*-appear-done类名。
在CSSTransition中还可以使用Animate.css里面的动画,就直接添加到
*-active类中即可
.alert-enter-active {
animation: bounceInLeft; /* referring directly to the animation's @keyframe declaration */
animation-duration: 0.3s; /* don't forget to set a duration! */
}
.alert-exit-active {
animation: bounceOutLeft; /* 进入和离开的离开的动画还是配套的 */
animation-duration: 0.3s;
}
最后,看一看本文所示弹窗组件代码的演示效果😀