概括
作为直接面向用户的守门员,一个合格的前端当然要会做一些比较炫酷有意思的页面效果,现在后端会写页面了,测试会做管理后台了,只有高级动效能体现(装)价值(B)了。
实现方式
一般也就这几种了
- 纯css撸,使用transition animation
- svg
- gif
- canvas
- lottie
一、纯css撸
这种方式讲究慢工出细活,只要耐心,一般的动效都可以做出来
transition
只要设置的属性发生了变化即执行过渡效果,比较easy
/**
* property 过渡效果的 CSS 属性的名称
* duration 过渡效果时间
* timing-function 过度效果速度曲线 默认ease=慢快慢 linear=匀速 ease-in=慢开 ease-out=慢关 ease-in-out=慢开慢关 cubic-bezier(n,n,n,n)-自由配置0~1之间的数值
* delay 过渡效果延迟时间
*/
transition: property duration timing-function delay;
animation+@keyframes
一对好基友,啥动画都有!
@keyframes定义动画名称和具体执行效果,搭配transform可以有效实现元素的平移、旋转、缩放效果。
/**
* name 关键帧的名称
* duration 动画执行时间
* timing-function 动画执行效果 默认ease=慢快慢 linear=匀速 ease-in=慢开 ease-out=慢关 ease-in-out=慢开慢关 cubic-bezier(n,n,n,n)-自由配置0~1之间的数值
* delay 过渡效果延迟时间
* iteration-count 动画播放次数 常用infinite-无限 或 数字
* 其他属性用的较少,需要可细查
*/
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
如果项目使用了sass, 可以使用变量$test、@mixin代码块、@for循环来完成类似于流星雨、存钱罐等多个元素的动画控制
// $i 表示变量
// @for $i from <start> through <end>
// @for $i from <start> to <end>
$Company:1px; // 单位基数
$base-color: #1875e7;
@for $i from 1 through 3 {
ul:nth-child(#{$i}) {
transform:translateY(-60 * $Company) translateX( (random(160) - 30) * $Company);
animation-delay: random(10) * .1s;
animation-duration: random(10) * .1s + .7s;
background-color: lighten($base-color, $i * 5%);
}
}
另外,推荐一个开源的css效果库animate.css animate.style/ 如果只用到了一个或几个效果大可不必npm或者地址全量引入,直接打开线上源码ctrl+V出来即可。
png序列帧或精灵图
所谓的序列帧,就是N多张图片的快速切换,类似小时候玩的小人书。序列图片视频化技术实现方法很多
- 合在一张大图上,控制
background-position或者transform实现播放效果。此方法优点是方便快捷,但是,background-position定位性能不怎么样,只适用于小元素小动画,例如一些loading效果,如果是全屏的大图切换,则在移动设备上可以明显感觉到卡顿;- 图片一次性在页面上,依次控制显隐。如果序列帧太多,还是存在性能问题,而且不好监控所有图片加载完成控制
- 页面上一个
<img>元素,然后不断改变src地址,每次切换都是先去拿图片资源,效果不是很好,容易卡顿。
考虑到页面性能,目前较为常见的处理方案是,预加载所有序列帧图片,预加载完成之后,执行序列帧的切换播放
直接上代码了
// vue
data() {
return {
indexRange: [0, 143],
maxLength: 143,
eleContainer: '',
store: {
length: 0
}
}
},
mounted() {
this.eleContainer = document.getElementById('container');
this.initFrame(); // 初始化动画
},
methods: {
// 预加载图片
initFrame() {
const _this = this;
const urlRoot = "https://test.img.com/a/b/c";
// 图片序列预加载
for (let start = this.indexRange[0]; start <= this.indexRange[1]; start++) {
(function(index) {
var img = new Image();
img.onload = function() {
_this.store.length++;
// 存储预加载的图片对象
_this.store[index] = this;
_this.play();
};
img.onerror = function() {
_this.store.length++;
_this.play();
};
img.src = urlRoot + index + '.jpg';
})(start);
}
},
// 播放控制
play() {
const _this = this;
let percent = Math.round(100 * (this.store.length / this.maxLength));
// 全部加载完毕,无论成功还是失败
if (percent === 100) {
let index = this.indexRange[0];
let removeFront = true;
this.eleContainer.innerHTML = '';
// 依次append图片对象
var step = function() {
if (_this.store[index - 1]) {
try {
_this.eleContainer.removeChild(_this.store[index - 1]);
} catch (error) {
console.log(error);
}
}
_this.eleContainer.appendChild(_this.store[index]);
// 序列增加
index++;
// 如果超过最大限制
if (index <= _this.indexRange[1]) {
setTimeout(step, 62);
} else {
// 播放结束执行逻辑
}
};
// 等100%动画结束后执行播放
setTimeout(step);
}
}
}
写在最后:序列帧做动画的优点是播放可控,局部或者全部循环播放都可以在播放结束逻辑里执行。缺点是序列帧做动画适合做图片数量较少,或者说图片体积比较小的动画效果。实测整个序列帧图片包的体积控制在2M以内,这样预加载所有图片所需的时间可以控制在2s以内,不影响正常的页面加载感官效果。如果太大了,预加载时间太长了,得不偿失了,不仅体验差,用户的流量就是这么没的
svg
SVG是一种基于XML的,用于定义缩放矢量图形的标记语言。通过定义必要的线和形状来创建一个图形,也可以修改已有的位图,或者将这两种方式结合起来创建图形。
主要的绘制方式
<svg width="200" height="250" version="1.1" xmlns="http://www.w3.org/2000/svg">
// 矩形绘制
<rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
// 圆形和椭圆
<circle cx="25" cy="75" r="20" stroke="red" fill="transparent" stroke-width="5"/>
<ellipse cx="75" cy="75" rx="20" ry="5" stroke="red" fill="transparent" stroke-width="5"/>
// 直线和曲线
<line x1="10" x2="50" y1="110" y2="150" stroke="orange" fill="transparent" stroke-width="5"/>
<polyline points="60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145"
stroke="orange" fill="transparent" stroke-width="5"/>
// 多边形
<polygon points="50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180"
stroke="green" fill="transparent" stroke-width="5"/>
// 路径
<path d="M20,230 Q40,205 50,230 T90,230" fill="none" stroke="blue" stroke-width="5"/>
</svg>
如果要用纯svg做动效就要结合animation来实现动画效果,如果是复杂的图形效果,主要是用到path(路径)来绘制。类似骨架屏这种的,就可以用svg根据不同页面的布局结构很方便的做出来。
// html
<div id="home_skeleton" class="main-bone" style="display: none;"></div>
// css
.main-bone {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1000;
background: #f4f5f7;
transition: all .6s linear;
-webkit-transition: all .6s linear;
pointer-events: none;
}
// js
const skeleton = document.getElementById('home_skeleton');
skeleton.innerHTML = skeletonHtml; // skeletonHtml定义的svg结构
skeleton.style.opacity = '1';
skeleton.style = 'display:block';
svga
敲黑板,重点来了,SVGA 是一种同时兼容 iOS / Android / Flutter / Web 多个平台的动画格式。复杂的动画效果我们来画有点费劲而且还原度也很难让设计师满意。这个时候借助svga,让设计通过AE+svga插件,导出一个动画的svga格式的一个二进制文件,我们直接调用svga的API就可以直接展示动画,岂不简单粗暴,皆大欢喜。
安装
npm install svgaplayerweb --save
使用
// 方式一
// 定义展示块
<div id="demoCanvas" style="styles..."></div>
// 播放逻辑
var player = new SVGA.Player('#demoCanvas');
var parser = new SVGA.Parser('#demoCanvas'); // Must Provide same selector eg:#demoCanvas IF support IE6+
parser.load('rose_2.0.0.svga', function(videoItem) {
player.setVideoItem(videoItem);
player.startAnimation();
})
// 方式二
<div src="rose_2.0.0.svga" loops="0" clearsAfterStop="true" style="styles..."></div>
此外只要知道了图像的键名,还可以自己替换特定的图像,或者在特定的图像上添加文本
player.setImage('http://yourserver.com/xxx.png', 'ImageKey'); // 替换图片
player.setText({
text: 'Hello, World!',
family: 'Arial',
size: "24px",
color: "#ffe0a4",
offset: {x: 0.0, y: 0.0}
}, 'ImageKey'); // 添加图像文本
优缺点
优点/优势
- 方便简单,解放双手,节省大量脑细胞与时间。视觉提升显著,能够和设计师的关系更加融洽。
- 性能提升,比gif强一万倍
- 支持音频播放
- 跨平台,多端皆可svga 缺点/问题
- Android 4.x 兼容,操作系统缺乏 Blob 的支持,所以需要手动添加 Blob 到代码中
<script src="//cdn.bootcss.com/blob-polyfill/1.0.20150320/Blob.min.js"></script>
gif
这个不必多说了吧,额,虽然很方便,但是gif是位图,不高清,加载慢,消耗性能,酌情使用。
canvas
不得不说canvas真的是非常好的一件装备,无论是图表、刻度划尺、波浪图等等,在做一些比较简单而且需要逻辑交互的动效或者功能的时候,canvas是最合适的,自己做的东西我们想怎么玩就怎么玩。
Lottie
Lottie一个适用于Web,Android,iOS,React Native和Windows 的移动库,它可以使用Bodymovin解析以json 格式导出的Adobe After Effects动画,并在移动设备上进行本地渲染!
web端我们主要用到的lottie-web, 默认svg的渲染方式,核心原理是读取AE导出的配置json,然后用js操作svg API,动态创建元素并执行每一帧动画信息。lottie以其体积小,高还原度的优势,目前应用已经十分广泛流行。
安装
npm install lottie-web
使用
import lottie from 'lottie-web';
import * as animationData from "../test.json";
let myLottie = lottie.loadAnimation({
container: this.$refs.lottieBox,
renderer: "svg", // 渲染方式:svg、canvas
loop: true, // 循环播放,默认:false
autoplay: true, // 自动播放 ,默认true
animationData: animationData.default // AE导出的lottie json数据 与path互斥
// assetsPath: "" // json文件里资源的绝对路径,webpack项目需要配合这个参数
// path:
// "https://labs.nearpod.com/bodymovin/demo/markus/halloween/markus.json" // json 路径
});
// myLottie.addEventListener("complete", lottieLoaded); // 动画播放完成触发 loop为false执行
myLottie.addEventListener("loopComplete", lottieLoaded); // 当前循环播放完成触发 loop设置为true时执行
常用的事件监听
/**
* 其他监听事件
* enterFrame 播放一帧动画的时候触发
* config_ready 初始配置完成
* data_ready 所有动画数据加载完成
* DOMLoaded 元素已添加到DOM节点
* destroy 销毁
* */
一些常用的方法
/**
* lottie 全局方法
* play(): 播放动画。
* pause(): 暂停动画。
* togglePause(): 切换播放和暂停的状态。
* stop(): 停止动画。
* goToAndStop(value: number, isFrame: boolean): 跳转到某时间/帧并播放。
* goToAndPlay(value: number, isFrame: boolean): 跳转到某时间/帧并停止。
* setSegment(init: number, end: number): 设置当前的segment区间。
* playSegments(range: [number, number], force: boolean): 设置并播放当前的segment区间,force为true的时候讲立即播放,否则会等当前一循环播放完再切换。如果range[0] > range[1]则会设置方向为反播放。
* resetSegments(force: boolean): 重置当前的segment区间,force依然是是否立即生效的标志。
* setSpeed(value: number): 设置播放速度,基准为1。
* setDirection(value: -1 | 1): 设置播放正反,1为正,-1为反。
*/
关于lottie的实现原理以及源码解析,不再本文描述了,这个哥们总结的也比较详细了:zhuanlan.zhihu.com/p/38140917