本科毕业设计的前端技术总结(二):Video、SVG、Canvas与Font
前情提要:
上次说过了页面的总体设计,接下来本文把页面中的动效和其他部分效果的实现总结一下。
成品链接(放在了GitHub Pages,国内访问速度可能较慢)与GitHub仓库地址:
总目录
第一部分 响应式设计与浏览器兼容
一、概览
二、响应式设计
三、浏览器兼容
第二部分 Video、SVG、Canvas与Font (本文内容)
四、视频部分
五、Lottie动画部分
六、Canvas动画部分
七、自定义字体与拆分字体
八、其他效果与彩蛋
第三部分 Webpack配置、打包与懒加载
九、Webpack与各loader配置
十、开发与生产配置拆分
十一、模块懒加载
十二、其他减少请求数和压缩资源大小的方法
四、视频部分
1. 视频的动态加载
一般情况下,如果要定义一个自动播放的页面背景视频,标签定义大致上是这样的:
<video id="bg-video" src="../../assets/video/banner.mp4"
poster="../../assets/img/bg-video-poster.jpg"
autoplay loop muted preload="auto">
</video>
属性就不细说了,目前的设置会在页面加载后即载入整个视频,并自动播放、无限循环且静音,这个设置在PC端是没问题的。但是在第二节响应式设计中说到,移动端的页面放弃了所有背景视频,所以我们就有了动态加载视频的需求。为了阻止页面在移动端自动请求这个视频,有两种方案:
- <video>标签不写在html里,而是通过js在判断设备后动态添加标签至DOM;
- <video>标签写在html里,通过js在判断设备后调用视频对象的load()方法加载。
出于html和js尽量分离的考虑,我采用了第二种方法,因此在html里就要把autoplay属性去掉,并把preload="auto"改为preload="none"。这样即使页面里保留了这个标签,加载时也不会出现对这条视频资源的请求。在js里判断如果当前设备不是手机且支持播放视频,才开始加载:
if (Modernizr.video() && !device.mobile()) {
window.addEventListener('load', function () {
const bg_video = document.getElementById('bg-video');
bg_video.muted = true;
bg_video.addEventListener('canplay', () => bg_video.play());
bg_video.load();
}, false);
第一行涉及到上文讲过的 modernizr 和 device.js 的使用,而下面的代码由如下两个经验点:
(1)在页面加载完的事件里,先把视频对象静音(bg_video.muted =true),再调用load()方法手动加载。为什么这样做呢?事实上Chrome的自动播放政策(Autoplay Policy)在2018年4月做了更改,浏览器为了提高用户体验,减少数据消耗,只有muted的autoplay始终被允许。可我们明明把autoplay属性去掉了,按理说不适用这个规则啊?其实不是这样,通过js实现的“自动播放”也属于autoplay的范围内。经我测试只在标签上加muted还不够(有时会卡住不播放),所以在播放视频之前手动将视频对象的muted置true,这样才能成功播放。
(2)视频在调用load()之后为了确保能够及时自动播放,canplay事件一被触发就需要调用play()方法。canplay事件的含义是该视频虽然可能还没加载完成,但已经可以播放了。
经过如上的优化,非移动端下的视频已经保证能在任何情况下自动加载并播放。
2. 视频的加载进度条
为了提醒用户等待背景视频又不至于打扰到用户,我模仿了很多国外网站的做法把loading进度条放到了页面顶端,并加了阴影突出层次感。
这涉及到响应<video>标签的onloadedmetadata和onprogress事件。前者在成功获取视频元数据(包括视频时长等)时触发,后者在已加载视频数据的长度发生更新时触发。通过第一个事件取到视频总长度,通过第二个事件取到视频已加载的长度即可。我是用Vue来绑定事件的,代码片段如下:
// 上下文是Vue实例
bgVideoInit: function (e) { // 绑定onloadedmetadata事件
this.bg_video_duration = e.target.duration;
bgVideoLoadProgress: function (e) { // 绑定onprogress事件
var bg_video = e.target;
if (this.bg_video_duration > 0) {
var buffered = 0;
for(var i = 0; i < bg_video.buffered.length; i++) {
buffered += (bg_video.buffered.end(i) - bg_video.buffered.start(i));
if (buffered) {
var progress = Math.floor(buffered / this.bg_video_duration * 100);
if (progress>this.bg_video_progress) {
this.bg_video_progress = progress;
if (this.bg_video_progress === 100) {
$('#bg-video-progress').fadeOut();
需要注意的是,<video>加载视频时,为了能取到正确的已加载长度,我将每个buffer已加载的长度加起来作为结果。
除了背景视频,“作品”部分的预览视频其实也有loading进度条,原理相似,这里就不赘述了。
五、Lottie动画部分
页面中“作品”部分最显眼的就是一个SVG形式的动画,作为一个MG动画视频的预览,看上去比原视频还要清晰而锐利:
这其实是借助 Lottie-web 库实现的。Lottie是Airbnb推出的动效神器,简单来说就是可以把AE(Adobe After Effects)做出来的动画转化成json文件,并可以在网页端或移动端直接播放!有了AE这一业界顶尖的2D动画工具之一加持,动效什么的真的不是问题了。
关于Lottie,我之前写了一篇详尽的文章介绍它的使用,也是这个专栏的第一篇文章。现在已经将近两年过去了,这个库也由Bodymovin改名为lottie-web。我已经将原文章对应更改过,可以用来参考:
两年前在这个页面的开发过程中,我发现Bodymovin还是有很多局限,比如不支持AE里合成的倒放,json生成不出来,不知道现在修复了没有。当时我用了个trick,把“GOPRO”出现和消失的动画各生成一个json(其实消失的原始动画倒过来的,也是一种“出现”效果),在js里进行控制,实现无限循环交替播放:
// GOPRO字体动画,两段动画交替播放
var an1 = lottie.loadAnimation({
animationData: go1,
loop: false, autoplay: false,
renderer: 'svg',
container: $container1[0]
var an2 = lottie.loadAnimation({
animationData: go2,
loop: true, autoplay: false,
renderer: 'svg',
container: $container2[0]
var isAn2Stop = true;
$container2.hide();
an1.onComplete = function() {
an1.stop();
$container2.show();
$container1.hide();
isAn2Stop = false;
an2.play();
an2.onEnterFrame = function (e) {
// 由于an2是倒放,判断是不是到第0帧
if (e.currentTime < 1 && !isAn2Stop) {
isAn2Stop = true;
an2.stop();
$container1.show();
$container2.hide();
an1.play();
an2.setDirection(-1); // an2倒放
an1.play();
在渲染方式上采用了SVG,因为经过试验,我发现这是最适合这种动画的方式了。
六、Canvas动画部分
第二节提到过,为了在移动端去掉视频之后保持第一屏的动感,我使用了canvas动画。效果是这样:
这里借助了一个canvas动画库实现: Scrawl-canvas 。但事实上我不是很推荐这个库,第一用的人比较少,第二比较老旧(在npm上找不着),但真的很好上手,自成体系,你能想到的二维动画功能都有涵盖。如果实在找不到什么东西用就可以用这个。
Scrawl-canvas的官网 上有很多范例,文档也还算齐全。千万别被它官网的设计风格吓到,人家是故意复古的……
这个聚光灯动画并不直接包括在官方Demo里,只能找到探照灯效果(只有鼠标附近发出圆形的光,其他地方都是暗的):
这个时候就需要一点想象力了:探照灯效果只是一个径向渐变的半透明图层叠加在图片上,这个图层和鼠标一起移动;那么聚光灯不就是 两个线性渐变和一个径向渐变的叠加 么?
于是,把实现聚光灯需要的图层理清楚后,再加上每个图层的动画,这个动效就完成了。具体源码有点长就不放这里了,如果对这个库的使用感兴趣,虽然可以参考我源码里的src/scrawl_canvas_animate.js文件,但是还是推荐去看官方文档。为了尽可能地压缩Scrawl-canvas库的大小,我在引用的时候改过它的部分代码。
七、自定义字体与拆分字体
这个页面里几乎所有有字的地方都使用了不同粗细的思源黑体而不是系统自带字体。
但是如果把这些思源黑体文件全部打包到网页资源中,这个大小是不能忍受的。这也可能是大多数网页不常用自定义字体的原因之一。这个时候一个很自然的想法就出现了:一个汉语字体文件里面有几万个汉字的字形信息,但网页上可能只用到其中十几个字,我能不能只把这十几个字拆分出来做一个字体文件呢?实际上确实可以大幅压缩字体文件大小。
阿里巴巴有一个在线工具就是做这个事情的:
比如你的网页上会用到思源黑体极细,你只需要把用到这个字体的字全部收集起来,去一下重,粘贴到这个网页里,它会自动生成对应的拆分字体,甚至会告诉你怎么在css中引用。
在开发中,我的经验是把每个自定义字体涉及的字做一个本地备份(如我源码里的assets/fonts/font.txt),这样的话就算后来修改了网页上的文案,更新字体文件时也不用去重新统计网页上的字,直接对备份进行增删就可以了。
拆分字体的方案也有局限性:对于接受用户输入的<input>来说,拆分字体是没法拆的,你无法预测用户会输入什么,除非限定数字或英文字母。这个时候,就把系统可能自带的较为现代的字体往css上堆就行了,可以作为一项保底样式。
* {
font-family: PingFang SC,Helvetica Neue,Helvetica,Hiragino Sans GB,STHeitiSC-Light,Microsoft YaHei,Arial,sans-serif;
}
八、其他效果与彩蛋
除了上文提到的那些动效,其他动效用css3就可以实现。比如第一屏左下角这个比较像挊的动画就是一个标准到无聊的css3动画。
如果懒得自己实现css3动画,也可以使用现成的。比如 animation.css 库就囊括了一些常用的动画,这个网页里鼠标移上“关于我们”合影的效果就是直接使用了这个库提供的“tada”动画(感觉名字萌萌哒)。
比直接引用库还方便的动效就当属上文提过的 transition属性 了。比如“团队”部分,我只是设置了当鼠标hover的时候,有以下变化:
- 团队成员照片上叠加几个filter属性,比如高斯模糊、对比度增强和褐色化;
- 带有介绍的、具有半透明背景和内阴影的覆盖层显示出来。
但是由于transition属性的设置,这些属性都有了渐变过程,变得自然多了。
下面介绍一下彩蛋。最大的彩蛋就是当你打开开发者工具,会在控制台发现格式化的版权信息:
这涉及到console的格式化输出。
console.log('%cDEEP%c深度映像工作室%c\n%c ©2017 by DEEP IMAGING VISION STUDIO ',