”网易聚合阅读“的前端研究【源码篇】
网易这个聚合阅读的js有很多要学习的地方,我以自己资浅的水平分析了一下,分享思路和小技巧:
源码地址:http://img1.cache.netease.com/product/tag/2.2/js/index2.js
页面地址:http://tag.163.com
【实现篇】:http://www.cnblogs.com/phpgcs/p/tag_163_frontEnd_realization.html
原创文章,转载请注明:http://www.cnblogs.com/phpgcs
主要内容:
-
javascript拼装html的封装方法实现:自定义microtemplate
-
画布的切割(核心)
-
错误处理的呈现(自己补充的)
- AutoLoader加载
- 通用的javascript技巧
内容1:首先是一个microTemplate的模板功能
在html中的模板这样写,模板结构一目了然,改起来的话也很方便:
<script id="tag_template" type="text/x-micro-template"> <% var doctitle = data.doctitle, title = doctitle || ''; %> <a href="<%=data.url%>?from=tagwall" title="<%=title%>" > <span class="inner"><%=data.name%> <% if(doctitle) { %> <span class="doc-title"><%=doctitle%></span> <% } %> </span><i></i> </a> </script>
如何通过Javascript处理上面这段模板代码实现跟数据的结合并拼装成为最终 的html呢?
<a href="http://tag.163.com/ent/116/116610.html?from=tagwall" title="" > <span class="inner">张艺谋女儿婚礼 </span><i></i> </a>
传统的做法当然是,在js中循环拼接动态的数据和自定义的Html,像这样:
var html = "<table>"; for(var i in data){ html+="<tr>"; html+="<td>"; html+=i; html+="</td>"; html+="</tr>"; ); html+="</table>"; return html;
而在这里他用了一个microTemplate的功能封装了一个方法,这个方法让我们可以直接在html上按照规则书写template,而不是像传统做法那样要对每一种模板写不同的js拼装代码
这样如果有多个template,只需要在html中修改模板即可,非常方便。
总之,这里就是用一个microTemplate的方法实现了手动拼装html过程的统一化。
来看看,microTemplate是怎么实现的:
28 this.microTemplate = function microTemplate(str, data){ 27 // Figure out if we're getting a template, or if we need to 26 // load the template - and be sure to cache the result. + 25 24 var fn = !/\W/.test(str) ? 23 cache[str] = cache[str] || 22 microTemplate(document.getElementById(str).innerHTML) : ~ 21 20 // Generate a reusable function that will serve as a template 19 // generator (and which will be cached). 18 new Function("data", 17 "var p=[];" + ~ 16 15 // Introduce the data as local variables using with(){} 14 "p.push('" + ~ 13 12 // Convert the template into pure JavaScript 11 str 10 .replace(/[\r\t\n]/g, " ") 9 .split("<%").join("\t") 8 .replace(/((^|%>)[^\t]*)'/g, "$1\r") 7 .replace(/\t=(.*?)%>/g, "',$1,'") 6 .split("\t").join("');") 5 .split("%>").join("p.push('") 4 .split("\r").join("\\'") 3 + "');return p.join('');"); ~ 2 1 // Provide some basic currying to the user 0 return data ? fn( data ) : fn; 1 };
核心代码就是 str.replace...
用正则替换 《换行符/自定义分割符》 为纯的javascript代码
经过处理之后的纯Javascript拼装函数如下:
function anonymous(data) { var p=[]; p.push(' '); var doctitle = data.doctitle,title = doctitle || ''; p.push(' <a href="',data.url,'?from=tagwall" title="',title,'" > <span class="inner">',data.name,' '); if(doctitle) { p.push(' <span class="doc-title">',doctitle,'</span> '); } p.push(' </span><i></i> </a> '); return p.join(''); }
好吧,这不就是我们传统的拼接html函数嘛。通过将固定结构的data数据传递给这个拼装函数,最终得到html:
<a href="http://tag.163.com/ent/116/116610.html?from=tagwall" title="" > <span class="inner">张艺谋女儿婚礼 </span><i></i> </a>
另外我去查了下这个 模板的MIME类型 x-micro-template...是自定义的MIME类型
text/javascript as Keparo stated must be used in html4 and xhtml1 if you want it to validate but doesnt do anything. application/javascript is expected to be the new official mime type if everyone agrees and when everything catches up. application/x-javascript (x meaning unofficial) is the current server side mime reference for javascript. everyone expects that as per usual microsoft will decide to do something completely different to further confuse and stuff up the matter. Summary: for now, if you want your html/xhtml to work in MSIE and validate with W3C then declare type="text/javascript". and if you want your web server to that you know mean javascript then use application/x-javascript.
内容2:一些通用的javascript方法:
function range(start, end) { var result = []; if(typeof end === 'undefined') { end = start; start = 0; } for(;start < end; start++) { result.push(start); } return result; } undefined range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] range(4) [0, 1, 2, 3] range(2) [0, 1]
一个array的shuffle打乱函数
Array.prototype.randomEach = function(func) { if (typeof func != "function") { throw new TypeError(); } var len = this.length, indexes = range(len); while(len) { var cursor = Math.floor(Math.random() * (len--)); if(func(this[indexes[cursor]]) === false) { break; } } };
对比下常规的shuffle函数
function shuffle(array) { var currentIndex = array.length , temporaryValue , randomIndex ; // While there remain elements to shuffle... while (0 !== currentIndex) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } var arr = [2, 11, 37, 42]; shuffle(arr); console.log(arr);shuffle(arr); console.log(arr);shuffle(arr); console.log(arr); [42, 2, 11, 37, confound: function] [2, 11, 37, 42, confound: function] [2, 37, 11, 42, confound: function]
下面还是一个shuffle函数
Array.prototype.confound = function () { this.sort(function () { return Math.random() - 0.5; }); }; a ["c", "b", "a"] a.confound(); undefined a ["b", "a", "c"]
克隆object的方法:
function _shadowClone(obj){ var result = {}; for(var key in obj){ if(obj.hasOwnProperty(key)){ result[key] = obj[key]; } } return result; }
内容3:核心的方法 Autoloader
页面的布局和tag数据都用了这个方法
//自动填充下次填充的布局 gridsAutoLoader = new AutoLoader(function() { return _getGrids(window.tagConfig); }, 1000); //自动填充下次填充的Tag数据 tagDataAutoLoader = new AutoLoader(function() { return getTagData(); }, 1000);
定义如下:
/* * AutoLoader * * _generator * _timeout * _context * _pool * * _load() * get() * generator() * */ function AutoLoader(generator, timeout) { if (typeof generator != "function") { throw new TypeError(); } this._generator = generator; this._timeout = timeout; this._context = arguments[2]; this._pool = []; this._load(); } AutoLoader.prototype._load = function() { var self = this; clearTimeout(this._loading); this._loading = setTimeout(function() { self._pool.push(self._generator.apply(self._context)); }, this._timeout); }; AutoLoader.prototype.get = function() { var result; clearTimeout(this._loading); if(this._pool.length > 0) { result = this._pool.pop(); } else { result = this._generator.apply(this._context); } this._load(); return result; }; AutoLoader.prototype.regenerate = function() { this._pool.length = 0; this._load(); }; gridsAutoLoader = new AutoLoader(function() { return _getGrids(window.tagConfig); }, 1000); tagDataAutoLoader = new AutoLoader(function() { return getTagData(); }, 1000);
内容4:错误处理的呈现
下面要进行错误处理,网易没有做这块,比如说,我们的某一个系列的数据临时没有取到,在前端要展现出来:
原创文章,转载请注明:http://www.cnblogs.com/phpgcs
如图所示有7个系列的数据,现在假设第2个系列的数据获取不到,错误处理成这个样子如下图:
然后到有数据的系列,要恢复正常如下图:
处理代码如下:
18 function noDataToShow(){ 17 if($('#nodatadiv').html() != undefined){ 16 console.log("nodatadiv has been one"); 15 $('#nodatadiv').fadeIn("slow"); 14 }else{ 13 $('<div id="nodatadiv" style="width:100%;height:100%;background: rgb(209, 122, 77);font-size: 400%;text-align: center;padding: 150px 0 0 0;">很抱歉,没 有找到相关数据。</div>').insertBefore('#stage'); 12 } 11 } 10 9 function changeNav(button, index) { 8 if(window[tagDataNames[index]]) { 7 if($('#nodatadiv')){ 6 $('#nodatadiv').fadeOut(1000); 5 } 4 window.tagData = window[tagDataNames[index]]; 3 tagDataAutoLoader.regenerate(); 2 }else{ 1 noDataToShow(); 0 }
首先要判断下数据的有无,window[tagDataNames[index]] 来判断
然后如果数据为空,调用自定义的 noDataToShow() ,新建一个遮罩div ,并且 insertBefore到 tag墙的node前面
如果数据不为空,则先判断是否有遮罩层,如果有,则让其淡去;如果没有,正常处理。
另外,在加遮罩层之前也要先判断一下是否已经有遮罩层存在,不要重复制造遮罩层。
还有就是动画效果的平滑处理,用到了 .fadeOut(1000) .fadeIn("slow");
内容5:画布的切割(核心)
整个效果的实现就是画布切割成一个个tag,将数据填充到一个个的tag模板中去,加载并展现。
首次调用切割函数 _getGrids 是从这里开始的
gridsAutoLoader = new AutoLoader(function() { return _getGrids(window.tagConfig); }, 1000);
传入了参数 window.tagConfig
4 //input 5 areas 6 Object {cols_40_60: Array[2], cols_50_50: Array[2], cols_32_32_36: Array[3]} 7 pageLayout 8 Object {top: 0, left: 0, width: 1674, height: 305} 9 colorPatterns 10 [Array[4]] 11 tagLevels 12 Object {1: Object, 2: Object, 3: Object, 4: Object} 13 navConfig 14 Object {currentPos: "news_all", dataNames: Array[7], hashReg: /news_all|newspaper_all|news_local|newspaper_local|weibo_all|foreign_media|weibo_local/}
是之前定义的全局的参数:
- areas 切割模板
- pageLayout 画布参数
- colorPatterns 颜色模板
- tagLevels 级别
- navConfig 当前位置,数据系列的名字,数据系列的url的hash
最后经过 _getGrids的处理,得到了一个由28个object组成的数组,如下列举了3个object:
32 //output 31 [Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Ob ject, Object, Object, Object, Object, Object, Object, Object] 30 28 Objects... 29 0: Object 28 backgroundColor: "#595e90" 27 colorPattern: Object 26 fontColor: "#FFF" 25 fontSize: 57 24 height: 137 23 left: 0 22 top: 0 21 width: 753 20 backgroundColor: "#8e6da0" 19 colorPattern: Object 18 fontColor: "#FFF" 17 fontSize: 27 16 height: 85 15 left: 753 14 top: 152 13 width: 276 12 27: Object 11 backgroundColor: "#869259" 10 colorPattern: Object 9 fontColor: "#FFF" 8 fontSize: 12 7 height: 34 6 left: 1535 5 top: 271 4 width: 139
每个object的内容包括
- backgroundColor
- colorPattern
- fontColor
- fontSize
- height
- left
- top
- width
那这个 _getGrids 是怎么处理的呢?
27 function _getGrids(config) { 26 var result = [], 25 cursor = 0, 24 fontSizeFactor = 0.18, 23 colorPatterns = config.colorPatterns[0]; 22 21 _cutGrid(config.pageLayout, function(gridConfig) { 20 if(!gridConfig.colorPattern) { 19 gridConfig.colorPattern = colorPatterns[cursor++]; 18 } 17 if(gridConfig.rows || gridConfig.cols) { 16 _cutGrid(gridConfig, arguments.callee); 15 } else { 14 var colorPattern = gridConfig.colorPattern, 13 backgrounds = colorPattern.backgrounds, 12 backgroundsLength = backgrounds.length, 11 //borderColor = colorPattern.borderColor, 10 fontColor = colorPattern.fontColor; 9 8 gridConfig.fontSize = Math.floor(Math.sqrt(gridConfig.width * gridConfig.height) * fontSizeFactor); 7 gridConfig.backgroundColor = backgrounds[Math.floor(Math.random() * backgroundsLength)]; 6 gridConfig.fontColor = fontColor; 5 //gridConfig.borderColor = borderColor; 4 3 result.push(gridConfig); 2 } 1 }); 0 return result; 1 }
可以参考另一篇文章:http://litten.github.io/blog/2013/05/03/my-news-reader-box/
原创文章,转载请注明:http://www.cnblogs.com/phpgcs