xianzou / blog

弦奏的博客 一个混迹多年的前端开发人员,正在努力的学习中
17 stars 2 forks source link

React移动端添加路由转场动画 #29

Open xianzou opened 3 years ago

xianzou commented 3 years ago

移动端添加转场动画

移动端底部有菜单强烈建议采用flex竖向布局,不要使用position: fixed去定位菜单到底部

原React路由切换效果

img

实现的效果

img

实现

安装官方推荐动画库依赖

yarn add react-transition-group

使用

import {CSSTransition, TransitionGroup} from 'react-transition-group';

const ANIMATION_MAP = {
  PUSH: 'forward',
  POP: 'back',
  REPLACE: 'replace'// 底部菜单的页面不需要使用动态路由,底部菜单使用replace替换路径,防止菜单也会跟着转场动画
}

<TransitionGroup
    className={'router-wrapper'}
    childFactory={child => React.cloneElement(
      child,
      {classNames: ANIMATION_MAP[history.action]}
    )}
  >
    <CSSTransition
      timeout={500}
      key={location.pathname}
    >
        <Suspense fallback={<div>加载中...</div>}>
            <Switch>
                <Route exact path={'/'} component={HomePage} />
                <Route exact path={'/about'} component={AboutPage} />
                <Route exact path={'/list'} component={ListPage} />
                <Route exact path={'/detail'} component={DetailPage} />
            </Switch>
        </Suspense>
    </CSSTransition>
</TransitionGroup>

懒加载和非懒加载的区别

import { HomePage, AboutPage, ListPage, DetailPage } from '@/components/component/index';

const HomePage = lazy(() => import('@/components/component/HomePage'));
const AboutPage = lazy(() => import('@/components/component/AboutPage'));
const ListPage = lazy(() => import('@/components/component/ListPage'));
const DetailPage = lazy(() => import('@/components/component/DetailPage'));

network 页面会闪

引入样式

:global{
    .router-wrapper {
        height: 100%;
      }

      .forward-enter {
        opacity: 0;
        transform: translateX(100%);
        box-shadow: 0 4px 7px rgba(0,0,0,.4);
        transition-timing-function:linear;
      }

      .forward-enter-active {
        opacity: 1;
        transform: translateX(0);
        transition: all 120ms;
        transition-timing-function:linear;
      }

      .forward-exit {
        opacity: 1;
        transform: translateX(0);
        transition-timing-function:linear;
      }

      .forward-exit-active {
        opacity: 0;
        transform: translateX(-100%);
        transition: all 120ms;
        transition-timing-function:linear;
      }

      .back-enter {
        opacity: 0;
        transform: translateX(-100%);
        box-shadow: 0 4px 7px rgba(0,0,0,.4);
        transition-timing-function:linear;
      }

      .back-enter-active {
        opacity: 1;
        transform: translateX(0);
        transition: all 120ms;
        transition-timing-function:linear;
      }

      .back-exit {
        opacity: 1;
        transform: translateX(0);
        transition-timing-function:linear;
      }

      .back-exit-active {
        opacity: 0;
        transform: translate(100%);
        transition: all 120ms;
        transition-timing-function:linear;
      }
    .replace-enter >div:first-child{
       opacity: 0.01;
    }

    .replace-enter-active >div:first-child{
      opacity: 1;
      transition: opacity 250ms ease-in;
    }

    .replace-exit >div:first-child{
       opacity: 1;
    }

    .replace-exit-active >div:first-child{
        opacity: 0.01;
        transition: opacity 250ms ease-in;
    }
}

改造dva路由

import { Switch, Route, withRouter, HashRouter } from 'react-router-dom';

const Routes = withRouter(({ history, location }) => (
    <TransitionGroup
        className={'router-wrapper'}
        childFactory={child => React.cloneElement(
            child,
            { classNames: ANIMATION_MAP[history.action] }
        )}
    >
        <CSSTransition
            timeout={500}
            key={location.pathname}
        >
            <Suspense fallback={<div>加载中...</div>}>
                <Switch>
                    <Route exact path={'/'} component={HomePage} />
                    <Route exact path={'/about'} component={AboutPage} />
                    <Route exact path={'/list'} component={ListPage} />
                    <Route exact path={'/detail'} component={DetailPage} />
                </Switch>
            </Suspense>
        </CSSTransition>
    </TransitionGroup>
));

