在手機上打開頁面時,常常會由於網絡很差致使須要較長的加載時間,若是這段時間內只是顯示一個「白板」,用戶體驗很是很差。一般的解決方案就是完整打開頁面前給用戶展現一個加載動畫,讓用戶可以看到頁面還活着呢。本覺得是個很簡單的活,真作起來才發現【有很多學問】!css
一、不加載資源文件
順序:加載、解析、渲染、繪製html
二、加載CSS
加載外部的CSS文件會阻止渲染,不論link的標籤放在什麼位置上。
順序:加載、解析、加載(阻塞了渲染)、渲染、繪製瀏覽器
三、加載JS
a、在body前加載外部的JS文件會阻止渲染
順序:加載、解析、渲染(?)、加載(阻塞)、解析(JS執行完多了個解析)、渲染、繪製網絡
b、在body後加載外部的JS文件不會阻止渲染
順序:加載、解析、渲染、繪製、加載、解析、渲染、繪製app
四、在<script>標籤中動態建立link標籤加載CSS
加載CSS中斷了頁面的渲染和繪製框架
五、在<script>標籤中經過setTimeout函數動態建立link標籤加載CSS
加載CSS沒有中斷。我理解這是由於JS是單線程的,放在timeout裏建立的link去排隊,瀏覽器就先無論它了。異步
先定好目標:儘快讓用戶看到變化,不要讓用戶覺得頁面已經不響應,再逐步加載內容。
最快的方式就是作一個空的頁面,不加載任何外部資源(包括:CSS和JS)。頁面上方加載動畫的CSS定義和頁面元素,提供異步加載頁面元素、CSS和JS文件的JS。經過JS加載各種資源成功後關閉動畫效果,清除沒必要要的內容。async
這樣就來個新問題,如何實現動態加載問題?函數
外部文件的動態加載問題不少文章都深刻分析過了,簡單說,就是用異步加載,可是要考慮到各個JS文件的依賴關係問題。綜合比較了一下,決定requirejs實現動態加載。由於最近一直用angular,因此下面的代碼是require.js+angular。工具
HTML
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta content="width=device-width,user-scalable=no,initial-scale=1.0" name="viewport"> <base href='/'> <title>loading</title> <style id='loadingStyle' type="text/css"> /*省略了,這裏是loading元素轉圈的樣式,爲了快應該壓縮了再放裏面*/ </style> </head> <body> <div id="content" ng-app="app" ng-controller="ctrl"> <ul class='list-group'> <li class='list-group-item' ng-repeat="d in data"><i ng-bind="d"></i></li> </ul> </div> <div class="loading"><div class='loading-indicator'><i></i></div></div> <script src="static/js/require.js" defer async data-main="test/loading/loader.js?_=11"></script> </body> </html>
loader.js
由於angular不是AMD,因此用shim引用成angular全局變量
require.config({ paths: { "angular": "/static/js/angular.min", }, shim: { "angular": { exports: "angular" }, }, deps: ['/test/loading/app.js?_=10'] });
app.js
若是真的要動態添加樣式,建議先得到數據,把數據展示出來,在加載樣式,這樣就能讓用戶今早看到變化。
define(["angular"], function(angular) { 'use strict'; angular.module('app', []).controller('ctrl', ['$scope', '$timeout', function($scope, $timeout) { $scope.data = []; // 模擬長時間得到數據 $timeout(function() { for (var i = 0; i < 100; i++) { $scope.data.push('data:' + i); } // 模擬長時間得到樣式 $timeout(function() { var link, head; link = document.createElement('link'); link.href = "/test/loading/app.css?_=" + (new Date()).getTime(); link.rel = 'stylesheet'; link.onload = function() { var eleLoading, eleStyle; eleLoading = document.querySelector('.loading'); eleLoading.parentNode.removeChild(eleLoading); eleStyle = document.querySelector('#loadingStyle'); eleStyle.parentNode.removeChild(eleStyle); }; head = document.querySelector('head'); head.appendChild(link); }, 2000); }, 2000); }]); });
app.css
#content{color:red;}
能夠考慮頁面的佈局也動態加載,這樣用戶能夠先看見頁面的框架,而後再獲取數據填到框架中。可是尚未想到成熟的解決方案。