贴吧2012世界末日 - 初稿
发布时间:2013-08-06 20:51:30
发布时间:2013-08-06 20:51:30
目 录
1. 背景 1
2. 设计 2
2.1. 末日动画过程 3
2.2. 末日动画设计概述 3
3. 技术要点 4
3.1. 对动画过程的控制 4
3.2. 元素的旋转 7
3.3. JS缓动Tween.js 17
3.4. 水珠的均匀分布 18
4. 总结 20
Tag
CSS3 滤镜 Tween.js
摘要
揭秘2012吧世界末日特效技术内幕。为大家揭开末日特效中使用了哪些技术,以及关键技术点的重点说明。
贴吧2012末日特效,是为迎合玛雅末日预言推出的页面特效项目,目的是吸引新用户来到贴吧,宣传看图页,传达“无惧末日,勇敢新生,贴吧伴你身边”的主题,在拉新的同时提升贴吧品牌。
图 1 世界末日动画
图 2 末日新生
世界末日由两个项目组成,第一个为世界末日动画项目,为世界末日活动的关键项目。第二个为在23日上线的末日新生项目。新生项目中的动画相对简单很多,即在一片生机盎然的背景上生成水珠,当鼠标接触水珠时破裂。
word/media/image3.gif
世界末日项目中的动画,主要由以下几种形式组成:
● 震动:通过改变元素的marginTop和marginLeft完成。
● 摇摆:使用CSS3和Matrix滤镜对元素进行旋转。
● 坠落:通过改变绝对定位或相对定位的元素的top值完成。
● 断裂:通过CSS clip属性,将元素剪裁成若干块,然后分别完特定动画。
● 逐帧图片播放:典型的CSS Sprites。
对于每种分类,均可为其创建单独的类来完成特定的动画。
页面元素何时旋转,何时掉落,都依赖于动画的主控类“TimeLine”,它用于安排各个时间点应触发的动作,该类将在下文中详细说明。整个动画的编写过程即对TimeLine的初始化过程。
为保证动画过程的美观,为数不多的几个关键的元素采用了固定时间触发的方式,比如蓝条的断裂,搜索条的旋转等,对于大部分元素都采用了随机时间触发的方式,当然,随机也要随机到两次震动时间的区域内。
由于在动画结束后要播放一段视频,为保证视频播放流程,将在TimeLine的初始化期间就加载视频,为其缓冲。
末日特效动画过程中不需要与用户交互,不依赖用户的特定操作触发,因此完全是一个依靠时间触发的动画效果。所以我们需要一个时间线控制对象,其作用是某一个动画过程对象可向其注册在特定时间触发。
word/media/image4.gif
TimeLine相关的UML类图:
TimeLine内部依靠window.setInternal来确定当前时间,记录在time成员中,因此该实现无法精确的按照指定时间触发,而是当TimeListener指定的时间时在本次运行时间和下一次运行时间的区间内就执行,并从列表中移除该对象,即:
targetTime > time && targetTime < (time + interval)
TimeLine的时间间隔为16毫秒,之所以被设定为16毫秒,是因为一般显示器的刷新率为60Hz,即1秒刷新60次,那么1000毫秒除以60,得出16~17毫秒为最接近屏幕刷新率,用户看到的动画最为流畅。
该特效在代码中规定7秒钟的动画在实际执行中,Chrome需要11秒左右,火狐基本上与Chrome持平,在IE8中要14~15秒左右。除了整体时间上的延长外,所有动画的执行与指定时间点均有一定延迟。
延迟的主要原因是整个动画过程涉及的DOM较多,且部分动画涉及到图像变换,运行效率较低。如其是在IE8中,当对DOM进行旋转动画时,整个过程有比较明显的卡顿现象。
为了避免创造过多的setTimeout,因此采用了单个setInterval控制,但即使setInterval()为基础的动画循环比多套使用setTimeout()的动画循环高效,这里还是无法解决这个问题。由于JS为单线程,setTimeout和setInterval只是将要进行的要运行的代码添加到浏览器的UI线程队列中,因此如果队列中的其他代码没有执行完成之前那么该部分代码就不会得到执行。也就是说,前面的动画没有执行完,即便安排的时间到了,后面的动画也不会被执行。
对于执行延迟的问题没有在这次项目中得到解决。值得庆幸的是,虽然动画的整体时间延长,但整个动画的效果是可以接受的。对于未来如何解决该问题,考虑在现代浏览器使用原生的API:requestAnimateFrame方法,该方法通过在系统准备好绘制动画帧时调用该帧,从而为创建动画网页提供了一种更平滑更高效的方法。但是支持该方法的浏览器很有限,即便在现代浏览器中,也紧紧在高版本中得到支持,并且该API还没有标准化,各浏览器之间存在实现差异。
现代浏览器可以直接采用CSS3——transform属性完成旋转。CSS3的transform: rotate(deg)的旋转原点默认为元素的中心点。如下图:
为达到更加真实的摇摆过程,需要以左上角或右上角进行旋转,可以使用transform-origin属性,改变原点,其默认值为“50% 50%”,将其设为“0 0”即将原点移动到了左上角,达到如下效果:
无论是transform,还是transform-origin,目前都没有被标准化,在使用时需要加上浏览器前缀,例如:-webkit-transform,其对应的JS属性名为WebkitTransform。
需要注意的是在使用JS改变该属性时,IE10支持使用MsTransform和msTransform两个名称,而IE9仅支持msTransform(不按照标准来,明明第一个“m”应该大写)。
虽然IE9和IE10已经支持了CSS3的transform,但是作为目前市场占有率最高的IE8却不支持该属性,因此可以采用对IE6~IE8都有效的滤镜:
filter:progid:DXImageTransform.Microsoft.Matrix(SizingMethod= sMethod , Dx= fDx , Dy= fDy , M11= fM11 , M12= fM12 , M21= fM21 , M22= fM22 )
该滤镜是一个图像进行变换矩阵运算的实现,与CSS3中的transform:matrix()类似。然而,什么是变换矩阵呢?请看下一节。
变换矩阵的定义:
如果T是一个把Rn映射到Rm的线性变换,且x是一个具有n个元素的列向量 ,那么我们把m×n的矩阵A,称为T的变换矩阵。
变换矩阵应用范围很广,除IE的Matrix滤镜及CSS3的transform以外,在Canvas中也有对应的API。
对于二维图像的变换矩阵,可以表示为:
word/media/image8.gif
使用时如下:transform: matrix(a, b, c, d, e, f);
该变换为矩阵的默认值为:
word/media/image9.gif
即:transform:matrix(1, 0, 0, 1, 0, 0);
对于二维图像的变换矩阵实际上只需要一个2*2的矩阵,即a、b、c、d四个数组成,其e、f是用于进行仿射变换,进行对图像进行平移,需要补齐齐次坐标,变成了3*3的矩阵。
对于变换矩阵的运算,是用该DOM元素中的每个点的坐标组成一个1*3的向量矩阵与变换矩阵相乘,得出新的点的坐标,即该点移动的位置。需要指出的是矩阵变换的运算,其坐标的[0,0]点为图像的圆心。二维图像的变换矩阵的运算:
word/media/image10.gif
通过该公式,我们能得出以下公式:
● 旋转
绕原点逆时针旋转 θ 度角的变换公式是 与 ,用矩阵表示为:
● 缩放
缩放公式为 与 ,用矩阵表示为:
● 切变
切变有两种可能的形式,平行于 x 轴的切变为 与 ,矩阵表示为:
平行于 y 轴的切变为 与 ,矩阵表示为:
虽然以上变换均可以使用transform中的rotate、scale、skew等直接实现,那么为什么还需要matrix呢。
使用matrix可以做到以上属性无法完成的效果,例如镜像:
完成该效果,可以使用一下矩阵:
word/media/image24.gif
transform: matrix(-1, 0, 0, 1, 0, 0);
即将图像延y轴做镜像。
IE中滤镜格式:
filter:progid:DXImageTransform.Microsoft.Matrix(SizingMethod= sMethod , Dx= fDx , Dy= fDy , M11= fM11 , M12= fM12 , M21= fM21 , M22= fM22 )
其中的属性对应的变换矩阵为:
word/media/image25.gif
另一个非常关键的属性是SizingMethod,其可选值有两个,分别是:“clip to original”和“auto expand”,默认值为前者。其含义为:
“clip to original”:变换后,原dom的尺寸、位置不变,超出原dom的部分将被剪裁,如下图:
“autoexpand”:变换后原dom的尺寸按照变换后自动扩展。如下图:
从上图可以看到,实心的红色部分是紧邻绿色正方形的一个元素。绿色正方形使用auto expand进行变换旋转45度后,这时候获取其高度得到的实际上是其对角线的长度,但是实心红色矩形并没有因其高度增高而向下移动,也就是说,虽然旋转后会影响其宽度和高度,但不会影响到原本布局。
另外,从两幅图中可以看出,绿色正方形的旋转圆心不同,前者是以元素的左上角为圆心进行旋转,而后者的旋转实际上没有固定圆心。
当SizingMethod为auto expand时,会出现两个副作用,一个是Dx和Dy失效,这两个值不再起作用。另一个就是旋转方式的改变,其实际的旋转方式为沿着原DOM的两个上边缘和左边缘旋转,即:
这个问题在实际使用上给开发人员带来了很多的麻烦,由于clip to original是不符合预期的,因此我们只能使用auto expand,但是使用auto expand就不得不修正其旋转的位置问题。
为了修正其旋转的圆心,可以采用改变其marginLeft、marginTop或在相对、绝对定位下改变left、top的值,原理实际上都是靠挪动元素的原始位置达到修正的目的。
举一个比较简单的情况,以左上角为原点顺时针旋转为例:
我们已知O点将沿x轴向右移动,C点将沿y轴向上移动,因此我们实际上只要求出图中的CD线段得而长度,让元素向左移动CD长度的距离即可达到目的。我们已知旋转的角度,并且知道元素的宽度和高度,CD线段的长还是比较容易计算出来的。需要注意的是,完成旋转动画是一个持续改变元素旋转角度的过程,由于auto expand会改变元素的宽与高,因此不能直接使用元素的宽高进行计算,其依据旋转角度不同一直在变,需要使用元素在变换前的原始宽度和高度进行计算。
IE的Matrix滤镜的问题较多,如以下两个问题:
● 与clip属性搭配
在整个动画中,蓝色导航条的断裂是依靠复制两份其DOM元素,并使用clip做裁剪完成的。在CSS3 transform中,裁剪的区域会跟着一起做变换,因此得到的结果是与预期相符的。而在IE中,其clip区域会保持不变,并且不会因为元素旋转跟着一起旋转,仍然保持其矩形的区域。因此必须依据旋转的角度不停的改变其clip的区域保证元素能够正常显示出来。
图中的红色区域就是我们需要通过计算得出的clip区域。从图中我们也能看出,一旦涉及到clip,其旋转的边缘一定会被clip切割掉,整个旋转效果较差。
● 嵌套旋转
旋转的元素中,子元素也旋转,这种情况下预期的结果应该是子元素实际上旋转的角度为父元素的旋转角度与子元素自己的旋转角度之和。如下图,两个元素都旋转15度。
一般情况下Matrix滤镜也会得到相同的结果,但是如果子元素是relative元素,那么情况就会变得不一样了。
可以看到子元素的旋转完全独立,不再与父元素有任何关系,这一问题在修正其旋转原点后可能会变得更加难以接受,两个元素的位置的偏离较为严重。
为了让元素的整个旋转动画过程,表现出物体摇摇欲坠的真实感觉,我们需要使用一些算法来模拟一些物理效果。
Tween.js核心是以时间为维度,生成一个值到另一个值之间的部分,即补间值。以下图为例,x轴为我们的目标值,左上角为起始值,右下角为结束值。y轴为时间轴。
我们可以看到两个不同的算法生成的曲线。
以末日动画中图片的旋转为例。为求真实,图片最终旋转的角度应该是其对角线垂直于水平线时的角度,那么如果该图片为正方形,那么其旋转的角度就是45度。那么旋转的起始值为0,结束值为45。我们选择“elasticOut”,即上图中的绿色曲线,按照其给出的值在对应的时间改变元素的旋转角度,便完成了元素滑落左右摇摆的整个过程。
在末日新生中,水珠本身的动画与末日动画中的裂缝儿类似,都是靠CSS Sprites来完成的。其关键在于水珠的生成,第一,水珠总数不能过多,由于每个水珠都要创建一个DOM,因此过多的水珠会造成页面性能下降;第二,水珠的密度,水珠的位置不能集中在一起。因此,我们规定水珠的总数不能超过200个,且一个水珠的半径100像素内不能存在另一个水珠,每500毫秒生成一个新水珠(该速度可以保证那些有强迫症,一定要把所有水珠都弄破的用户,有点不完的水珠)。当然,在可视区域外生成水珠是没有意义的,生成的水珠应该在当前的可是区域内。
一个比较高效的办法是将当前页面划分成宽高均为100像素的格子,用一个二维数组表示每个格子。然后随机往这些格子中填充水珠,水珠在填充进格子时,可以随机一定的偏移量。这样的做法虽然无法完全保证两个水珠之间的距离大于100像素,但是还是可以有效的控制水珠的密度,且生成水珠时可以直接访问二维数组,快速定位那部分区域可以生成水珠。
但由于起初打算将该效果应用于看图页,而看图页是一个可无限下翻的页面,也就是说其页面高度在不断变化,每一次高度变化,都需要对格子数量进行更新。
考虑到水珠总数最多200个,即便需要遍历所有水珠的位置,也只是500毫秒执行一次,效率上没有太大的问题。因此最后采用了每次生成水珠时,先随机生成一个当前可见区域的像素点,然后遍历所有水珠,判断该点半径100像素内有没有其他水珠,如果没有,在当前点绘制一个水珠;如果有,停止本次绘制。之所以要停止本次绘制而不是再随机一个点再生成一次,是为了避免当水珠的数量饱和时程序陷入死循环。
世界末日项目给贴吧增加了一定的趣味性,用户反响较好,也让我们积累的不少宝贵经验,其中一些技术可以考虑用于提升用户体验上。毕竟,比起单纯的一动不动的页面,添加了一定的动画效果,增加了响应感的页面会让用户觉得更Cool更炫。当然我们也要记住,凡事不能过度,过多的动画与特效不但不能提高用户体验,反而会被用户扣分。