使用Canvas实现动画效果

这是动手开发一个HTML5赛车小游戏系列文章。你可以查看前一篇内容:

    o

  1. 动手开发一个HTML5赛车小游戏

这里我们使用Canvas开发这个游戏,而不是采用传统的DOM方式实现,虽然这个简单的游戏使用DOM也可以实现(甚至有可能更容易点)。但是Canvas可以更加方便的控制图片,功能更更加强大,因为你可以完全按照自己的想法进行绘制和操作图像。

Canvas是HTML5中新加入的标签。它定义了一个图形,可以是图表或任何内容,但是它只是一个容器,你必须使用JavaScript来进行绘制图形。Canvas并不是为动画准备的,但是你可以使用它来实现动画效果。

前面介绍过在HTML中如何使用JavaScript实现DOM的动画效果:

    o

  1. Javascript动画效果的实现(一)
  2. o

  3. Javascript动画效果的实现(二)
  4. o

  5. Javascript动画效果的实现(三)

其实使用Canvas中的动画实现某种程度也利用了同样的思想。快速的定时的改变所绘制的图形的位置。当然Canvas上所绘制的图形不会自动消失,如果你不断的画图形会不断的重复出现在画布中,所以你需要在重绘之前清除画布,这样你得到的才是动画而不是动画的轨迹。

JavaScript中没有诸如sleep等阻塞的方法,所以我们需要使用setTimeout或setInterval来实现定时执行的功能。大部分现代浏览器中提供了一个新的方法requestAnimationFrame来代替普通的定时器,这是由FireFox首先实现的,虽然目前还不是标准,但是大部分现代浏览器中已经支持这个方法,你可能需要使用不同的前缀调用它,如:mozRequestAnimationFramewebkitRequestAnimationFrame。这个方法类似setTimeout,它由浏览器自行判断执行的时机,当页面不可见时它会自动降频,以达到节约资源的目的。它是你实现动画的最佳选择。它的执行频率大于是每秒60次,因为这是浏览器刷新的极限,如果再高对浏览器来说就没有意义了。当然对于不支持的浏览器我们可以使用setTimeout或setInterval来代替,相信你可以很容易的实现类似功能。下面是一个兼容的写法:

window.requestAnimFrame = (function() {
oreturn window.requestAnimationFrame
o|| window.webkitRequestAnimationFrame
o|| window.mozRequestAnimationFrame
o|| window.oRequestAnimationFrame
o|| window.msRequestAnimationFrame || function() {
o//return setTimeout(arguments[0], 1000 / 60);
oreturn -1;
o} // return -1 if unsupported
})();

window.cancelRequestAnimFrame = (function() {
oreturn window.cancelAnimationFrame
o|| window.webkitCancelRequestAnimationFrame
o|| window.mozCancelRequestAnimationFrame
o|| window.oCancelRequestAnimationFrame
o|| window.msCancelRequestAnimationFrame || function() {
oreturn -1;
o} // return -1 if unsupported
})();

当然你还要熟悉一下Canvas绘图的API。

首先你需要获取一个画布的对象,然后通过画布对象的getContext()方法获取一个绘图上下文。这个方法接受一个字符串参数"2d",这是目前你所能用的,当然还有其他的模式,比如WebGL,虽然已经被Chrome和FireFox所支持,但是MS的IE浏览器应该是不会支持了。通过这个绘图上下文你可以画线也可以画图形,可以描边路径也可以填充它,还可以直接绘制图片,或者直接操作图片数据等等。

网上有很多这方面的资料,这里就不多说了,文章的结尾我会给出一些参考资料。如果你对Canvas绘图不是很熟悉,MDN的Canvas教程将会是你的好选择。

这里我们做一个最简单的实例,让一个圆在画布上移动起来:

    o

  1. 首先我们需要把一个方框画到画布上。点击查看示例:http://jsfiddle.net/mqVZ7/1/
  2. o

  3. 接下来让我们每秒将小方块向右向下移动5个像素,并改变它的颜色。为了方便查看我们将小方块的透明度设置为.5。你会看到一串小方块运动的轨迹:http://jsfiddle.net/mqVZ7/2/
  4. o

  5. 这并不是我们需要的动画效果,它绘制的是一个轨迹而不是动画,要想实现动画效果,我们需要在每帧的绘制之前清除画布的所有内容,当然你还可以只重绘需要的部分。这里我们重绘整个画布,为了运动更平滑我们将移动的距离改为1个像素:http://jsfiddle.net/mqVZ7/3/

