一个庞大的页面, 有时咱们并不会滚动去看下面的内容, 这样就形成了非首屏部分的渲染, 这些无用的渲染不只包括图片还包括其余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