React添加路由切换过渡动画

使用场景

在使用React router dom进行路由切换的时候,视图会直接切换,导致观感过于生硬,由此可以添加过渡动画,使过渡更加平滑。以下是两种可能的实现方案以供参考。

1. 组件懒加载+Suspense

React组件在被挂载可视前会执行一系列生命周期函数,如果待加载的组件被Suspense组件包裹,那组件在加载完毕前会被Suspense中设置的组件替代显示。

jsx 复制代码
<Suspense fallback={<Loading/>}>
  <MyComponent />
</Suspense>

这段代码实现的效果就是MyComponent组件在加载的时候会显示Loading的内容。

因此可以使用懒加载路由组件并用Suspense包裹所有路由组件的方法实现一个简单的过渡动画:

JSX 复制代码
// 路由配置文件
const Home=lazy(()=>import("../pages/Home/index.tsx"));

const routes=[
    {
        path:"/",
        element:<Home />
    }
]

// 路由挂载的组件
export default function Main() {
  const elements=useRoutes(routes);
  return (
    <main>
      <Suspense fallback={<Loading/>}>
        {elements}
      </Suspense>
    </main>
  )
}

使用ECharts实现的一个简单Loading做测试

JSX 复制代码
export default function Loading() {
  const loadingRef=useRef(null);

  useEffect(()=>{
    let loading=echarts.init(loadingRef.current);
    const option:EChartsOption = {
      graphic: {
        elements: [
          {
            type: 'group',
            left: 'center',
            top: 'center',
            children: new Array(7).fill(0).map((val, i) => ({
              type: 'rect',
              x: i * 20,
              shape: {
                x: 0,
                y: -40,
                width: 10,
                height: 80
              },
              style: {
                fill: '#ffffff'
              },
              keyframeAnimation: {
                duration: 1000,
                delay: i * 200,
                loop: true,
                keyframes: [
                  {
                    percent: 0.5,
                    scaleY: 0.3,
                    easing: 'cubicIn'
                  },
                  {
                    percent: 1,
                    scaleY: 1,
                    easing: 'cubicOut'
                  }
                ]
              }
            }))
          }
        ]
      }
    };
    loading.setOption(option);
  },[])
  return (
    <div ref={loadingRef} className={'loading-main'} />
  );
}

现在在路由页面切换时,这个Loading组件会显示,当组件加载完成时,Loading组件也会被正常卸载。

但美中不足的一点就是Loading这个组件本身也会没有过渡地突然出现和消失。我之前想过使用React的生命周期函数来动态控制CSS(添加复合类的形式),类似:

SCSS 复制代码
.loading{
    // 其他样式...
    transition: 0.3s;

    &.fade-in{
        opacity: 1;
        // 其他样式...
    }

    &.fade-out{
        opacity: 0;
        // 其他样式... 
    }
}

然后在componentDidMount时添加fade-in,在componentWillUnMount的时候添加fade-out实现Loading组件本身的过渡效果,后面测试时发现这种方法并不可行,经过搜索和自己测试,最终采取了下面的方式解决。

2. 使用react-transition-group

react-transition-group\]([react-transition-group - npm](https://link.juejin.cn?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Freact-transition-group "https://www.npmjs.com/package/react-transition-group"))是管理组件的挂载和卸载,并提供生命周期钩子来应用CSS动画或JavaScript动画的库,使用其可以实现比较完美的过渡动画 * ### 安装 ```shell npm install react-transition-group --save ``` * ### 使用 创建两个state用于管理动画状态 ```TSX const [isLoading, setIsLoading] = useState(false); const [showLoading, setShowLoading] = useState(false); ``` 再监测路由跳转,当路由变化时设置显示`Loading`组件并开始动画 ```TSX useEffect(() => { setIsLoading(true); setShowLoading(true); loadingStartTime.current = Date.now(); }, [location.key]); ``` 在所有路由组件的最外层使用`CSSTransition`包裹 ```TSX

{showLoading && ( )} } > {elements}
``` 并按`-enter, -exit`的CSS选择器名抽后缀设置CSS过渡效果`-active`则为状态激活后的动画效果 ```css .loading-transition-enter { opacity: 0; } .loading-transition-enter-active { opacity: 1; transition: opacity 150ms ease-in-out; } .loading-transition-exit { opacity: 1; } .loading-transition-exit-active { opacity: 0 transition: opacity 150ms ease-in-out; } .page-transition-enter { opacity: 0; } .page-transition-enter-active { opacity: 1; transition: opacity 150ms ease-in-out; } .page-transition-exit { opacity: 1; } .page-transition-exit-active { opacity: 0; transition: opacity 150ms ease-in-out; } ``` 现在已经能实现`Loading`组件的平滑进入效果,但是有一个新的问题就是状态不会退出,一直卡在`Loading`的动画中,我们只设置了`isLoading`和`showLoading`为`true`并没有在组件加载完成之后设置回`false`,因此要想办法在组件加载完成时还原状态。使用`Redux`或给每个路由组件传递一个回调控制父组件的`isLoading`和`showLoading`状态有点过于复杂,且不符合开闭原则。 所以我们可以创建一个组件用于跟踪路由组件的加载状态,给这个跟踪组件传递回调用于改变父组件加载相关`state`为`false`。 ```TSX type Props={ onFinish:()=>void } function LoadingTracker({ onFinish }:Props) { const [isMounted, setIsMounted] = useState(false); useEffect(() => { if (!isMounted) { setIsMounted(true); return; } onFinish(); }, [isMounted, onFinish]); return null; } ``` 把这个组件放到`Suspense`标签内 ```JSX } > {elements} ``` 添加`handleLoadingFinish`函数 ```javascript const handleLoadingFinish = () => { // 最短加载时间,防止加载时间过短导致闪屏 const minimumLoadingTime = 700; const elapsed = Date.now() - loadingStartTime.current; const remaining = Math.max(minimumLoadingTime - elapsed, 0); setTimeout(() => { setIsLoading(false); setShowLoading(false); }, remaining); }; ``` 这段代码执行的逻辑是 1. `LoadingTracker`组件被创建,同时初始化`isMounted`为`false`,然后触发`useEffect`设置`isMounted`为`true` 2. `Suspense`加载新的路由组件(`element`) 3. 路由组件加载完成后,`Suspense`重新渲染更新子组件(`LoadingTracker`和路由组件) 4. `LoadingTracker`触发`useEffect`,执行`onFinish`回调,执行父组件的`handleLoadingFinish` 5. 加载完毕 至此就完全实现了路由切换的加载过渡,欢迎在评论区进行指正和讨论。

相关推荐
brzhang3 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
程序员Bears3 分钟前
深入理解CSS3:Flex/Grid布局、动画与媒体查询实战指南
前端·css3·媒体·visual studio code
David凉宸15 分钟前
凉宸推荐给大家的一些开源项目
前端
袋鱼不重17 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor
hyyyyy!17 分钟前
《从分遗产说起:JS 原型与继承详解》
前端·javascript·原型模式
竹苓18 分钟前
从一个想法到上线,一万字记录我开发浏览器插件的全过程
前端
小桥风满袖19 分钟前
Three.js-硬要自学系列19 (曲线颜色渐变、渐变插值、查看设置gltf顶点、山脉高度可视化)
前端·css·three.js
zayyo19 分钟前
Vue.js性能优化新思路:轻量级SSR方案深度解析
前端·面试·性能优化
北溟鱼鱼鱼20 分钟前
跨域解决方案
前端
六边形66620 分钟前
一文搞懂JavaScript 与 BOM、DOM、ECMAScript、Node.js的用处
前端·javascript·面试