好了我们实现了我们想要的效果。当然你同样可以使用前面用过的数学公式。先说到这,后面会继续跟大家讨论控制动画对象等内容。

引用内容:

    o

  1. HTML DOM CanvasRenderingContext2D 对象
  2. o

  3. 玩转html5<canvas>画图
  4. o

  5. The canvas element
  6. o

  7. MDN Canvas

转载请注明原文出处《使用Canvas实现动画效果》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。

动手开发一个HTML5赛车小游戏

你是不是曾经也有自己做一个游戏的冲动呢?是不是因为事情太多,手头没空或者没有做过游戏的基础而屡屡放弃了呢?现在HTML5正如火如荼,让我们一起使用HTML5中的新功能做一个小游戏玩玩吧。

先来看看效果:

在线演示地址:点击查看

源代码GitHub:点击查看

麻雀虽小,五脏俱全。先来说一下这个小游戏有的内容:

    o

  1. 一个开始画面,写了游戏的名字和一个开始按钮。
  2. o

  3. 游戏的主体内容,一个可以自己控制的小汽车,还有很多打酱油的汽车,另外还有马路及马路旁边的龙套树木和自行车道…
  4. o

  5. 一个结束游戏的画面。

好了这就是全部游戏的内容。游戏的玩法很简单,相信大家应该都玩过,就是一辆汽车在马路上跑,你要尽量躲避开其他汽车,不与他们相撞,否则游戏结束,游戏会以你跑过的旅程计算分数。

主要用的技术是HTML5中的画布元素Canvas和JavaScript。主要要做的工作就是实现Canvas的tween动画,一些简单的矢量,速度,位移等效果的处理。另外还有一些Sprint动画的处理。

具体的实现过程会在以后的文章中详细讲解。

目前本人正在进一步完善这个简单的JavaScript游戏引擎,以便使用这个游戏引擎实现一个SRPG游戏。新的游戏正在开发当中,如果您对此感兴趣欢迎联系我:dukai86@gmail.com

转载请注明原文出处《动手开发一个HTML5赛车小游戏》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。

基于JavaScript的微博备份应用

鉴于微博管理的日益严格,很多内容都变成了不适合公开或被用户删除。为了保存自己的微博,只好使用一些具有中国特色的应用-微博备份。

你的微博会这样么?

你的微博会这样么?

现在微博备份工具有两个思路:1.使用微博提供的API,但是微博提供的API目前是无法读取所有的微博消息的。2.通过wap版的微博备份。这是目前为止比较好的一个办法。

但是,作为一名前端攻城师,我们需要更加透明,更加安全的方式来备份自己的微博。

所以这里选择使用JavaScript来做一个微博备份的工具,具体请查看微博备份时光机

其实实现的思路还是很简单的。

    o

  1. 使用bookmarklet载入一段JavaScript,模拟浏览微博的过程,载入页面的全部微博。
  2. o

  3. 分析页面上的数据,提取需要的信息并保存到数据库。
  4. o

  5. 打开一个新的页面重复第1步和第2步操作直到最后一页。注意不要关闭第一页,因为要通过第一页监控新打开的页面的状态。
  6. o

  7. 在全部处理完成之后将,提示保存到本地并将服务器数据删除。

基本的思路就是这样的。

优点:

    o

  1. 使用JavaScript技术,所有过程透明,系统不需要您提供任何个人信息或密码信息,你只需要打开你要备份的微博页面,点击备份按钮即可以完成备份.
  2. o

  3. 系统提供将备份的页面保存到本地的功能,同时提供JSON格式的元数据,方便进行管理和重用.
  4. o

  5. 所有源代码开源

缺点:

    o

  1. 备份过程较为复杂,需要本地等待。
  2. o

  3. 需要使用较为先进的浏览器。
  4. o

  5. 有时候可能链接失败,断点处继续的功能目前还没有。

抛砖引玉,欢迎大家指正。

转载请注明原文出处《基于JavaScript的微博备份应用》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。

使用AngularJS高效便捷的开发网络应用

Google于最近发布了一款增强HTML的AngularJS框架.它可以使用增强的HTML语言快速的构建Web App.

