关于性能优化的那点事——BigRender首屏渲染优化
2017-05-12 11:47
232 查看
背景
一个庞大的页面, 有时我们并不会滚动去看下面的内容, 这样就造成了非首屏部分的渲染, 这些无用的渲染不仅包括图片还包括其他DOM元素, 甚至一些js/css(某些js/css根据模块请求,比如ajax), 理论上每增加一个DOM, 都会增加渲染的时间, 并且影响着页面打开的加载速度.这时就需要一种办法使得html, js, css实现按需加载.
案例
新浪, 美团, 途牛旅行网, 360网址导航, 淘宝商品详情页等等.查看它们的源代码(ctrl+u),ctrl+f 搜索 textarea 关键字, 很容易可以看到一些被textarea标签包裹的HTML代码.
原理
使用textarea标签包裹HTML/JS/CSS代码, 当作textarea的value值, 在页面渲染的时候实际并没有渲染到DOM树上, 而是与图片懒加载类似, 当textarea标签出现或即将出现在用户视野时, 将textarea中的HTML代码取出, 用innerHTML动态插入到DOM树中, 如有必要使用正则取出js/css代码动态执行.玉伯指出:页面下载完毕后, 要经过Tokenization - Tree Construction - Rendering. 要让首屏尽快出来, 得给浏览器减轻渲染首屏的工作量. 可以从两方面入手:
减少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>里面, 使搜索引擎认为网页内容贫乏, 大幅影响排名情况)
SEO解决方案
关于美团BigRender技术的SEO解决方案:如果放弃BigRender手段, 虽然可以提升SEO效果, 但也会因为网页打开变慢使用户体验受损.和技术权衡后尝试了一种解决方案, 将原有的大量团购单链接分别置于多个<textarea>内部.测试证明有效, 搜索引擎认为多个<textarea>构成的网页仍是信息丰富的, 排名有非常显著的提升.
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:
<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>
js:
;(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 // 距离底部多高,进行延迟加载的阈值 });
参考原文
相关文章推荐
- 关于Unity图形渲染的性能优化
- 关于Apache的性能优化
- 关于asp.net 性能——关于数据处理相关的优化(转)
- 关于性能优化的以及int Number uint的比较
- 关于JQuery的性能优化(转摘自-痞子刘的博客)
- 关于flash性能优化的好书
- 关于性能优化
- 【转】性能优化-关于Asp.net性能的技巧
- Java性能优化[4]:关于finalize函数
- 关于系统性能优化的一些操作和泛型的应用(第二天)
- 关于jQuery性能优化的几点建议
- 关于asp.net 性能——关于数据处理相关的优化(转)
- 关于PHP性能优化
- Asp.net性能优化总结[关于数据处理相关的优化]
- 关于性能诊断和性能优化的方法和实施案例
- 关于Servlet、Jsp性能优化
- Asp.net性能优化总结[关于数据处理相关的优化]
- 关于PHP中启用Zend Optimizer后性能优化程度的简单测试
- 关于Apache的性能优化(zt)
- 性能优化-关于JQuery的性能优化