简述年会新春特别奖的前端实现

2016-05-17 by Dron

firework.png

回乡心切,去年年底我早早的就撤了,年会并没有参加,亲手为年会定制的一个烟花抽奖程序——新春特别奖,年会时的现场秀也因此没能感受到,遗憾之余听说 @文龙 中了头奖,心里平衡了许多:三天时间的开发总算没白忙活!

写本文前,我将程序简单做了下脱敏和静态分离:

进入页面后,片头动画开始播放,「你」划燃火柴,转身面向炮仗,进入等待状态(此时应该跟观众唠唠家常,提高下神秘感);待唠瞌尽兴,「你」将火柴移向并点燃引信,待引信燃尽,炮仗应声升空,爆炸,焰火奇迹般的组成一个花名(中奖者),拍照留念,背景持续燃放烟花,完毕。

本文将介绍该效果的前端实现。

内部代码开源流程走起来麻烦,就不给源码了,反正原理很简单。

视频与画布

实际上,整个过程分为 Video 视频和 Canvas 动画两部分,从进入页面起,到出现淘宝城背景之前,都属于时长 21 秒钟的 animate.mp4,写到这里,我仿佛听到先前以为是 CSS3 实现的同学发出「茄 qié」字的感叹!

<div class="container">
  <div id="video-container">
    <video preload src="animate.mp4"></video>
    <canvas></canvas>
  </div>
</div>

PS:去年学了点视频制作还是能派上用场滴,玩法平台首页的模块动效也都是 mp4。:) PS2:特别感谢 @聪酱 同学为本次年会的抽奖程序提供美术支持!

有人会问,为什么不干脆整个都视频得了?这么做目的是为了后面的烟花能按需爆出服务器端抽中的花名,上面的 demo 多放几遍就会发现,最终爆出的花名是 JS 随机的。

等待点火

视频播放大约 10s,进入等待状态,为了保持火苗的动画,这里的处理不能直接暂停视频,而是大约在 556~616 帧之间持续循环播放。

为此,我写了一段 JS 用于循环播放的控制,大致原理是侦听视频 timeupdate 事件,检查当前播放进度,只要视频位置在 616~676 帧之间,则立即回退 1s,为了方便,我将视频里火苗的循环做成刚好 1 秒钟。

var videoHolding = [ 616 / 60 * 1e3, 676 / 60 * 1e3 ];

videoWatcher.watch( videoHolding, function( relativeTime, absoluteTime, video ){
  video.currentTime -= 1;
} );

值得注意的是:timeupdate 事件实测每秒钟仅触发 2-4 次,如果精度要求高的话,timeupdate 不能满足需求,可改用 requestAnimationFrame 主动扫描。

设计此环节,是为了老大在抽奖之前可以跟大家多唠唠嗑,谁知现场演示时给来了这么一出:大 Boss 讲完话后直接突然说「点火」,当时浏览器还并未进入该页面,@文龙 直接操作进入页面与点火动作一气呵成,中间并没等待状态,这个功能白写了,好在并不复杂。:)

花名呈现

观众摒住呼吸等了 20 几秒,主角终于要登场了。在视频播放结束后,Canvas 开始工作,它的任务是:爆出一个名字、快照以及背景持续燃放的烟花。

文字棚格化

在 Canvas 上绘制五颜六色的圆圈来表示焰火粒子,这些粒子要排成貌似文字的样子,它们的位置须来源于文字的渲染像素,CanvasRenderingContext2D 的 getImageData 方法可以帮助我们获取画布上指定矩形的像素数据。

text-pixelate.png

在离屏 Canvas 上绘制文字,通过 getImageData 取得像素数据 ImageData,ImageData 保存了所有像素点的 RGBA 四个通道的颜色信息,这里我们只需要用 Alpha 通道就够了,扫描所有像素点,记录下 Alpha 值不等于 0 的像素的坐标,这些坐标将用作焰火粒子在空中的位置。

Alpha 值等于 0 的像素点,无论 R、G、B 是否有值,该点都是完全透明(不可见)的,是一个空点。实际上,getImageData 得到的 ImageData 里,某点的 Alpha 值为 0 时,它的 RGB 通道上的值也都是 0。

鱼眼滤镜

为了使爆出来的文字更加真实好看,我特意对粒子加了类似鱼眼的滤镜效果,使原 x 轴离起爆点越远的粒子,y 轴离起爆点越近,同时使原 y 轴离越爆点越远的粒子,x 轴离起爆点越近:

fisheye.png

快照效果

起先是没有快照的,焰火形成文字后很快就散落开来,主办方觉得不行,观众稍不留神就没看清是谁中奖了,在一番「怎么调停留时间都不如意」后,突发奇想做了快照效果,让名字永久停留于空中。

take-photo.png

实现起来很简单,说破不值钱,该效果要归功于 HTMLCanvasElement 的 toDataURL 方法,toDataURL 返回一个包含当前画布图像内容的 dataURI 地址,将该地址做为 src 赋值给一个备用的 img 标签即可展现照片。至于旋转、缩放等,CSS3 就可以了。

(本文完)