HTML用来定义静态文档非常赞,但是用来定义web应用的动态视图就显得有点力不从心了.AngularJS能够为你的应用扩展HTML词汇,让你的代码更加具有表现力,更高的可读性,以及更高效的开发速度.

其他的框架使用抽象HTML,CSS及JavaScript的方式或者提供一个规则的方法来操作DOM来解决HTML的短板.但是所有的这些都没有解决HTML并非为动态视图设计的这一根源问题.

AngularJS是完全可扩展的并且能够与其他的框架完美结合.所有的功能都可以被修改或者代替以便适合你的独特的开发流程和功能需求.

AngularJS主要有一下特点:

    o

  1. 不唐突的数据绑定.AngularJS自动的将数据从UI一道你的model中,并在数据改变后自动更新到UI.不需要继承类,不需要包装也不需要调用getter/setter方法.你的model可以简单如原始的数组也可以复杂如你自定义的JavaScript对象.
  2. o

  3. HTML作为模板.你,你的浏览器,你的编辑器以及你的其他工具早已了解如何使用HTML工作.为什么还要使用其他的格式?AngularJS让你扩展属于你的应用的特定元素,属性,类型,而且这完全符合HTML标准.
  4. o

  5. HTML的可重用的组件!AngularyJS让你有能力使用你添加了行为和动画的元素和属性扩展HTML语法.想像这样使用<tab>,<calendar>或者<colorpicker>来代替<div><div><div>…吗?想通过添加一个key=’ctrl-s’来给任意元素添加快捷键吗?你错过<blink>标签了么?所有的这些以及更多都不在是梦想.
  6. o

  7. 视图和路由.AngularJS让你通过简单的路由切换子视图.同时你会得到免费的深链接.
  8. o

  9. 测试和可测性.发布应用意味着测试他们.我们提供了常见的模拟,我们充分利用依赖注入,并且我们鼓励MVC结构分离行为和视图以便更易测试.它还提供了一个端到端的场景测试器通过了解应用状态来消除测试片段.

一个简单的例子,让你输入到input中的内容自动反映到页面其他的地方:

<!doctype html>
<html ng-app>
<head>
  <script src="http://code.angularjs.org/angular-1.0.1-ffb27013.min.js"></script>
</head>
<body>
  <div>
    <label>Name:</label>
    <input type="text" ng-model="yourName" placeholder="Enter a name here">
    <hr>
    <h1>Hello {{yourName}}!</h1>
  </div>
</body>
</html>

可以看到确实很简洁.至于效率方面我还没进行测试,源码也还没读,如果感兴趣可以到官网看一下,官网也有实例.

http://angularjs.org/

转载请注明原文出处《使用AngularJS高效便捷的开发网络应用》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。

渐进增强的无刷新多图片上传控件(iFrame+HTML5)

目标:

实现一个无刷新的,多图片上传控件.(目前已在91美图网中使用)

特点:

采用渐进增强的设计思路,针对支持HTML5新特性比较好的现代浏览器,使用HTML5中的新特性,包括File对象,XMLHttpRequest中的upload对象,File对象等新增的功能实现较为高级的多图片无刷新能够检测上传进度的上传控件.而对于不支持HTML5特性的较老的浏览器则使用传统的隐藏iFrame的形式来实现伪装的多图片上传功能.

优缺点分析:

对于支持HTML5的现代浏览器,可以通过原生的input控件添加multiple属性实现文件的多选上传.这种方法可以使用XMLHttpReqeust对象直接上传图片对象,使用较为方便,而且可以在本地获取图片上传的进度.但是目前占市场份额较多的IE浏览器包括IE9以及低于IE9版本的IE浏览器均不支持这项新内容.

所以对于不支持HTML5中的multiple属性的input[type='file']的浏览器这里采用了隐藏iframe,将待提交的表单的target设置为iframe的name,以此来实现变通的无刷新图片上传.事实上,这种方法并不是没有刷新,而是把刷新的操作放到iframe中了而已.由于较旧的浏览器事实上并不支持上传控件的多选,所以这里只能通过多次选择,选中多个文件,操作较为复杂.而且本方法要想实现上传进度必须配合服务器端的查询,增加服务压力.

实现效果:

上传效果图1

上传效果图1

