引言
在React应用中,实现过渡动画是一个常见的需求。本文将介绍如何使用react-transition-group
库来实现一些常用的可控的淡入淡出过渡效果。为了便于复用,我将其封装为了一个核心组件FadeContentWrapper
和两个主要的应用组件SimpleFadeTransition
、SwitchFadeTransition
。
安装依赖
首先,确保你已经安装了react-transition-group
库。
npm install react-transition-group
核心组件
FadeContentWrapper
这是一个styled-components组件,用于应用过渡效果。
import {styled} from "twin.macro"; /** * A styled div component that applies a fade transition effect. * * @component * @param {Object} props * @param {string} [props.fadeStyle='opacity'] - The type of transition style ('opacity', 'down', 'up'). * @param {string} [props.duration='0.3s'] - The duration of the transition. * @param {string} [props.timingFunction='ease'] - The timing function for the transition. * @param {string} [props.offset='15px'] - The offset value, used for 'down' and 'up' styles. * @param {string} [props.className='fade'] - The className for the transition styles. * * @example * <FadeContentWrapper fadeStyle="down" duration="0.5s" timingFunction="linear" offset="20px" className="myTransition"> * Content goes here... * </FadeContentWrapper> */ export const FadeContentWrapper = styled.div` ${({fadeStyle = 'opacity', duration, timingFunction, offset, className = 'fade'}) => createTransitionStyles(fadeStyle, duration, timingFunction, offset, className) }; `; /** * Creates a CSS transition style string based on the provided parameters. * * @function * @param {'opacity' | 'down' | 'up' | 'left' | 'right' | 'slideFromBottom' | 'scale'} style - The type of transition style ('opacity', 'down', 'up', 'left', 'right', 'slideFromBottom', 'scale'). * @param {string} [duration='0.3s'] - The duration of the transition. * @param {string} [timingFunction='ease'] - The timing function for the transition (e.g., 'linear', 'ease-in'). * @param {string} [offset='15px'] - The offset value, used for 'down' and 'up' styles. * @param {string} [className='fade'] - The className for the transition styles. * @returns {string} The generated CSS transition style string. * * @example * const fadeStyle = createTransitionStyles('opacity', '0.5s', 'linear', '15px', 'myTransition'); * const downStyle = createTransitionStyles('down', '0.4s', 'ease-in-out', '20px', 'myTransition'); */ export function createTransitionStyles(style, duration = '0.3s', timingFunction = 'ease', offset = '15px', className = 'fade') { switch (style) { case 'opacity': return ` &.${className}.enter { opacity: 0; } &.${className}.enter-active { opacity: 1; transition: opacity ${duration} ${timingFunction}; } &.${className}.exit { opacity: 1; } &.${className}.exit-active { opacity: 0; transition: opacity ${duration} ${timingFunction}; }`; case 'down': return ` &.${className}.enter { transform: translateY(-${offset}); opacity: 0; } &.${className}.enter-active { transform: translateY(0); opacity: 1; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; } &.${className}.exit { transform: translateY(0); opacity: 1; } &.${className}.exit-active { transform: translateY(${offset}); opacity: 0; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; }`; case 'up': return ` &.${className}.enter { transform: translateY(${offset}); opacity: 0; } &.${className}.enter-active { transform: translateY(0); opacity: 1; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; } &.${className}.exit { transform: translateY(0); opacity: 1; } &.${className}.exit-active { transform: translateY(-${offset}); opacity: 0; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; }`; case 'scale': return ` &.${className}.enter { transform: scale(0); opacity: 0; } &.${className}.enter-active { transform: scale(1); opacity: 1; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; } &.${className}.exit { transform: scale(1); opacity: 1; } &.${className}.exit-active { transform: scale(0); opacity: 0; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; }`; case 'slideFromBottom': return ` &.${className}.enter { transform: translateY(${offset}); opacity: 0; } &.${className}.enter-active { transform: translateY(0); opacity: 1; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; } &.${className}.exit { transform: translateY(0); opacity: 1; } &.${className}.exit-active { transform: translateY(${offset}); opacity: 0; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; }`; case 'right': return ` &.${className}.enter { transform: translateX(-${offset}); opacity: 0; } &.${className}.enter-active { transform: translateX(0); opacity: 1; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; } &.${className}.exit { transform: translateX(0); opacity: 1; } &.${className}.exit-active { transform: translateX(${offset}); opacity: 0; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; }`; case 'left': return ` &.${className}.enter { transform: translateX(${offset}); opacity: 0; } &.${className}.enter-active { transform: translateX(0); opacity: 1; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; } &.${className}.exit { transform: translateX(0); opacity: 1; } &.${className}.exit-active { transform: translateX(-${offset}); opacity: 0; transition: opacity ${duration} ${timingFunction}, transform ${duration} ${timingFunction}; }`; default: return ''; } }
SimpleFadeTransition
这是一个简单的淡入淡出组件。主要用于单个内容对象的可控淡入淡出动画。
import React, { useRef } from 'react'; import { CSSTransition } from 'react-transition-group'; import { FadeContentWrapper } from './FadeStyles'; /** * A component that provides a simple fade transition effect for a single content element. * * @param {Object} props * @param {boolean} props.in - Determines if the content should be visible. * @param {React.ReactNode} [props.children=null] - The content to display and animate. * @param {'opacity' | 'down' | 'up' | 'left' | 'right' | 'slideFromBottom' | 'scale'} [props.fadeStyle='opacity'] - The type of transition style. * @param {string} [props.className='fade'] - The className for CSSTransition. * @param {string} [props.duration='0.3s'] - The duration of the transition. * @param {string} [props.offset='15px'] - The offset value, used for 'down' and 'up' styles. * @param {boolean} [props.exit=true] - Determines if the exit transition should be animated. * * @example * <SimpleFadeTransition * in={isVisible} * fadeStyle={'scale'} * duration={'0.2s'} * className={'content-fade'} * offset={'20px'} * exit={false} * > * <div>My Content</div> * </SimpleFadeTransition> * * @attention * * 不能在条件渲染语句中使用该组件,而应该由组件自身的`in`属性来决定是否显示内容,否则会丢失过渡效果。 */ const SimpleFadeTransition = ({ in: inProp, children = null, fadeStyle = 'opacity', className = 'fade', duration = '0.3s', offset = '15px', exit = true }) => { const nodeRef = useRef(null); return ( <CSSTransition in={inProp} nodeRef={nodeRef} className={className} timeout={parseFloat(duration) * 1000} // Convert duration to milliseconds exit={exit} unmountOnExit > <FadeContentWrapper ref={nodeRef} fadeStyle={fadeStyle} className={className} duration={duration} offset={offset}> {children} </FadeContentWrapper> </CSSTransition> ); } export default SimpleFadeTransition;
SwitchFadeTransition
这是一个用于在两个内容之间切换的组件,可以在切换的过程中应用指定过渡效果。
import React, {useRef} from 'react'; import {SwitchTransition, CSSTransition} from 'react-transition-group'; import {FadeContentWrapper} from './FadeStyles'; /** * A component that provides a fade transition effect between two content elements. * * @param {Object} props * @param {boolean} props.isOn - Determines which content to display. * @param {React.ReactNode} [props.onContent=null] - Content to display when isOn is true. * @param {React.ReactNode} [props.offContent=null] - Content to display when isOn is false. * @param {'opacity' | 'down' | 'up' | 'left' | 'right' | 'slideFromBottom' | 'scale'} [props.fadeStyle='opacity'] - The type of transition style ('opacity', 'down', 'up', 'left', 'right', 'slideFromBottom', 'scale'). * @param {'out-in' | 'in-out'} [props.mode='out-in'] - The transition mode for SwitchTransition ('out-in' or 'in-out'). * @param {string} [props.className='fade'] - The className for CSSTransition. * @param {string} [props.duration='0.3s'] - The duration of the transition. * * @example * <SwitchFadeTransition * isOn={isOn} * fadeStyle={'scale'} * duration={'0.2s'} * className={'moon-sun'} * onContent={<FontAwesomeIcon icon={solid("sun")} spin tw={'text-amber-400'}/>} * offContent={<FontAwesomeIcon icon={solid("moon")}/>} * /> */ const SwitchFadeTransition = ({ isOn, onContent = null, offContent = null, fadeStyle = 'opacity', mode = 'out-in', className = 'fade', duration = '0.3s' }) => { const nodeRef = useRef(null); return ( <SwitchTransition mode={mode}> <CSSTransition key={isOn ? "on" : "off"} nodeRef={nodeRef} className={className} addEndListener={(done) => nodeRef.current.addEventListener("transitionend", done, false)} > <FadeContentWrapper ref={nodeRef} fadeStyle={fadeStyle} className={className} duration={duration}> {isOn ? onContent : offContent} </FadeContentWrapper> </CSSTransition> </SwitchTransition> ); } export default SwitchFadeTransition;
使用示例
SimpleFadeTransition
<SimpleFadeTransition in={isVisible} fadeStyle={'scale'} duration={'0.2s'} className={'content-fade'} offset={'20px'} exit={false} > <div>My Content</div> </SimpleFadeTransition>
SwitchFadeTransition
<SwitchFadeTransition isOn={isOn} fadeStyle={'scale'} duration={'0.2s'} className={'moon-sun'} onContent={<FontAwesomeIcon icon={solid("sun")} spin tw={'text-amber-400'}/>} offContent={<FontAwesomeIcon icon={solid("moon")}/>} />
再封装一个简单的含切换动效的按钮
import React, {useEffect, useRef, useState} from 'react'; import tw from 'twin.macro'; import {UniButton} from "@/components/Button/Styled.twin"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {solid} from "@fortawesome/fontawesome-svg-core/import.macro"; import SwitchFadeTransition from "@/styles/transition/SwitchFadeTransition"; const SwitchButton = ({defaultOn = false, onChange, hasShadow = true, _tw}) => { const [isOn, setIsOn] = useState(defaultOn); const onRef = useRef(null); const offRef = useRef(null); const nodeRef = isOn ? onRef : offRef; const handleToggle = () => { const newState = !isOn; setIsOn(newState); if (onChange) { onChange(newState); } }; return ( <div tw={''}> <UniButton _tw={_tw} tw={'h-10 w-10 leading-10 text-blue-400 md:hover:text-blue-300'} onClick={handleToggle} hasShadow={hasShadow} > <SwitchFadeTransition isOn={isOn} fadeStyle={'scale'} duration={'0.2s'} className={'moon-sun'} onContent={<FontAwesomeIcon icon={solid("sun")} spin tw={'text-amber-400'}/>} offContent={<FontAwesomeIcon icon={solid("moon")}/>} /> </UniButton> </div> ); }; export default SwitchButton;
效果展示
注意事项
- 不能在条件渲染语句中使用
SimpleFadeTransition
组件,而应该由组件自身的in
属性来决定是否显示内容,否则会丢失过渡效果。
总结
通过react-transition-group
库和一些基础的React知识,我们可以轻松地在React应用中实现各种复杂的过渡效果。希望这篇文章能帮助你更好地理解和使用这个强大的库。
Comments NOTHING