图片墙特效和 Bin Packing

截图

预览:http://ambar.no.de/demos/binpacking/index.html

截图

关于

Wiki:Bin packing problem

Demo: Bin Packing

我看到原文是 lightmap 拼接,还有 CSS Sprite 自动化处理,有了这些印象之后,我想图片展示也是用途之一。

装箱问题我想大概是两类:

  1. 提供一个大箱子,还有很多小箱子。每个小箱子的大小不一样,如何尽可能多的放进大箱子中。
  2. 不限定大箱子大小,寻找一个最合适尺寸的大箱子,把一批小箱子装起来。

如何做

数据结构定义:每一块空间做为一个节点,节点的值定义为一个矩形。

  1. 根节点定义为整个容器
  2. 每一次分区检查条件,根据目标节点的大小,再划分出两个节点,即目标节点的左右分支。
  3. 不断重复二,形成一颗树。

当要插入一个新的块的时,查找位置就到整个树中寻找匹配的节点。

每一个块也定义为一个矩形,所有的块定义为一个列表:


blocks = [
{ w: 300, h: 390 }, 
{ w: 150, h: 195 }, 
{ w: 300, h: 390 }, 
]

取出要插入的块时,有很多选择:
1) 宽度优先 2) 高度优先 3) 面积优先 4) 大边优先 5) 随机

根据应用的不同来决定了处理的优先顺序,而不同的顺序可以采用不同的分区策略。

我要做的是图片墙,因此选择了随机顺序。这样在刷新页面之后显得比较有新意。

分区策略

我观察了三种不同分区的算法:

  • simple-packer

    目标恰好排除到节点分区之外
    目标方块区域不计入节点。这里,只插入一个块时,分区只进行一次,packer 树上存在三个节点。图示:

  • growing-packer

    把首个插入的方块当做根节点,然后往它的下方或右方自适应增长。
    它不适用于随机生成顺序的情况,大方块在后面的的话,排列时直接被丢弃了。图示:

  • bin-packer

    直到给目标划分了节点
    上面参照 blackpawn 的实现,每一个插入的方块必然被一个节点包含;每一次分区就进行预判,决定横向还是纵向分区。
    因此当第一个方块插入时就已经存在五个节点了。图示:
    横向
    纵向

代码

可视化

不能图形化的东西是很难了解的,因此我加了两个可视化效果来增强理解。

可视化有两部分:1) 树的形状; 2) 高亮分区块的动画。分别点击 示例 中的 "draw tree" 和 "highlight block" 按钮。

高亮动画

递归高亮分区顺序中的每个节点,使用 Jscex 能简单地在中途模拟 sleep 效果;高亮按钮每点击一次将使高亮速度翻倍。

绘制树

绘制工具我用的 Raphaël ,它在主流浏览器使用 SVG 绘制图形,低版本 IE 中自动切换 VML 绘制图形。

绘制办法我想出了两种:

  • 一是横向平铺。遍历树时记住每一层节点的数量再平均分布。这种方式占屏幕空间比较小,图示加了点干扰:
  • 二是纵向平铺。绘制目标节点时,计算出所有后代节点的数量,再根据数量和父节点偏移目标。

注:树的每个节点击将高亮对应的块。

验证

对这个特定示例测试时,由于三种模式策略完全不同,它们排列时达到完美效果的概率也不同,尝试10万次运行统计如下:

Mode            耗时     完美次数/尝试次数
bin-packer      947ms    93598/100000
growing-packer  993ms    29349/100000
simple-packer   753ms    25672/100000

为了提高成功机率,可以加点改进。由于我的示例是人工定义好了块的数量,因此可以定义一个模式:


patterns = [ 
  { blocks:['300x390x2','150x195x10'], w: 900, h: 585 }
]

如果发生图片切换时,图片大小和数量正好匹配了这个模式,就让机器重复去尝试布局吧!

假定概率是 .93598 完美排列,看看 10 人亿次访问时,小于 1 人查看排列失败的情况,要至多重试多少次呢?


var prop = function(people,failed,p0) {
  var times = 0, p, f;
  while(++times){
    p = Math.pow(1-p0,times)
    if( Math.round( f = people * p ) <= failed )
      return [times,p0,1-p,f]
  }
}
// => [8, 0.93598, 0.9999999997178206, 0.2821794342917953]
console.log( prop(1e9,1,.93598) )

结果是,只要至多尝试 8 次,这点时间对机器是微不足道的。

总结

多多观察,找出各种问题间的联系,发现相似的解决办法,一点点付出就可以得到梦幻般的图片墙展示效果。

示例中还尝试了一些合适的技术,有益于快速地测试和验证。

  • Modernizr 用于检测 CSS 3 变换模块支持,不支持的情况回退 jQuery 动画
  • Raphaël 用于 SVG 图形,带交互和动画,语法类似于 jQuery
  • ES 5 Shim 让低版本 IE 中的JavaScript 写起来爽快一点
  • Jscex 用于高亮块,异步动画
  • Aristo 主题 UI,按钮样式
posted @ 2012-01-11 20:20  ambar  阅读(1118)  评论(0编辑  收藏