上传效果图2

上传效果图2

实现:

首先通过能力检查的方式判断浏览器是否支持HTML5中的新特性,如果支持的话选择HTML5的上传方式,不支持则使用传统的方式实现.

isSupportMultipleFile: window.File && window.FileReader && window.FileList && window.Blob,

在使用HTML5的新功能上传时用的主要内容有:1.<input type="file" />控件的新属性multiple,添加了此属性的控件支持同时选择多个文件,在选择完成后会自动生成一个File对象的集合FileList对象.这里我们就是通过监控input控件的onchange事件,来获取用户选中的文件.

File对象包括的主要属性:

    o

  1. fileName-字符串,文件的名称
  2. o

  3. fileSize-无符号的长整型,文件的大小的bytes
  4. o

  5. name-字符串,文件名
  6. o

  7. size-无符号长整型,File对象引用的文件的大小(bytes)
  8. o

  9. type-字符串,File对象引用文件的类型(MIME type)

在获取到FileList之后根据每个File对象的内容生成界面中的待上传项目:

addUploadItem: function(guid, filename, percent){}

本方法主要是根据唯一编号,文件名称和当前进度生成一个待上传的条目,用于界面显示,主要操作为DOM操作,这里不再赘述.

接下来就是最主要的文件上传部分的内容了,主要的工作就是:当用户点击上传按钮时,将缓存起来的FileList遍历上传.

这里我采用了每次同时上传5个文件的策略,同时上传的文件太多会造成浏览器崩溃,以及服务器端阻塞超时的情况.具体同时上传文件数目可以根据实际情况决定.

这里要说明一下,使用XMLHttpReqeust对象上传File对象有两种方法:

    o

  1. 直接使用XMLHttpRequest将File对象Post到服务器端,但是由于这样发送的内容没有有效的请求信息,只是将文件内容Post到服务器端,所以在服务器端无法通过正常的post获取文件内容,所以要自己读取服务器到的源内容来获取图片.PHP中可以通过$GLOBALS["HTTP_RAW_POST_DATA"]来获取,ASP.NET中可以通过Request.BinaryRead(Request.TotalBytes)来获取,其他语言您可以自行查找.当然文件的其他属性,比如大小,名称等等可以通过设置请求头的方式来实现,XMLHttpRequest.setReqeustHeader();
  2. o

  3. 另外Mozilla Developer NetWork上提供了另外一种方法,即通过FileReader对象来获取File的二进制字符串,然后自己按照标准的结构构造一个请求,通过XMLHttpReqeust.sendAsBinary来以二进制流的形式上传,来实现后端服务器像普通模式一样获取内容.但是很遗憾,XMLHttpReqeust.sendAsBinary这个方法是FireFox私有的方法,标准中没有,其他浏览器也并没有支持,所以经过测试能用后我果断放弃了这个方法.

需要了解的技术内容已经清晰了,下面就来看一下实现吧,我用伪代码+注释的形式来说明:

//获取缓存好的FileList对象
var allFiles = fileuploader.cache.files;
//新建一个迭代游标用于控制当前同时上传的文件数目
var iterator = 0;
//上传的主要方法
var upload = function(){
o//检查如果FileList对象的数目为0则直接退出
oif(allFiles.length == 0){
oreturn;
o}
o//如果FileList不为空,且当前的迭代游标小于5则执行上传
owhile(allFiles.length > 0 && iterator < 5){
o//弹出一个File
ovar file = allFiles.shift();
o//游标加1
oiterator++;
o//TODO:获取界面上的条目,并设置状态为开始上传
ovar perDom = fileuploader.uploadItems[file.guid];
o/*样式设置代码省略*/
o//新建XMLHttpRequest上传File文件,通过一个自执行的匿名函数将perDom传给异步请求,留作请求完成使用
o(function(perDom){
ovar req = new Request({
ourl: '/member/img-item/ajax-upload',
odataType: 'json',
omethod: 'post',
osuccess: function(data){
o//检查数据的状态,并处理返回的数据
oif(data.status){
ofileuploader.addImages([data]);
o//TODO:设置状态为成功
o/**样式设置代码省略**/
o}else{
o//TODO:设置状态为失败
o/**样式设置代码省略**/
o}
o//游标减1表示上传完成
oiterator--;
o//检查是否全部上传完毕
oif(allFiles.length == 0){
ofileuploader.close();
o}
o//如果游标变为0则重新调用upload方法上传剩余的内容
oif(iterator == 0){
oupload();
o}
o},
oprogress: function(e){
o//上传过程中计算上传的进度
ovar percent = parseInt(100 * e.loaded / e.total);
operDom.progressBar.style.width = percent + '%';
o}
o});
o//设置请求头信息,包括文件的名称大小类型唯一标识等等
oreq.setRequestHeader("DKUPLOADER_NAME", file.name);
oreq.setRequestHeader("DKUPLOADER_SIZE", file.size);
oreq.setRequestHeader("DKUPLOADER_TYPE", file.type);
oreq.setRequestHeader("DKUPLOADER_GUID", file.guid);
o//上传文件
oreq.send(file);
o})(perDom)
o}
};

