摘要:首屏渲染優(yōu)化背景一個龐大的頁面有時我們并不會滾動去看下面的內(nèi)容這樣就造成了非首屏部分的渲染這些無用的渲染不僅包括圖片還包括其他元素甚至一些某些根據(jù)模塊請求比如理論上每增加一個都會增加渲染的時間并且影響著頁面打開的加載速度這時就需要一種辦法使
BigRender首屏渲染優(yōu)化 背景
一個龐大的頁面, 有時我們并不會滾動去看下面的內(nèi)容, 這樣就造成了非首屏部分的渲染, 這些無用的渲染不僅包括圖片還包括其他DOM元素, 甚至一些js/css(某些js/css根據(jù)模塊請求,比如ajax), 理論上每增加一個DOM, 都會增加渲染的時間, 并且影響著頁面打開的加載速度.這時就需要一種辦法使得html, js, css實現(xiàn)按需加載.
案例新浪, 美團, 途牛旅行網(wǎng), 360網(wǎng)址導航, 淘寶商品詳情頁等等.查看它們的源代碼(ctrl+u), ctrl+f 搜索 textarea 關(guān)鍵字, 很容易可以看到一些被textarea標簽包裹的HTML代碼.
原理使用textarea標簽包裹HTML/JS/CSS代碼, 當作textarea的value值, 在頁面渲染的時候?qū)嶋H并沒有渲染到DOM樹上, 而是與圖片懶加載類似, 當textarea標簽出現(xiàn)或即將出現(xiàn)在用戶視野時, 將textarea中的HTML代碼取出, 用innerHTML動態(tài)插入到DOM樹中, 如有必要使用正則取出js/css代碼動態(tài)執(zhí)行.
優(yōu)點玉伯指出:
頁面下載完畢后, 要經(jīng)過Tokenization - Tree Construction - Rendering. 要讓首屏盡快出來, 得給瀏覽器減輕渲染首屏的工作量. 可以從兩方面入手:減少DOM節(jié)點數(shù), 節(jié)點數(shù)越少, 意味著Tokenization, Rendering等操作耗費的時間越少.(對于典型的淘寶商品詳情頁,經(jīng)測試發(fā)現(xiàn), 每增加一個DOM節(jié)點, 會導致首屏渲染時間延遲約0.5ms)
減少腳本執(zhí)行時間. 腳本執(zhí)行和UI Update共享一個thread, 腳本耗的時間約少, UI Update就能越發(fā)提前.
* 減少首屏DOM渲染, * 加快首屏加載速度 * 分塊加載js/css(使用于模塊區(qū)分度高的網(wǎng)站)缺點
* 需要更改DOM結(jié)構(gòu) * 可能引起一些重排和重繪 * 沒有開啟js功能的用戶將看不到延遲加載的內(nèi)容 * 額外性能損耗(渲染前的textarea里面的html代碼,在服務(wù)端把html代碼保存在隱藏的textarea里面 所以在服務(wù)端會把html代碼轉(zhuǎn)義, 尖括號等都被轉(zhuǎn)義了, 會增加服務(wù)端的壓力, 而且這個改造只是前端 的渲染, 服務(wù)器依舊是一次計算所有的數(shù)據(jù), 輸出所有的數(shù)據(jù). 一般使用都是由后端拼接成html字符串 然后塞入textarea標簽, 吐給前端) * 不利于SEO(在搜索引擎看來網(wǎng)頁也缺少了關(guān)鍵的DOM節(jié)點, 原本信息量豐富的網(wǎng)頁內(nèi)容被放入單個的SEO解決方案
關(guān)于美團BigRender技術(shù)的SEO解決方案:
如果放棄BigRender手段, 雖然可以提升SEO效果, 但也會因為網(wǎng)頁打開變慢使用戶體驗受損.和技術(shù)權(quán)衡后嘗試了一種解決方案, 將原有的大量團購單鏈接分別置于多個
BigRender 完整示例: css:ul { width: 300px; padding: 0; list-style: none; } .lazy { width: 300px; height: 168px; margin-bottom: 100px; background: #0cf; } .datalazyload { width: 300px; height: 168px; }html:
;(function(win, doc) { // 兼容低版本 IE Function.prototype.bind = Function.prototype.bind || function(context) { var that = this; return function() { return that.apply(context, arguments); }; }; // 工具方法 begin var Util = { getElementsByClassName: function(cls) { if (doc.getElementsByClassName) { return doc.getElementsByClassName(cls); } var o = doc.getElementsByTagName("*"), rs = []; for (var i = 0, t, len = o.length; i < len; i++) { (t = o[i]) && ~t.className.indexOf(cls) && rs.push(t); } return rs; }, addEvent: function(ele, type, fn) { ele.attachEvent ? ele.attachEvent("on" + type, fn) : ele.addEventListener(type, fn, false); }, removeEvent: function(ele, type, fn) { ele.detachEvent ? ele.detachEvent("on" + type, fn) : ele.removeEventListener(type, fn, false); }, getPos: function(ele) { var pos = { x: 0, y: 0 }; while (ele.offsetParent) { pos.x += ele.offsetLeft; pos.y += ele.offsetTop; ele = ele.offsetParent; } return pos; }, getViewport: function() { var html = doc.documentElement; return { w: !window.innerWidth ? html.clientHeight : window.innerWidth, h: !window.innerHeight ? html.clientHeight : window.innerHeight }; }, getScrollHeight: function() { html = doc.documentElement, bd = doc.body; return Math.max(window.pageYOffset || 0, html.scrollTop, bd.scrollTop); }, getEleSize: function(ele) { return { w: ele.offsetWidth, h: ele.offsetHeight }; } }; // 工具方法 end var Datalazyload = { threshold: 0, // {number} 閾值,預加載高度,單位(px) els: null, // {Array} 延遲加載元素集合(數(shù)組) fn: null, // {Function} scroll、resize、touchmove 所綁定方法,即為 pollTextareas() evalScripts: function(code) { var head = doc.getElementsByTagName("head")[0], js = doc.createElement("script"); js.text = code; head.insertBefore(js, head.firstChild); head.removeChild(js); }, evalStyles: function(code) { var head = doc.getElementsByTagName("head")[0], css = doc.createElement("style"); css.type = "text/css"; try { css.appendChild(doc.createTextNode(code)); } catch (e) { css.styleSheet.cssText = code; } head.appendChild(css); }, extractCode: function(str, isStyle) { var cata = isStyle ? "style" : "script", scriptFragment = "<" + cata + "[^>]*>([Ss]*?)" + cata + "s*>", matchAll = new RegExp(scriptFragment, "img"), matchOne = new RegExp(scriptFragment, "im"), matchResults = str.match(matchAll) || [], ret = []; for (var i = 0, len = matchResults.length; i < len; i++) { var temp = (matchResults[i].match(matchOne) || [ "", "" ])[1]; temp && ret.push(temp); } return ret; }, decodeHTML: function(str) { return str.replace(//g, ">").replace(/&/g, "&"); }, insert: function(ele) { var parent = ele.parentNode, txt = this.decodeHTML(ele.innerHTML), matchStyles = this.extractCode(txt, true), matchScripts = this.extractCode(txt); // console.log(txt) console.log(matchStyles); console.log(matchScripts); parent.innerHTML = txt .replace(new RegExp("