先来个传送门:藏地传奇-真言专题。话说搞这个专题也折腾了不少时间,看似简单的页面,实际也隐藏着大大小小的坑。下面请听我一一道来。
一、先从布局说起
真言专题页采用的布局是屡见不鲜的瀑布流。其实当时一接到这个需求的时候,第一反应是Github上搜插件库,因为关于瀑布流的JQ库实在是太多了。像KISSY的waterfall,像@Sebobo 的Wookmark-jQuery,像jQuery Masonry等,都是很简单易用的库。但体验过其中几个DEMO之后,发现其实都不符合需求的预期,于是决定自己写一个。虽说写得有点挫,但效果还算不错,
在我的理解看来,瀑布流布局主要有三类方式可以实现,分别是绝对定位,浮动排列以及CSS3定义。这三种方式其实并没有绝对的优劣好坏之分。使用绝对定位的方法,能使得每个方块的位置更容易安排,受到HTML结构的影响较少,但要使用JS大量的进行位置计算,性能上会有损失。使用浮动的话,更多的是CSS主导,布局简单易用,避免了大量的JS位置计算,但方块位置受到的约束较大。CSS3方法实现的话IE系列就明显不给力了,或许现阶段并不适合业务大环境。
而这次的藏地需求中,每个方块的大小是固定的,主要有三种规格,使用浮动布局实现起来更加简单快捷,于是我采用了浮动布局的方法进行设计。父容器使用三列等宽的DIV,然后每次加载时候通过JS计算三列的高度,然后把新的方块插进最短的父容器当中。因为父容器的宽度是固定的,然后每个方块的大小也是固定的模板,所以直接插入父容器就可以了,无需计算每个方块的位置。
关于瀑布流布局的扩展阅读:
- 1.淘宝UED-瀑布流布局浅析 http://ued.taobao.com/blog/2011/09/waterfall/
- 2.司徒正美-各大瀑布流简析与建议 http://www.cnblogs.com/rubylouvre/archive/2012/04/18/2455222.html
- 3.张鑫旭-折腾:瀑布流布局 http://www.zhangxinxu.com/wordpress/?p=2308
二、方块模板和组合
此次专题中,方块模板分几种,分别是有图片的大方块(329X219),有图片的小方块(164X109),无图纯文字的大方块(329X219),无图纯文字的小方块(164X109) 。根据设计稿,这些方块出现的组合只有三种,分别是一个大方块单独出现,两个小方块同时出现以及四个小方块同时出现。
关于滚动加载,一开始先入为主的想法就是通过AJAX返回数据数组,然后通过拼接字符串凑出HTML,然后把拼出来的HTML插入到DOM当中去。一开始写DEMO的时候,拼凑字符串的确是见效最快的方法,但这并不适合生产环境。因为拼凑HTML带来了很多的问题。首先是代码格式问题,用JS拼出的HTML可谓是一坨一坨的,即使加上合理的缩进,看上去也感到很不舒服。而且这样也不符合重构的思想——表现,行为和结构三者的分离,导致可维护性变得很差。然后,这样的做法还存在着XSS的风险,因为简单的拼凑容易忽略了HTML危险字符的转义。
这个时候,使用模板就是不二的选择。而且优秀的JS模板也很多,诸如Mustache,Handlebars,Juicer等等。而且模板写起来也更加纯粹,避免了JS和HTML混写带来的不爽。因为之前一直有使用Handlebarsjs这个模板,于是此次藏地的专题也就自然而言的选择了它。Handlebars其实算是一个带逻辑的模板,这次用在这里显得有点牛刀杀鸡了。因为方块的模板用不到逻辑判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<script id=“big-box-noimg-tpl”type=“text/x-handlebars-template”>
<div class=‘b-box’data-tag=‘nopic’>
<div class=‘cont-only’>
<h3 class=‘y-title-only’>{{title}}</h3>
<pclass=‘y-writer-only’>{{nickname}}</p>
<pclass=‘y-desc-only bx’>{{short}}</p>
<pclass=‘content-all’>{{content}}</p>
<p class=’y-share-only’><i></i>转发微博</p>
<p class=’y-up-only’ data-artid=’{{id}}’><i></i>{{vote}}</p>
<pclass=“y-zyx”>点爱心赞一下</p>
</div>
<ahref=“javascript:;”class=“mask-close”></a>
<div class=“all-over”></div>
</div>
</script>
|
为了专题版面的美观,大方块的出现频率应该比小方块更高,所以我使用了一个简单粗暴的方法实现方块的组合,就是使用数组存放每种组合出现的次数来模拟概率。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
if(arr.length>0){
varmapHash=[1,1,1,1,1,2,2,4];
while(arr.length>0){
varhtml;
varcolNum=returnShortCol();
varindex=18;
if(arr.length<4){index=17;}
if(arr.length<2){index=14;}
varsign=mapHash[Math.floor(Math.random()*index)];
if(sign==1){
vari1=arr.shift();
//具体渲染操作
}elseif(sign==2){
vari1=arr.shift();
vari2=arr.shift();
//具体渲染操作
}else{
vari1=arr.shift();
vari2=arr.shift();
vari3=arr.shift();
vari4=arr.shift();
//具体渲染操作
}
}
}
|
这段代码写得有点傻,存在着很大的改进空间。其实可以将其封装成函数,通过传参调用来控制每种组合的出现的频率。
关于JS模板引擎的一些阅读:
- 1.浅析JS模板引擎 http://blog.csdn.net/meikidd/article/details/8931904
- 2.高性能Javascript模板引擎原理解析 http://cdc.tencent.com/?p=5723
- 3.如何选择需要的模板引擎 http://garann.github.io/template-chooser/
- 4.JS模板引擎对比 http://blog.csdn.net/wuchengzhi82/article/details/8938122
三、关于跨域的那些事
此次专题页面涉及到三个服务器,分别是单独存放页面HTML的服务器,存放静态资源的CDN服务器,以及存放各种动态数据的后端服务器,所以数据的交互就不能回避跨域这个问题。
数据方块的展示这个好办,jQuery里“一键式”的JSONP便可以实现。这里我是使用了$.getJSON这个方法,在uri query里加上了callback=?就可以实现跨域的AJAX获取。但这里要注意一点,就是后端服务器返回的content-type需要是JSON格式才能使回调函数被正确调用。以前就遇到过返回格式为text/html的坑爹问题。
难点是在大网易通行证的异步登陆和图片上传这两个环节上。要解决这两个问题,都需要用到iframe作为跳板。在解决大网易通行证的异步登陆上,十分感谢@yangjunwei 这篇详细的参考案例——大网易统一登录入口异步登录http://nanny.netease.com/blog/ntes-async-login 。依葫芦画瓢,登录功能便可以实现。
接下来是玩家提交图片的功能。因为涉及到图片的上传,普通的JSONP方法就失效了。普通的JSONP本质上也只是发送一个get请求,而图片的上传是需要通过post方式,而且Content-Type为multipart/form-data才能传送二进制的文件,这个时候就需要iframe来帮忙了。实际上,这与上面提到的异步登录大体上是相同的。先在页面上添加一个宽高都为0,且frameborder也为0的iframe,并将iframe的name赋值,这个iframe用于数据的提交和返回结果的接收。
1
|
<iframe name=“upload”frameborder=“0″width=“0″height=“0″></iframe>
|
然后将需要提交的form表单的target指向刚才的iframe,并声明enctype为multipart/form-data,这时候submit就可以通过iframe将表单内容提交到不同域的服务器上。但这样还不够,因为我们需要取得服务器返回的结果,并触发页面上定义好回调的回调函数。这时候需要在iframe上声明
1
|
document.domain=’163.com’;
|
使得iframe和父页面拥有相同的domain,然后就可以在iframe中调用父页面的函数,获得服务器返回的结果。
1
2
3
4
5
|
<script>
document.domain=’163.com’;
varresult={“msg”:“”,“success”:true,“error”:false};
parent.postArtCallback(result);
</script>
|
关于JS跨域的一些阅读:
- 1.JavaScript跨域总结与解决办法 http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html
- 2.前端跨域总结 http://www.slideshare.net/zhangsuoyong/ss-10511572
四、坑爹的IE大爷
不知道大家会不会觉得浏览器自带的上传控件太丑了,与设计稿的风格格格不入。这时候自然就会想到使用a标签进行模拟。将a标签的click事件和input type=file的change事件进行挂钩,并将input隐藏掉,就能实现这种模拟效果。看上效果很完美,于是心生暗喜。
作为一枚靠谱的前端工程师,接下来肯定要在浏览器端检查一下输入框的字数是否超长,验证码是否填写等等嘛,于是就通过e.preventDefault阻止submit的默认行为,通通先通过JS检查,等验明正身了,才放马交到服务器上。Chrome下一切完美,正准备拍手收工之时,IE突然跳出来大喝一声:“小样!你那代码休想在我身上跑!”这下我愣住了,这IE怎么就那么坑爹了,人家别的浏览器都跑得好好的,就你净捣乱。
于是乎我又得在IE身上折腾一番,终于查明原因。原来IE浏览器下的上传控件input['file']处于隐藏状态的时候,表单不能通过JS提交,即不能通过form.submit()方法进行提交,这是出于安全原因的考虑。那什么才算隐藏状态?自己分别测试过三种情况,分别为display为none,visibility为hidden,opacity为0的时候都属于隐藏状态,这时候若通过form.submit()的方法进行表单提交都会失败。
看着设计稿上漂亮的样式在苦苦哀求着,我又怎忍心帮它披上默认样式这么挫的外衣呢。于是我只能舍弃客户端的验证,不用JS阻止form submit的默认行为,而是把这项粗重活交由服务端处理了。
除了踩了这个坑,我在IE下使用jQuery方法获取页面DOM元素时也掉进一个阴沟。IE8及其以下的版本,使用$(ele).html()获取到的innerHTML是大写的字符串,而IE9及Chrome等浏览器都是返回小写的字符串。所以要先使用String.toLowrCase进行处理以使得后续的操作前提是一致的。
另外顺带提一下IE下使用new Date()时候传入的参数格式和其他浏览器也是有差别的。通常在Chrome下我们可以使用new Date(‘yyyy-MM-dd’)获得一个时间,但IE下这样的参数格式是无效的,会返回undefined。正确的写法应该是new Date(yyyy,MM,dd),要分别传入三个参数才能返回正确的时间值。
五、关于图片预览和方块定位
在页面上有一个上传图片的环节,用户选择图片后,是需要实现图片预览的功能。那么要实现这个效果,支持HTML5 FileReader的浏览器就可以通过API实现,而IE浏览器则只能通过滤镜的方法实现。这里的IE是指IE9及其以下版本的浏览器,IE10是不能通过滤镜实现这个操作的。多说无益,show me the code,首先是HTML结构。
1
2
3
4
5
6
|
<div class=“right”>
<div class=“pre-div”><img class=“pre-img”></div>
<pclass=“post-att”>特别提醒:图文并茂的真言将会比没有图片的真言更容易获奖哦!</p>
<aid=“upload-a”>选择图片</a>
<input id=“upload-btn”type=“file”onchange=“handleFiles(this)”name=“picture”>
</div>
|
为了方便IE下使用滤镜,在img标签外多包了一层div。下面是实现图片预览功能的具体代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
//chrome上传预览
functionhandleFiles(files){
if(window.File&&window.FileReader&&window.FileList&&window.Blob){
//遍历files并处理
files=files.files;
for(vari=0;i<files.length;i++){
varfile=files[i];
varimageType=/image.*/;
//通过type属性进行图片格式过滤
if(!file.type.match(imageType)){
continue;
}
//读入文件
varreader=newFileReader();
reader.onload=function(e){
//e.target.result返回的即是图片的dataURI格式的内容
varimgData=e.target.result,
img=document.createElement(‘img’);
//img.src = imgData;
//展示img
$(“.pre-img”).attr(“src”,imgData).css(“visibility”,“visible”);
}
reader.readAsDataURL(file);
}
}else{//IE
varhtml=“<div class=’pre-img’></div>”;
$(‘.pre-div’).html(”).html(html);
//采用滤镜效果生成图片预览
path=$(‘#upload-btn’).val();
$(‘.pre-img’).css({“filter”:“progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=’true’,sizingMethod=’scale’,src=””+path+“”)”});
}
}
|
其实在折腾这个页面上比较耗时间的反倒是每个方块展开形式的判断。当用户点击方块时候,要判断方块是从顶部展开还是底部展开,当前的屏幕是否足以容纳完全展开的大方块。这里的技术性不强,但是逻辑就比较复杂。首先要判断是否在第一屏展开,第一屏的空间和非第一屏的空间是存在差别的,因为第一屏存在着版头。而要获取点击方块的位置,我们可以借助jQuery的position()方法,获得方块的left值和top值。这里有两点要注意的,就是假如top和bottom的值同时声明,则top指起效,bottom值无效,left值和right值同理。jQuery还有另外一个方法为offset(),与position的区别就是,offset是获取匹配元素在当前视口的相对偏移,而position是获取匹配元素相对父元素的偏移。
关于HTML5 FileReader的一些阅读:
- 1.HTML5 FileReader http://hushc.sinaapp.com/post/27.html
- 2.FileReader – Web API reference | MDN https://developer.mozilla.org/en-US/docs/Web/API/FileReader
- 3.jQuery获得元素位置offset()和position()的区别 http://www.cnblogs.com/meihua/articles/2031992.html
From http://www.g7blogs.com/?p=728