使用HTML5上传图片的内容到这里就结束了.怎么样很简单是不是.下面就继续说一下传统的方法上传的实现.

普通的基于iframe的无刷新文件上传系统已经是老生常谈的问题了,即将Form的target指向iframe,在执行submit的时候,页面不会刷新,只是iframe刷新.只要页面返回的内容中执行javascript调用父窗口的相关对象来更新状态即可.

当然本身这种方法是无法实现获取文件上传进度的,但是在php环境中,通过Alternative PHP Cache(APC)模块的扩展带来的小惊喜,可以获取文件上传的进度.前提是你需要独立安装这个扩展,而且需要你通过脚本定时查询后台来获取上传进度.这么做一是进度不够精确,另外增加服务器负担,除非是文件体积较大,否则实在是没有太大的必要这么做.至于其他的后台语言我没有深入研究,如果有哪位愿意分享还请多多指教.

这里实现选择多个文件是通过将<input type="file"/>注册onchange事件,待其选择文件后将其移动到另外一个隐藏容器中并将其缓存起来.而在原来上传按钮的位置重新创建一个新的input对象.这在点击选择文件按钮时会一直有上传选择窗口弹出,来实现伪装的多文件上传.

是实际上传的时候,<input type="file"/>移动的策略跟第一种情况基本类似,只不过上传文件的方法由XMLHttpRequest改为Form的Submit方法.下面继续已代码说明:

var upload = function(){
o//获取所有缓存的input[type='file']对象
ovar allFiles = fileuploader.cache.files;
o//如果数目为零则跳出
oif(allFiles.length <= 0){ fileuploader.close(); return; } //迭代游标 var iterator = 0; var uploadForm = fileuploader.dom.uploadForm; var uploadItems = fileuploader.uploadItems; //先清空form对象内部的内容 uploadForm.innerHTML = ''; //限定一次上传最多5个文件 while(allFiles.length > 0 && iterator < 5){
ovar file = allFiles.shift();
oiterator++;
ovar perDom = uploadItems[file.guid];
o//修改状态为正在上传
operDom.status.className = 'message';
operDom.status.innerHTML = "正在上传";
operDom.thumb.innerHTML = '<img src="http://www.91meitu.net/resource/images/ajax_loader_s.gif" />';
o//将input[type='file']对象移动到待上传的表单内部
ouploadForm.appendChild(file);
o}
o//开始上传
ouploadForm.submit();
}

当然利用iframe跟使用HTML5上传还是有些区别的,因为使用Form提交的内容,通过javascript是无法直接获取上传的状态的,所以要被动等待iframe的内容返回后,通过调用方法来实现完成上传后的下一步操作.

我这里的图片上传控件是一个静态的对象,名字叫fileuploader,所以在iframe中调用的时候可以通过调用fileupload.complete方法来实现图片上传完成后的下一步操作.页面返回的内容是:

<script>window.parent.fileuploader.complete(" . json_encode($result) . ")</script>

而complete方法的具体内容如下:

var complete = function(){
ovar filesInfo = [];
ofor(var i = 0, len = data.length; i < len; i++){
ovar imgData = data[i];
ovar perDom = fileuploader.uploadItems[imgData.guid];
oif(imgData.status){
ofilesInfo.push(imgData);
o//TODO:设置状态为成功
o/**样式设置代码省略**/
o}else{
o//TODO:设置状态为失败
o/**样式设置代码省略**/
o}
o}
ofileuploader.addImages(filesInfo);
o//继续上传剩余的文件
othis.iframeupload();
}

写到这基本上整个上传控件就写完了,当然还有一些open(),close()方法,一些自定义事件什么的都可以继续丰富和完善.抛砖引玉,欢迎不吝指教!

使用方法,在页面载入完毕后执行fileuploader.open();

文件中使用了我自己写的一个小工具包,打包成zip文件,欢迎大家下载源代码.

点击下载:fileuploader源代码

参考资料:

    o

  1. Using files from web applications
  2. o

  3. Using the DOM File API in chrome code
  4. o

  5. File-Mozilla Developer Network
  6. o

  7. File API-W3C
  8. o

  9. XMLHttpRequest Level 2

转载请注明原文出处《渐进增强的无刷新多图片上传控件(iFrame+HTML5)》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。

瀑布布局的JavaScript实现方式

瀑布式布局是一种多列等宽不等高的一种页面展示方式,用于图片来源比较复杂,图片尺寸比较复杂时可以使用的一种展示方式,这种展示方式比较优美,让人有种错落有致的感觉.这种展示方式在淘宝的我要买,新浪微博的广场以及蘑菇街等等网站都有应用.这里是我刚刚做的一个小站91美图

实现布局有三个思路:

    o

  1. 最传统的思路,多弄几个容器,分几列,然后往每个列里面插入元素.其实用table分几列实现更加方便:P;
  2. o

  3. 使用html5中css3的多列布局来实现.参见w3c标准中的css3多列布局模块;
  4. o

  5. 使用绝对布局,通过javascript生成元素的布局位置.

前两种方法在网上都有比较详细的介绍,我这里就不再多说了,这里主要说一下我做的第三种实现的优缺点以及我的实现思路.

第三种方案是所有的要布局的元素都是绝对定位的,然后通过javascript来判断每个元素位置,动态设置位置实现布局.

缺点

需要使用javascript来遍历元素,然后要根据前一个元素来判断后一个元素的位置,这样可能对一些老版本的浏览器造成负担,特别是IE6这种老古董,而且在javascript失效的时候,整个页面的布局都会乱掉.另外如果整个页面宽度是变化的,则可能每次窗口尺寸改变时都要重新计算所有元素的位置,在页面中元素较多的时候可能会有闪烁的现象.另外如果页面中出现图片,则需要实现定义好图片的尺寸,否则会出现无法正确计算元素位置的情况.

优点

布局更加灵活,元素绝对定位,可以使用javascript灵活操作.页面宽度改变时可以重新布局整个页面.可以使页面的中的元素真正流动起来,让新添加的元素插入到高度最低的列,使页面的低端更加整齐,对插入的元素高地要求较低.可以较为方便的实现延迟加载.

具体实现

最开始我实现的时候是通过使用javascript生成虚拟的列,根据元素的顺序为每个元素分配一个列和行,然后计算每个元素的位置,举个例子,现在假设有四个列:

使其在页面中布局.事实上这个解决办法跟第一种和第二种是一个道理的.最后页面每列的高度差别可能会很大.

//getElements()方法用于获取页面中的元素
var items = getElements();
var columnCount = 4;
var columnWidth = 230;
var padding = 8;
//遍历所有元素
for(var i = 0, len = items.length; i < len; i++){
o//获取当前的元素
ovar currentItem = items[i];
o//获取当前对象的列
ovar currentColumn = (i + 1) % 4;
o//获取当前对象的行
ovar currentLevel = parseInt(i / 4);

o//有了当前的行跟列可以根据上一层的对象计算出当前对象的高度
ovar left = currentColumn * columnWidth;
ovar top = items[i - 4] ? 0 : items[i - 4].style.top + items[i - 4].clientHeight + padding;
o//设置当前的位置
ocurrentItem.style.top = top + 'px';
ocurrentItem.style.left = left + 'px';
}

代码和逻辑都比较简单,根据当前的行跟列计算出位置就行了.但是这个逻辑还是会出现元素高地差距过大的情况.看一下新浪weibo的广场图片效果:

新浪weibo广超图片列表效果图

新浪weibo广超图片列表效果图

可以看到越到最后可能列高度之间的差距会越大.这不是我们想要实现的效果.

