通過一個月的時間,在我遇到了不少「這個我不會作啊?」,「這個到底怎麼作「的問題後,它終於成功上線了!下面總結一下整整一個月的時間我是如何開發 JDRD,遇到的各類問題以及解決方案。
JDR DESIGN 是京東零售設計中臺的門戶站點,展現京東零售設計服務平臺的產品以及應用場景,特色是動效豐富、圖片細節多、要求整站文案和外鏈可配置。項目最大的困難就是動效開發複雜和開發排期緊湊的問題。css
這是我入職以來負責的第一個項目,須要花大量時間來熟悉新的開發流程,項目排期很是緊湊,而且在排期完後又新增了窄版、骨架屏、首頁圖標動效、入場動畫、產品頁頭部動效等新的需求,天天高強度的加班,回想起來雖然很難,可是很是有挑戰性,很是有收穫。html
做爲一個 9012 年的 PC 端項目,咱們天然也須要很是先進的技術選型來幫助咱們提高研發生產力,因此一個優秀的前端框架和一個高效的前端工程化工具,天然是必不可缺的選擇。選擇團隊自研的 Nerv 進行開發,Nerv 是一個基於 Virtual DOM
的類 React 組件框架,比 React 更小的體積更高的性能,還保持了對 IE 瀏覽器的兼容,知足了 JDRD 須要兼容 IE10 的要求。前端
自動化前端構建工具選擇了團隊自研的前端工程化工具 Athena,簡化 Webpack 配置工做,幫助咱們在項目中實現自動化編譯、代碼處理、依賴分析、文件壓縮、文件 MD5 戳等需求。webpack
在前端架構方面,根據上述的技術選型以及常見的前端體系,基於本項目的需求進行了些調整,總體架構設計以下:css3
Athena 和 Nerv 上文已經介紹過,下面介紹一下另外幾個:git
Nerv-loadable
等。兜底展現:github
整個開發流程總結以下圖所示:web
既然是總結,開發過程當中的流程固然沒有這麼完整,漏掉了一部分(已紅色標註),致使開發到後面由於前面漏掉的環節,浪費了不少的時間。npm
數據埋點:交互稿其實有詳細介紹哪些地方須要點擊跳轉的事件,開發時應該在定義點擊跳轉時就給數據埋點傳參。json
除了在開發流程中的問題外,最頭疼的就是動效的開發了,下文會詳細介紹。
性能優化的初衷就是加快網站的加載速度,讓用戶可以更快的看到內容,上面介紹到前端工程化工具 Athena 已經作到合併、壓縮了靜態資源文件,那還有什麼方法可以縮小請求的靜態資源體積,加快首屏的加載速度呢,咱們嘗試瞭如下性能優化手段。
樓層懶加載就是按樓層劃分組件,並進行代碼切割,在頁面滾動時按需加載組件。
Nerv-loadable
是一個專門用於動態 import 的 React 高階組件,你能夠把任何組件改寫爲支持動態 import 的形式,利用 import()
來進行動態加載。
const NewsBannerLoadable = Loadable({ loader: () => import(/* webpackChunkName: "news_banner" */ './news_banner'), loading: loadingPlaceholder.bind(null, loadingBlock), delay: 0 });
上面的代碼在首次加載時,會先展現一個 loadingBlock
,而後動態加載 news_banner
的代碼,組件代碼加載完畢以後,便會替換掉 loadingBlock
。
Lazyload 經過監聽 window 對象或者父級對象的 scroll 事件,觸發 load,實現懶加載,讓組件進入頁面可視區時才加載該組件。須要注意的是 lazyload
須要設置高度,纔會撐起懶加載的區域。
<Lazyload {...this.lazyloadOptions} height={this.floorHeight.newsBanner}> <NewsBannerLoadable /> </Lazyload>
以首頁爲例,有四處組件是不須要首次加載的,而是使用動態加載:多端適配、物料、應用場景、設計思考。首次加載實際上只須要加載首屏的頭部、視頻、banner 便可。切分以後,首屏 js 體積縮減了 50KB。
整站圖片很是多,爲了保持清晰度並且所有采用二倍圖引入,消耗資源比較大,爲了加快加載速度,咱們選擇讓滾動條滾動到圖片的可視區後才加載該圖片。
使用 Lazyload 實現,和上述組件懶加載介紹的同樣,<Lazyload></Lazyload>
包裹着須要懶加載的圖片,就能夠實現圖片懶加載。
圖片懶加載以外還有個優化,就是圖片加載中、加載失敗、加載成功的狀態的判斷,根據不一樣狀態展現圖片的內容。
使用 Lazyimg 實現這個功能:
new Image()
建立一個新的 HTMLImageElement 實例img.onload()
,img.onerror()
捕獲到圖片加載成功或者失敗的狀態
除了上面兩點外,還能夠從 Webpack 打包進行性能優化,Webpack 打包後會生成一個或多個包含源代碼最終版本的「打包好的文件」,它們由 chunks 組成,SplitChunks 插件能夠將公共依賴項提取到現有的 entry chunk
或全新的代碼塊中,進行代碼切割,減少 chunks 包的大小。
以往的傳統網站通常會在加載中展現一個 loading 態,也能夠達到佔位的效果,可是 loading 動畫和真實模塊耦合度低,界面效果不夠優美,JDRD 則是選用骨架屏進行佔位,以灰色豆腐塊的形式儘可能縮小真實模塊結構與加載佔位之間的視覺差別。
骨架屏的兩個用途:
使用 Lazyload 懶加載樓層組件,加載中使用 Loadable
提早佔位,佔位符設置爲骨架屏。
設置組件的 state.loaded
初始值爲 false
,數據加載成功時 state.loaded = true
,render
函數裏若是 loaded === false
,則顯示骨架屏。
骨架屏的實現方式有兩種,一是下載並引入骨架屏插件(如 antd ),根據不一樣模塊引入對應的骨架屏組件,這種方式和 loading 動畫同樣,耦合度低,可是全局通用,節省代碼量。二是根據視覺稿寫骨架屏的樣式。JDRD 選擇的是第二種,骨架屏和真實模塊實現高度耦合。每一個頁面結構不同,對應的骨架屏也是徹底不一樣,骨架屏暫時不能抽成公共組件全局通用。
首頁定稿設定的寬度爲 1240px ,對小屏不夠友好,咱們增長了一版窄版樣式兼容小屏。
寬版和窄版開發的重點是定好通用的變量,包括字號粗細、寬窄版寬度、窄版尺寸比。這些通用樣式規範須要和設計師統一規範,兼容窄版的開發就會變得很是簡單。
只須要在兩個地方判斷寬窄版,給最頂層的標籤加上 wide/narraw 類
,在 narrow 下添加窄版的自定義樣式。
wide/narraw
!function(e) { window.pageConfig = {}; pageConfig.isWide = function() { var n = e, i = document, o = i.documentElement, t = i.getElementsByTagName("body")[0], a = n.innerWidth || o.clientWidth || t.clientWidth; return a >= 1300 } (); var n = []; pageConfig.isWide ? (n.push("wide")) : n.push("narrow"); var i = document.getElementsByTagName("html")[0]; i.className = n.join(" ") } (window, void 0);
先引入 Events.js ,而後在 componentDidMount
裏生命週期函數裏綁定 isWideChange
事件,在文檔視圖寬度達到寬窄版臨界點時調用。
componentDidMount() { window._.eventCenter.on('isWideChange', evt => { this.setState({//更新state,更新視圖 isWide: evt.detail.isWide }); }); }
爲了突出設計理念,首頁圖標動效包含大量位移、旋轉、縮放、形變、路徑動畫等細節,由始末動畫+循環動畫合成,傳統作法是 CSS3 實現,這須要逐幀寫動畫細節,工做量很是大,咱們嘗試使用 Lottie 直接解析從 AE 導出的 json 格式的動畫(方案由燕婷提出),發現可以徹底還原AE動畫。
Lottie 是 Airbnb 開源的一套跨平臺的完整的動畫效果解決方案,可實時渲染 After Effects
動畫,從而使應用程序能夠像使用靜態圖像同樣輕鬆地使用動畫。這樣實現起來就很是簡單了。分如下兩步:
lottie-web
文檔中的方法很是全面,JDRD 圖標動效使用加載動畫、播放指定幀區間、反向播放動畫方法,就實現了起始動畫 20 幀 + 循環動畫 60 幀 + 起始動畫反向播放 20 幀的動畫合成操做。
項目示例代碼以下:
npm install lottie-web //安裝lottie-web import lottie from 'lottie-web' //引入lottie-web到項目中 //lottie-web經常使用方法 this.anim = lottie.loadAnimation({ //加載動畫 container: element, renderer: 'svg', loop: true, autoplay: true, path: 'data.json' }); this.anim.playSegments([[0,60]], true); //播放指定幀區間 this.anim.setDirection(-1);//動畫反向播放 this.anim.play();//播放動畫 this.anim.pause()//暫停動畫
通過以上兩步,Lottie 已經將一個 AE 格式的動畫渲染在 Web 頁面上。
這裏有 2 個須要注意的點:
json+img
。圖片動畫的兼容性有待確認。以上就是用 Lottie 實現的動畫,看到這裏,是否是以爲 so easy,可是 Lottie 並非萬能的,不能解析全部的動畫特性,開發前須要先看下支持列表。並和設計師確認是否都支持。
JDRD 整站採用了骨架屏佔位,那麼入場動畫最大的問題就是如何讓它不和骨架屏衝突,解決方法就是樓層懶加載裏面,再加一層入場動畫的組件懶加載,兩層懶加載的設置 offset 差,就能夠作到在可視區外加載樓層組件,在可視區內播放入場動畫。具體實現以下:
// 在距離底部200px時,加載樓層組件 getMaterialLoadable(){ return this.getFloor( <Lazyload lazyloadOptions={offset: 200} height={1000}> <MaterialLoadable /> </Lazyload> ); }
// 在距離底部-200px時,加載入場動畫組件,這時由於樓層組件已經加載過了,頁面顯示是真實組件而不是骨架屏 <Lazyload lazyloadOptions={offset: -200}> <div className="w"> <IndexTitle showLine={true} title={this.state.title}></IndexTitle> {this.renderMaterial()} </div> </Lazyload>
另一個難點是序列動畫的效果,序列動畫就是將列表元素的動畫執行時機錯開,具體實現參考css3 animation 屬性衆妙。實現代碼以下:
@for $i from 1 to 6 { .list__item:nth-child(#{$i}) { animation-delay: (-1+$i)*0.1s; /*計算每一個元素的 animation-delay */ } }
產品頁的頭部動效分兩部分,氛圍動效 + 波浪動效。
氛圍動效的實現比較簡單,也是使用 Lottie 實現,這裏遇到了 Lottie 不支持的特性,就是漸變,對於不支持的特性,咱們能夠拿到須要自定義樣式的標籤的 id,自定義樣式,如圖所示:
#__lottie_element_369 { stop[offset="0%"] { stop-color: #FDFDFF; } stop[offset="100%"] { stop-color: #F7F7FB; } }
自定義樣式的時候,這裏有個坑必定要注意,SVG 的 ID 是會變的,例如開發時,這個 ID 是 100,測試時這個 ID 有可能變成 101,這個是偶現的,目前尚未找到 ID 值 + 1 的緣由,可是爲了讓自定義的樣式生效,須要給 ID 爲 100 和 ID 爲 101 的標籤都加上樣式。
經過引入正弦波浪動效庫 sine_wave 實現。sine_wave 使用 canvas 元素生成多個可配置的正弦波,這樣咱們就能夠經過配置參數獲得想要的正弦波浪,具體實現以下:
new SineWaves({ el: document.getElementById( `waves`),//dom speed: 0.75,//速度 width: function() {//canvas寬度 return document.body.clientWidth; }, height: 68,//canvas高度 ease: 'Linear',//動畫曲線 waves: [//須要配置的正弦波浪 { "timeModifier":1,//速度 "lineWidth":1,//線條寬度 "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波長 "strokeStyle":"rgba(221,221,233,1)",//顏色 "type": function(x, waves) {//自定義波浪類型 return waves.sine(x); // Combine two together } } ], rotate: 0,//旋轉角度 wavesWidth: '400%',//波浪寬度 });
根據文檔介紹的參數來看,有兩個參數是實現 3D 旋轉波浪效果的關鍵:
type: function(x, waves) { return waves.sine(x+8); // Combine two together }
開發過程當中發現 sine_waves 的一個顯示問題,它以默認二倍屏的方式定義的 canvas,這樣在一倍屏下波浪是有問題的,解決方法是在參數波浪高度 amplitude
和波長 wavelength
根據 window.devicePixelRatio
來定義。
{ "amplitude":30 * window.devicePixelRatio,//波浪高度 "wavelength":125 * window.devicePixelRatio,//波長 }
最後介紹的就是全局細節動效的優化,爲了讓整站的動效流暢,平滑的過渡,作了如下工做:
$common_animation
管理通用動畫,讓整站動畫一致化visibility
代替 display
控制元素的顯示隱藏本文從項目架構、開發流程、項目優化 3 個方面闡述 JDRD 官網的開發過程,中間遇到了太多問題,在問題的解決過程當中記錄和總結,是收穫滿滿的喜悅,也發現了一些能夠優化的模塊,讓下次可以作得更好,在開發過程當中的多些思考和探究,最優化的設計項目。
感謝閱讀!
歡迎關注凹凸實驗室博客:aotu.io
或者關注凹凸實驗室公衆號(AOTULabs),不定時推送文章: