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

相關文章
相關標籤/搜索