export default () => {
    return (
        <HashRouter>
            <Routes />
        </HashRouter>
    );
};

懒加载没有效果?

试下不使用懒加载,不使用懒加载有效果

改造适合懒加载

<div className="mobile-main">
    <Suspense fallback={<Loading />} maxDuration={500}>
        <Switch location={location}>
            ...
        </Switch>
    </Suspense>
</div>

修改index.scss,添加样式

.mobile-main{
    position: relative;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    height: 100%;
}

大功告成

其他效果

项目运用

https://cli.im/ 二维码生成

其他问题

完整代码

## router.js
import './index.scss';
import React, { lazy, Suspense } from 'react';
import { Route, Switch, HashRouter, withRouter } from 'react-router-dom';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

const HomePage = lazy(() => import('@/components/component/HomePage')); //路由组件
const AboutPage = lazy(() => import('@/components/component/AboutPage')); //路由组件
const ListPage = lazy(() => import('@/components/component/ListPage')); //路由组件
const DetailPage = lazy(() => import('@/components/component/DetailPage')); //路由组件

// import { HomePage, AboutPage, ListPage, DetailPage } from '@/components/component/index';

const ANIMATION_MAP = {
    PUSH: 'forward',
    POP: 'back',
   REPLACE: 'replace'
};

const Router = withRouter(({ history, location }) => (
    <TransitionGroup
        className={'router-wrapper'}
        fade
        childFactory={child => React.cloneElement(
            child,
            { classNames: ANIMATION_MAP[history.action] }
        )}
    >
        <CSSTransition
            timeout={500}
            key={location.pathname}
        >
            <div className="mobile-main">
                <Suspense fallback={<div>加载中...</div>}>
                    <Switch>
                        <Route exact path={'/'} component={HomePage} />
                        <Route exact path={'/about'} component={AboutPage} />
                        <Route exact path={'/list'} component={ListPage} />
                        <Route exact path={'/detail'} component={DetailPage} />
                    </Switch>
                </Suspense>
            </div>
        </CSSTransition>
    </TransitionGroup>
));

export default () => (<HashRouter><Router /></HashRouter>);

样式

:global{
    .router-wrapper {
        height: 100%;
    }
      .mobile-main{
        position: relative;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        height: 100%;
      }
      .forward-enter {
        opacity: 0;
        transform: translateX(100%);
      }

      .forward-enter-active {
        opacity: 1;
        transform: translateX(0);
        transition: all 500ms;
      }

      .forward-exit {
        opacity: 1;
        transform: translateX(0);
      }

      .forward-exit-active {
        opacity: 0;
        transform: translateX(-100%);
        transition: all 500ms;
      }

      .back-enter {
        opacity: 0;
        transform: translateX(-100%);
      }

      .back-enter-active {
        opacity: 1;
        transform: translateX(0);
        transition: all 500ms;
      }

      .back-exit {
        opacity: 1;
        transform: translateX(0);
      }

      .back-exit-active {
        opacity: 0;
        transform: translate(100%);
        transition: all 500ms;
      }
      .replace-enter >div:first-child{
        opacity: 0.01;
      }

      .replace-enter-active >div:first-child{
        opacity: 1;
      transition: opacity 250ms ease-in;
      }

      .replace-exit >div:first-child{
        opacity: 1;
      }

      .replace-exit-active >div:first-child{
        opacity: 0.01;
        transition: opacity 250ms ease-in;
      }
}

入口JS

import 'bootstrap/dist/css/bootstrap.min.css';
import 'font-awesome/css/font-awesome.min.css';// shareui-font版本迁移完成之后删除该依赖
import '@share/shareui-html';
import '@share/shareui-font';

import '@/components/form';

import dva from 'dva';
import router from './router';

// 1. Initialize
const app = dva();

// 2. Plugins
// app.use({});

// 3. Model
// app.model();
// 4. Router
app.router(router);

// 5. Start
app.start('#root');
xianzou commented 3 years ago

底部菜单路由,不用懒加载,不然会闪一下,其他非菜单页可以使用