require.context
預加載圖片詳情文檔地址css
項目中爲了確保頁面顯示時,圖片已經所有加載完畢,所以須要提早加載圖片,加載圖片的過程使用進度條顯示。html
在webpack構建的項目中,可使用require.context
來獲取到靜態資源的地址。語法以下:node
require.context(directory, useSubdirectories = false, regExp = /^\.\//)
第一個參數表示要搜索的文件夾目錄,該目錄支持相對路徑與在配置文件中定義的路徑別名。
第二個參數表示是否搜索其子目錄。
第三個參數是一個用來匹配文件的正則表達式。react
require.context('modules/App', true, /\.(png|jpg|jpeg|gif)$/); // 建立一個包含App目錄下全部圖片的上下文模塊
可使用該上下文模塊自帶的keys方法獲得路徑組成的數組。webpack
const images = require.context('modules/App/', true, /\.(png|jpeg|jpg|gif)$/); console.log(images.keys());
效果大概以下圖所示。git
獲得圖片路徑以後,就能夠藉助Promise.all
來完成圖片預加載,確保圖片加載完成以後再渲染頁面。github
Promise.all(images.keys().map(path => { const image = new Image(); image.src = path; image.onload = image.onerror = () => { resolve(); } }))
可是在開發中遇到一個問題,本地頁面引用的圖片是編譯事後的圖片地址,並非相對路徑,所以若是直接這樣的話會所以地址不一致而報錯。web
解決辦法是在設置image對象src屬性時,修改以下:正則表達式
image.src = images(path); // images 是由require.context 建立的上下文模塊
打印出images(path)
以後的圖片路徑以下:api
上面的修復方式可使用以下的知識點來理解。
const ctx = require.context('modules/App', true, /*\.js/); const table = ctx('./table.js'); // 上面的代碼等價於 const table = require('modules/App/table.js'); // 使用require引入模塊
當還須要從服務端提早加載其餘資源時,可使用數組的concat
方法一塊兒放入Promise.all
中。
Promise.all(images.keys().map( // ... ).concat(http.get('/api/v1/summary')))
整個頁面的顯示,一共有15頁構成,因爲每一頁的邏輯與效果都有很多差別,所以將每一頁定義爲了一個組件,最初在引入這些模塊時很糟糕的這樣作:
import Page00 from './Page00'; import Page01 from './Page01'; import Page02 from './Page02'; import Page03 from './Page03'; import Page04 from './Page04'; import Page05 from './Page05'; import Page06 from './Page06'; import Page07 from './Page07'; import Page08 from './Page08'; import Page09 from './Page09'; import Page10 from './Page10'; import Page11 from './Page11'; import Page12 from './Page12'; import Page13 from './Page13'; import Page14 from './Page14'; // render裏也很複雜 // ... render() { return ( <Fragment> <Page00 /> <Page01 /> <Page02 /> ... <Page14 /> </Fragment> ) }
當組件更多時,這樣的引入方式天然是不合理的,可使用循環的方式來引入代碼,優化以下:
const allPages = []; for(let i = 0; i < 15; i++) { const id = `0${i}`.slice(-2); allPages.push(require(`./Page${id}`).default) }
這樣就將全部的Page組件放在了allPages
數組中。
render裏也可使用map來渲染。
render() { return ( <div className="pages"> {allPages.map(({ id, Component: Page }) => <Page key={id} {...other} />)} </div> ) }
每個Page組件中,都有共同的元素或邏輯,包括logo,分享當前屏幕截圖按鈕,統計邏輯,判斷對應頁面是否顯示等。能夠將這些共用邏輯使用高階組件來處理以簡化代碼。
所以定義了withBox
組件來處理它們。
import React from 'react'; import logo from './images/logo.png'; import { sendEvent } from 'utils/track'; import share from './share'; export default function(Wrapped, checkProp) { return class NewPage extends React.Component { shareScreen = () => { const id = this.refs.box.getAttribute('data-page-id'); this.refs.box.classList.add('will-screenshot'); setTimeout(() => share.shareScreenshot(), 100); setTimeout(() => this.refs.box.classList.remove('will-screenshot'), 1500); sendEvent('share-click', 'page' + id); sendEvent('click', 'share-btn'); }; render() { const { id, className, ...props } = this.props; const cls = className ? `page${id} ${className}` : `page${id}`; if (!checkProp || (props.info[checkProp] !== null && props.info[checkProp] !== 'undefined')) { return ( <section className={cls} data-page-id={id} ref="box"> <Wrapped {...props} /> <button className="share-btn aninode fadeIn" onClick={this.shareScreen} /> <img className="logo aninode fadeIn" src={logo} alt="tigerbrokers" /> </section> ); } return null; } }; }
首先定義一個class以下,將會參與動畫的元素(或其父級)都添加該class以隱藏。
.aninode { visibility: hidden; }
並在同元素(或父級)添加了animated
時,元素顯示。
.animated { &.aninode, .aninode { visibility: visible; } }
並在運動元素的class中添加了animated
時,運動生效,所以定義運動css時,應該這樣作:
.animated { &.flyTopIn, .flyTopIn { animation-name: flyTopIn; animation-duration: 1s; } /* more */ }
所以,運動元素在運動開始以前,應該保持這樣
<div class="test aninode flyTopIn"></div>
須要運動時,在該元素的class中添加animated
便可。
<div class="test aninode flyTopIn animated"></div> // or <div class="animated"> <div class="test aninode flyTopIn"></div> </div>
使用sass的循環語法定義delay樣式
@for $i from 0 through $delay_count { .animated .delay#{$i * 100} { animation-delay: $i * 100; animation-fill-mode: backwards; } }
js的計算中,常常會遇到小數精度的問題,最初沒有注意,致使數據顯示出了不少問題。例如以下計算結果
1.099 * 100 109.89999999999999
解決方法以下:
(1.099 * 100).toFixed(2)
利用setTimeout
判斷某個對象是否注入成功。
// 錯誤寫法 export const checkSDK = () => { var timer = null; const start = Date.now(); return new Promise((resolve, reject) => { if (typeof window.TigerBridge === 'object') { resolve(); return; } if (Date.now() - start <= 5 * 1000) { clearTimeout(timer); timer = setTimeout(checkSDK, 100); return; } reject(); }) } // 正確寫法 export const checkBridge = () => { var timer = null; const start = Date.now(); function check(resolve, reject) { if (typeof window.TigerBridge === 'object') { resolve(); return true; } if (Date.now() - start <= 5 * 1000) { clearTimeout(timer); timer = setTimeout(check.bind(null, resolve, reject), 100); return; } reject(); return false; } return new Promise((resolve, reject) => check(resolve, reject)) }
本地模擬注入過程
if (process.env.NODE_ENV != 'production') { setTimeout(() => { window.TigerBridge = { getAccessToken: () => { return pkg.token; }, isAccountPermissionLimited: () => false }; }, 1600); }
一次性加載全部圖片會致使瀏覽器http線程阻塞嚴重。所以須要稍做優化,讓圖片一張一張加載。
// 優化前 images.keys().map(path => new Promise(resolve => { const image = new Image(); image.src = images(path); image.onload = image.onerror = resolve; })) // 優化後 images.keys().reduce((cachePromise, path) => cachePromise.then(() => { return new Promise(resolve => { const image = new Image(); const complete = () => { clearTimeout(timer); resolve(); } const timer = setTimeout(complete, 1000); // 單張圖片最多加載1s image.src = images(path); image.onload = image.onerror = complete; }) }), Promise.resolve());