图片瀑布流展现的实现
上一篇结尾处说再接再厉,希望能多写点东西。半个多月过去了,还没有动静。果然男人的话是不可信的😓 。为了挽回一点仅有的信用,还是赶快写点东西吧。
现在有些有较多图片展示的网站会选择使用瀑布流的方式实现。上周随手写了一个JS脚本来实现此功能,下面介绍实现过程。
首先说明一下瀑布流展示的特点:图片定宽不定高(当然个别情况宽高都不定,不过这种情况不在我们讨论的范围内);下一张图片会被放置到当前高度最小的一列。基于上述的两个特点,该如何布局呢?首先想到的是浮动布局,但无论是向左浮动还是向右浮动,都会如下面截图中的布局一样,无法满足要求:
向左浮动
向右浮动
既然 float
无法满足,只能换其它思路了。正常的文档流实现不了这种跳跃式的布局,看来只能跳出文档流之外了。由此可以想到使用 position: absolute
来布局。布局某张图片时,动态计算当前高度最低的列,然后通过设置图片容器的 left
值来放置图片到那一列,再通过设置 top
值来设置图片的垂直位置,最后更新这一列的高度。
具体实现上,定义了一个叫 Waterfall
的构造函数,需要传递如下的配置参数:
其中 container
参数是瀑布流的父容器节点,为必须参数,其它参数可选。将传递的参数 extend (内部有实现 extend
方法。由于是原生JS实现,未使用jQuery等工具库,所以一些工具函数需要手动实现)到运行时的 this
对象(即 Waterfall
对象)上后,调用 init
(内部私有函数,对于不需要用户调用的方法,我一般习惯封装成私有函数)方法进行初始化。最后调用 render
(此瀑布流实现中只需暴露了一个 render
方法给用户)方法渲染出瀑布流布局。
上面提到的 init
初始化函数做了三件事:获取图片容器(这里假定每张图片的外层都会有容器元素包裹,而不是光秃秃的图片并排排列,且每个图片容器的宽度相同。如果没有包裹或宽度不等,只能呵呵了)的盒模型的大小;计算总共有多少列;由于用到 position: absolute
布局,所以需要给瀑布流容器 container
设置 position: relative
CSS 属性。
上面说到的计算图片容器盒模型的大小需要展开说明一下。构造函数 Waterfall
的参数中有一个 itemWidth
的参数,代表了图片容器元素的宽度(此处的宽度,我把它当做是元素盒模型的总宽度,即 width + padding + border
,而非元素内容的宽度 width
,此处,我觉得这样更合理,也方便调用者处理),如果有就使用此值,如果没有,则获取第一个图片容器的 width + padding + border
作为 itemWidth
。还有一点需要说明,如果图片容器有设置 CSS 属性 box-sizing: border-box
,则其 width
就已经是内容宽度、padding
以及 margin
之和了,所以就不用再相加了。
在调用 render
方法渲染瀑布流布局时,有一点需要特别注意:图片是需要通过网络加载的,因此正常情况下,当运行 render
方法时,图片应该还没有加载完(如果是读缓存的话,可能此时已经加载完了。当然如果是在 window.onload
事件处理中调用的 Waterfall
构造函数,则此时图片都已经加载完成了。但这样的话,一是如果有大量图片的话,会等很久才会渲染出瀑布流效果,二是如果有分页异步加载的情况,依然会有此问题。),所以需要针对图片的加载做处理。此处首先查找出所有的图片,然后遍历每张图片,判断图片是否加载完成(img 元素会有一个 complete
属性,如果已经加载完成,则此属性为 true,否则为 false)。如果已加载完成,则直接计算布局渲染出来,否则的话,给图片添加 onload
事件,onload
事件触发后再计算布局渲染。
还有几个细节需要提一下。渲染图片时,由于需要设置几个样式属性,为了减少可能的 reflow/repaint
,将多个样式属性组合起来,一次赋值给 style.cssText
;图片渲染时默认添加了动画效果,开始是准备先 display: none
隐藏图片,并设置 transition: display 1s
的 CSS 属性来设置动画效果,待加载完成后通过 display: block
动画效果显示出来。但是最后发现 display
属性不支持动画效果,包括使用其它属性和它组合动画也不行。于是改用 visibility
。但直接使用 visibility
也是无法实现动画效果,结合 opacity
后才实现。
好了,大致实现过程介绍完了,做了一个 demo ,链接如下:
代码在 Github 地址如下: