引言
在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