一個龐大的頁面, 有時咱們並不會滾動去看下面的內容, 這樣就形成了非首屏部分的渲染, 這些無用的渲染不只包括圖片還包括其餘DOM元素, 甚至一些js/css(某些js/css根據模塊請求,好比ajax), 理論上每增長一個DOM, 都會增長渲染的時間, 而且影響着頁面打開的加載速度.這時就須要一種辦法使得html, js, css實現按需加載.css
新浪, 美團, 途牛旅行網, 360網址導航, 淘寶商品詳情頁等等.查看它們的源代碼(ctrl+u), ctrl+f 搜索 textarea 關鍵字, 很容易能夠看到一些被textarea標籤包裹的HTML代碼.html
使用textarea標籤包裹HTML/JS/CSS代碼, 看成textarea的value值, 在頁面渲染的時候實際並無渲染到DOM樹上, 而是與圖片懶加載相似, 當textarea標籤出現或即將出如今用戶視野時, 將textarea中的HTML代碼取出, 用innerHTML動態插入到DOM樹中, 若有必要使用正則取出js/css代碼動態執行.前端
玉伯指出:
頁面下載完畢後, 要通過Tokenization - Tree Construction - Rendering. 要讓首屏儘快出來, 得給瀏覽器減輕渲染首屏的工做量. 能夠從兩方面入手:ajax
減小DOM節點數, 節點數越少, 意味着Tokenization, Rendering等操做耗費的時間越少.(對於典型的淘寶商品詳情頁,經測試發現, 每增長一個DOM節點, 會致使首屏渲染時間延遲約0.5ms)後端
減小腳本執行時間. 腳本執行和UI Update共享一個thread, 腳本耗的時間約少, UI Update就能愈加提早.數組
* 減小首屏DOM渲染, * 加快首屏加載速度 * 分塊加載js/css(使用於模塊區分度高的網站)
* 須要更改DOM結構 * 可能引發一些重排和重繪 * 沒有開啓js功能的用戶將看不到延遲加載的內容 * 額外性能損耗(渲染前的textarea裏面的html代碼,在服務端把html代碼保存在隱藏的textarea裏面 因此在服務端會把html代碼轉義, 尖括號等都被轉義了, 會增長服務端的壓力, 並且這個改造只是前端 的渲染, 服務器依舊是一次計算全部的數據, 輸出全部的數據. 通常使用都是由後端拼接成html字符串 而後塞入textarea標籤, 吐給前端) * 不利於SEO(在搜索引擎看來網頁也缺乏了關鍵的DOM節點, 本來信息量豐富的網頁內容被放入單個的 <textarea>裏面, 使搜索引擎認爲網頁內容貧乏, 大幅影響排名狀況)
關於美團BigRender技術的SEO解決方案:瀏覽器
若是放棄BigRender手段, 雖然能夠提高SEO效果, 但也會由於網頁打開變慢使用戶體驗受損.和技術權衡後嘗試了一種解決方案, 將原有的大量團購單連接分別置於多個<textarea>內部.測試證實有效, 搜索引擎認爲多個<textarea>構成的網頁還是信息豐富的, 排名有很是顯著的提高.服務器
ul { width: 300px; padding: 0; list-style: none; } .lazy { width: 300px; height: 168px; margin-bottom: 100px; background: #0cf; } .datalazyload { width: 300px; height: 168px; }
<ul> <div class=""> <li class="lazy"></li> </div> <div class=""> <li class="lazy"></li> </div> <div class=""> <li class="lazy"></li> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #333;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #444;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #555;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #666;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #777;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #888;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #999;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #ccc;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #bbb;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> <div class="loading"> <textarea class="datalazyload" style="visibility: hidden;"> <style>.lazy {width: 300px; height: 168px; background: #aaa;}</style> <script>console.log('eval success');</script> <li class='lazy'></li> </textarea> </div> </ul>
;(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} 延遲加載元素集合(數組) 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 + "[^>]*>([\\S\\s]*?)</" + 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, ">").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("<script[^>]*>([\\S\\s]*?)</script\\s*>", "img"), "") .replace(new RegExp("<style[^>]*>([\\S\\s]*?)</style\\s*>", "img"), ""); if (matchStyles.length) { for (var i = matchStyles.length; i--;) { this.evalStyles(matchStyles[i]); } } // 若是延遲部分須要作 loading 效果 parent.className = parent.className.replace("loading", ""); if (matchScripts.length) { for (var i = 0, len = matchScripts.length; i < len; i++) { this.evalScripts(matchScripts[i]); } } }, inView: function(ele) { var top = Util.getPos(ele).y , viewVal = Util.getViewport().h , scrollVal = Util.getScrollHeight() , eleHeight = Util.getEleSize(ele).h; if (top >= scrollVal - eleHeight - this.threshold && top <= scrollVal + viewVal + this.threshold) { return true; } return false; }, pollTextareas: function() { // 需延遲加載的元素已經所有加載完 if (!this.els.length) { Util.removeEvent(window, "scroll", this.fn); Util.removeEvent(window, "resize", this.fn); Util.removeEvent(doc.body, "touchMove", this.fn); return; } // 判斷是否須要加載 for (var i = this.els.length; i--; ) { var ele = this.els[i]; if (!this.inView(ele)) { continue; } this.insert(ele); this.els.splice(i, 1); } }, init: function(config) { var cls = config.cls; this.threshold = config.threshold ? config.threshold : 0; this.els = Array.prototype.slice.call(Util.getElementsByClassName(cls)); this.fn = this.pollTextareas.bind(this); this.fn(); Util.addEvent(window, "scroll", this.fn); Util.addEvent(window, "resize", this.fn); Util.addEvent(doc.body, "touchMove", this.fn); } }; win['datalazyload'] = Datalazyload; })(window, document); // demo: datalazyload.init({ cls: "datalazyload", // 須要延遲加載的類,即 textarea 的類名 threshold: 100 // 距離底部多高,進行延遲加載的閾值 });
參考原文app