React SPA应用中丝滑般的过场动画

引言


这是个人处女做品,也是构思了好久,不知如何下手🙄。那就先在引言部分说说为何要开始写文章吧。javascript

我看掘金也有较长时间了,刷到过不少优秀的文章,不单单补充了本身的知识盲区,并且还掌握了不少知识细节,可是咱们这样子获取的知识都是碎片化的,实际场景中去解释的时候,会发现碎片拼凑有些困难,甚至没法顺畅的解释一些细节知识,这点我我的深有体会,因而决定开始经过写文章的方式来锻炼一下,写一篇文章很花时间,写一篇好的文章更是要花时间,写一篇文章会逼迫本身去了解这个知识的细节点,只有本身先了然于心,理解消化后,写出来的东西才会有读者接受。html

废话很少说,下面直接开始。html5

效果图

pageanimation

知识准备

  1. react-transition-groupjava

  2. window hashchange事件react

如何准确的把握用户或者开发人员的跳转方式

做为开发人员,咱们代码逻辑里面跳转到下一个页面或者回退到上一个页面的时候,会有好几种方式,而做为用户,其实只有浏览器的前进后退(不管是左右滑屏仍是安卓物理键,最终表现为浏览器的前进后退),总结了如下表格git

动做 方式 动画
前进 react-router结合history.js的原生api:props.history.push\props.history.replace; window.location.href; window.location.replace;go(n); browser forward; 自右向左
后退 go(n)/goback(); browser back; 自左向右

接下来是重点,如何在用户点击或者程序执行的时候,提早知道页面正确的过场动画呢?首先,咱们要知道react-router的router render方法,另外咱们要了解window hashchange,以下表格统计了俩个事件与页面组件执行顺序。github

动做
props.history.push\props.history.replace router render hashchange
window.location.href; window.location.replace;go(n)/goback(); browser back; browser forward; hashchange router render

对于咱们代码层面,props.history.push\replace 彻底能够作到自控,统一书写规范,所有使用props.history的api,可是若是在一个旧的项目里面增长过场动画,你会发现,页面跳转基本经过 window.location 的api。注意表格的第二行,先触发hashchange事件的动做,既有前进的方法,也有后退的方法,咱们怎么区别呢?api

本地维护一个historyStack,只记录hash值,由于若是业务状况复杂的话,不少参数会经过url上动态变化来控制,因此若是直接存所有url的话,达不到咱们的目的。既然提到业务复杂会动态改变url的参数,咱们能够经过props.history.replace的方式完成,这种方式会触发router render,因为react组件没有任何改变,因此不会引发dom更新,这个方法是可行的。可是我的更加推荐使用 html5 history的 replaceState、pushState的方式去动态改变url的参数,俩个方法不会引发任何render或者hashchange事件,可是由于咱们本地维护了historyStack,因此咱们须要对这俩个api进行改造。具体看如下代码:浏览器

import React from 'react'
import ReactDOM from 'react-dom'
import { HashRouter, Switch, Route } from 'react-router-dom'
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { createBrowserHistory } from 'history'

import Page1 from 'containers/Page1'
import Page2 from 'containers/Page2'
import Page3 from 'containers/Page3'
import Page4 from 'containers/Page4'

// 一个不许确的 appHistoryStack,不对外暴露接口,不能当作历史记录参考
const setStorage = true
const appHistoryStack = setStorage && sessionStorage.getItem('appHistoryStack') && JSON.parse(sessionStorage.getItem('appHistoryStack')) || [getPathname(window.location.href)]
const replaceState = history.replaceState
const pushState = history.pushState
let appAction = 'FORWARD'
let onAnimation = false
let animationClassName = ''

function getPathname(url) {
  if (url.indexOf('#/') !== -1) {
    const hash = url.split('#/')[1]
    return hash.split('?')[0]
  }
  return window.location.pathname
}

history.replaceState = function() {
  setTimeout(() => {
    const newPathname = getPathname(window.location.href)
    appHistoryStack.splice(appHistoryStack.indexOf(newPathname), 1, newPathname)
  }, 0)
  replaceState.apply(history, arguments)
}
history.pushState = function() {
  setTimeout(() => appHistoryStack.push(getPathname(window.location.href)), 0)
  pushState.apply(history, arguments)
}

window.addEventListener('hashchange', (HashChangeEvent) => {
  const { newURL, oldURL } = HashChangeEvent
  const newURLPathname = getPathname(newURL)
  const oldURLPathname = getPathname(oldURL)
  if (newURLPathname !== oldURLPathname) {
    const newURLIndex = appHistoryStack.indexOf(newURLPathname)
    const oldURLIndex = appHistoryStack.indexOf(oldURLPathname)
    if (newURLIndex === -1) {
      appHistoryStack.push(newURLPathname)
    }
    if (newURLIndex === -1 || newURLIndex - oldURLIndex > 0) {
      appAction = 'FORWARD'
    } else {
      appAction = 'GOBACK'
    }
  } else {
    appHistoryStack.splice(newURLPathname, 1, newURLPathname)
  }
})

ReactDOM.render((
  <HashRouter history={createBrowserHistory()}>
    <Route render={({ location, history }) => {
      if (['PUSH', 'REPLACE'].includes(history.action)) {
        animationClassName = onAnimation && animationClassName ? animationClassName : 'slide-left'
      } else {
        animationClassName = appAction === 'FORWARD' ? 'slide-left' : 'slide-right'
      }
      return (
        <TransitionGroup className={animationClassName}>
          <CSSTransition
            key={location.pathname}
            classNames="animation"
            timeout={304}
            onEnter={() => {
              onAnimation = true
            }}
            onEntered={() => {
              onAnimation = false
              sessionStorage.setItem('appHistoryStack', JSON.stringify(appHistoryStack))
            }} >
            <Switch location={location}>
              <Route exact path="/page1" component={Page1}/>
              <Route exact path="/page2" component={Page2}/>
              <Route exact path="/page3" component={Page3}/>
              <Route exact path="/page4" component={Page4}/>
            </Switch>
          </CSSTransition>
        </TransitionGroup>
      )
    }}/>
  </HashRouter>
), document.getElementById('app'))
复制代码

具体源码地址:git地址session

相关文章
相关标签/搜索