所以我这里换了一个思路,虚拟的列还是要有的,但是行的概念我们抛弃掉,我采用的是一个类似流动的概念,新插入的元素是根据每个列的高度,那个高度最低就流向哪个列,最后确保每个列的高度都趋近一致,实现我们想要的效果.当然我们可以采取获取所有元素的高度,然后统一计算一下,获取一个最佳的排列方法,但是这会给浏览器带来较大的计算量,而且如果不断的加载更多的图片我们会得不偿失,所以我们采用的是一个流动的模型,只让当前对象寻找最低的高度然后插入.

这里我实现了一个Column对象,一个ImgItem对象.Column对象用于维护每一列的信息,包括列的最到高度列宽度等列信息.ImgItem对象保存了一个html节点对象,还有一些设置元素位置,获取当前元素位置的方法.

下面是Column对象的代码:

var Column = function(order){
othis.order = order;
othis.maxHeight = 0;
othis.columnWidth = 230;
othis.left = this.columnWidth * order;
othis.lastItem = null;
othis.positioned = false;
othis.setReferItem = function(item){
othis.lastItem = item;
o}

othis.getHeight = function(){
oif(this.lastItem){
othis.maxHeight = this.lastItem.getBottom();
o}
oreturn this.maxHeight;
o}

othis.getLeft = function(){
oreturn this.left;
o}
};

ImgItem对象的代码:

var ImgItem = function(referNode, column){
othis.referNode = referNode;
othis.bottom = -1;
othis.positioned = false;
};

ImgItem.prototype = {
o/*
o *set the refer node's top
o * @param value: Number
o */
osetTop: function(value){
othis.referNode.style.top = value + 'px';
o},
o/*
o *set the refer node's left
o * @param value: Number
o */
osetLeft: function(value){
othis.referNode.style.left = value + 'px';
o},
o/*
o *get the refer node bottom position
o */
ogetBottom: function(){
oif(this.positioned){
oif(this.bottom < 0){
othis.bottom = parseInt(this.referNode.style.top) + this.referNode.clientHeight;
o}
oreturn this.bottom;
o}else{
othrow("current node has not been positioned!");
o}
o},
osetPosition: function(column){
othis.positioned = true;
othis.setLeft(column.getLeft());
othis.setTop(column.getHeight() + 10);
ocolumn.setReferItem(this);
o}
};

基础打好了,下面要做的就是给元素进行布局了:

//首先根据配置信息中的列数初始化列
for(var i = 0, len = this.config.columnCount; i < len; i++){
othis.columns.push(new Column(i));
}
//获取页面上已存在的对象
var liItems = document.getElementById('img_list').getElementsByTagName('li');
//将所有的对象进行布局
for(i = 0, len = liItems.length; i < len; i++){
othis.addNewItem(liItems[i]);
}

好吧这里还用到了一个addNewItem方法:

getMinHeightColumn: function(){
ovar minHeight = -1, tempColumn = null;
o//遍历所有的列,获取当前高度最低的列,并返回
ofor(var i = 0,len = this.columns.length; i < len; i++){ if(minHeight > this.columns[i].getHeight() || minHeight == -1){
ominHeight = this.columns[i].getHeight();
otempColumn = this.columns[i];
o}
o}

oreturn tempColumn;
},

getMaxHeight: function(){
ovar maxHeight = -1;
o//遍历列对象,获取高度最高的列并返回高度
ofor(var i = 0, len = this.columns.length; i < len; i++){
oif(maxHeight < this.columns[i].getHeight()){
omaxHeight = this.columns[i].getHeight();
o}
o}

oreturn maxHeight;
},
addNewItem: function(liItem){
o//设置元素的位置
ovar imgItem = new ImgItem(liItem);
oimgItem.setPosition(this.getMinHeightColumn());
othis.cachedItems.push();
o//设置容器的高度
odocument.getElementById('img_list').style.height = this.getMaxHeight() + 'px';
}

基本的布局逻辑已经都齐了,还有的就是lazyload的一些逻辑了,这些逻辑都比较通用.加载后布局对象的逻辑是相同的.这里就不再赘述了.

点击下载源文件

抛砖引玉,如有谬误还请不吝指教.

转载请注明原文出处《瀑布布局的JavaScript实现方式》 如无特别声明,所有文章均遵守创作共用 署名-非商业-禁止演绎 3.0协议。