前端项目直面客户,为了更好的交互体验,免不了须要使用动画来增香提味。在此分享自如动画的尝试与探索。css
使用transition实现:位移、旋转、缩放、透明度等简单过渡效果。 html
优势: 流畅、简单。前端
缺点: 仅能实现简单动画,仅能监控动画结束(transitionend),没法实现用户交互。vue
<template>
<div class="box1" ref="box1" :class="{move: isMove}" @click="isMove=!isMove"></div>
</template>
<script lang="tsx">
import { Component, Vue } from 'vue-property-decorator';
@Component({ name: 'index' })
export default class Index extends Vue {
private isMove: boolean = false;
private transitions: any = {
'transition':['transitionend'],
'OTransition':['otransitionend'],
'MozTransition':['mozTransitionEnd'],
'WebkitTransition':['webkitTransitionEnd']
};
mounted(){
let _style = (this.$refs.box1 as any).style;
for(let t in this.transitions){
if( _style[t] !== undefined ){
this.transitions[t].map((element:string)=>{
(this.$refs.box1 as any).addEventListener(element, ()=>{this.callBack(element)}, false);
});
}
}
}
callBack(type:string){
// do something
console.log(type); // transitionend
}
}
</script>
<style lang="scss" scoped>
.box1{
width: 100px;
height: 100px;
background: #999;
transition: all 0.5s;
&.move{
transform: translateX(100px);
}
}
</style>
复制代码
动画的速度曲线 animation-timing-function :
css3
除了以上常规用法,还有一个实用的函数:git
阶梯函数:steps(n,direction),这个函数可以起到定格动画的效果。阶梯函数不像其余定时函数那样,平滑的过渡,而是以帧的方式过渡。github
n:阶梯数(必须是一个正整数),它将动画的总时长按照阶梯数等距划分web
direction:可选值为start或end,默认end。npm
start表示动画的第一帧会被当即执行,直接从第二帧开始,而后以第一帧结束;canvas
end则表示动画从第一帧开始到正常结束; **优势:**能实现较复杂的动画
**缺点:**图片资源容易过大,
1)生成雪碧图(横向纵向都可),在线生成雪碧图地址:
www.toptal.com/developers/… 2)使用animation的steps实现动画:
<template>
<div class="canvas">
<div v-for="(item,index) in list" :key="index" :class="`step ${item.className||''} ani-${item.imageDirection=='v' ? 'tb' : 'lr'}-step-${item.steps||1}`"
:style="{
'backgroundImage':`url(${item.backgroundImage})`,
'width':`${item.width}px`,
'height':`${item.height}px`,
'top':`${item.y/75}rem` ,
'left':`${item.x/75}rem` ,
}"></div>
</div>
</template>
<script lang="tsx">
import { Component, Vue } from 'vue-property-decorator';
import { girl, paper, son, run } from '../assets';
interface AnimateItem{
backgroundImage: string; // 图片地址
x: number; // 定位x轴,750宽度下px值
y: number; // 定位y轴,750宽度下px值
width: number; // 宽,单位px
height: number; // 高,单位px
imageDirection?: 'v' | 'h'; // 图片是横向仍是纵向
steps?: number; // 图片steps
className?: string; // class类名
}
@Component({ name: 'index' })
export default class Index extends Vue {
private list: Array<AnimateItem> = [
{ backgroundImage: son , x: 185, y: 20, width: 185, height: 319, imageDirection: 'v', steps: 18},
{ backgroundImage: paper , x: 15, y: 30, width: 115, height: 175, imageDirection: 'v', steps: 24, className: 'paper'},
{ backgroundImage: girl , x: 115, y: 589, width: 320, height: 391.5, steps: 12},
{ backgroundImage: run , x: 515, y: 569, width: 88.83333, height: 54, steps: 6},
];
}
</script>
<style scoped lang="scss">
.canvas {
position: relative;
width: 100%;
height: r(1305);
.step {
position: absolute;
// 必定不能写该属性,不然动画会有问题
// background-repeat: no-repeat;
}
.paper{
transform-origin: left top;
transform: scale(.65);
}
}
// 高图
@each $step,$time in (18,1.8),(24,4) {
.ani-tb-step-#{$step}{
background-size: 100% auto;
animation: step-tb-#{$step} $time*1s steps($step) infinite;
}
};
// 宽图
@each $step,$time in (12,1.5),(6,3) {
.ani-lr-step-#{$step}{
background-size: auto 100%;
animation: step-lr-#{$step} $time*1s steps($step) infinite;
}
};
// 高图动画
@each $steps in 18,24 {
@keyframes step-tb-#{$steps} {
0% {
background-position: 0 0;
}
100% {
background-position: 0 #{-1*$steps*100%};
}
}
}
// 宽图动画
@each $steps in 12,6 {
@keyframes step-lr-#{$steps} {
0% {
background-position: 0 0;
}
100% {
background-position: #{-1*$steps*100%} 0;
}
}
}
</style>
复制代码
// src/assets/index.ts
export { default as girl } from './banner/girl.png'
export { default as paper } from './banner/paper.png'
export { default as son } from './banner/son.png'
export { default as run } from './banner/run.png'
复制代码
1)动画元素使用绝对定位(absolute/fixed),使其脱离文档流,有效避免重排。
2)使用transform:translateY/X来移动元素,而不是修改left、margin-left等属性
3)逐帧动画(雪碧图+animation steps)元素宽高使用px。
4)必定不能写 background-repeat: no-repeat; 属性,不然动画会有问题
5)出现卡顿或闪烁,能够开启硬件加速
.cube {
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-perspective: 1000px;
-moz-perspective: 1000px;
-ms-perspective: 1000px;
perspective: 1000px;
/* Other transform properties here */
}
复制代码
在webkit内核的浏览器中,另外一个行之有效的方法是:
.cube {
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
/* Other transform properties here */
}
复制代码
<template>
<div class="box1" ref="box1" :class="{move: isMove}" @click="isMove=!isMove"></div>
</template>
<script lang="tsx">
import { Component, Vue } from 'vue-property-decorator';
@Component({ name: 'index' })
export default class Index extends Vue {
private isMove: boolean = false;
//监听animation动画开始、结束、重复运动方法
private animations = {
'animation':['animationstart', 'animationend', 'animationiteration'],
'OAnimation':['oanimationstart', 'oanimationend', 'oanimationiteration'],
'MozAnimation':['mozAnimationStart', 'mozAnimationEnd', 'mozAnimationIteration'],
'WebkitAnimation':['webkitAnimationStart', 'webkitAnimationEnd', 'webkitAnimationIteration']
};
mounted(){
let _style = (this.$refs.box1 as any).style;
for(let t in this.animations){
if( _style[t] !== undefined ){
this.animations[t].map((element:string)=>{
(this.$refs.box1 as any).addEventListener(element, ()=>{this.callBack(element)}, false);
});
}
}
}
callBack(type:string){
console.log(type);
let _type = type.toLowerCase();
if(_type.endsWith('animationend')){
this.isMove=false;
}
}
}
</script>
<style lang="scss" scoped>
.box1{
width: 100px;
height: 100px;
background: #999;
&.move{
animation: move 1s 2; // 循环2次
}
}
@keyframes move {
0%{
transform: translateX(0px);
}
50%{
transform: translateX(100px);
}
100%{
transform: translateX(0px);
}
}
</style>
复制代码
打印结果: 注:
让动画停留在某一帧(通常是最后一帧)的方法:
Lottie是Airbnb推出的支持Web、Android、iOS等多平台的动画库。设计师在AE上完成动画后,可使用它在AE中的插件Bodymovin输出一个Json格式的文件,Json文件中就包含了制做动画所包含的各类图层元素及效果的动画关键帧等内容。
lottie官方:airbnb.design/lottie/
效果演示:
SVGA 是一种跨平台的开源动画格式,同时兼容 iOS / Android / Flutter / Web。
SVGA 除了使用简单,性能卓越,同时让动画开发分工明确,各自专一各自的领域,大大减小动画交互的沟通成本,提高开发效率。动画设计师专一动画设计,经过工具输出 svga 动画文件,提供给开发工程师在集成 svga player 以后直接使用。
SVGAPlayer-iOS:github.com/svga/SVGAPl…
SVGAPlayer-Android:github.com/svga/SVGAPl…
SVGAPlayer-Flutter:github.com/svga/SVGAPl…
SVGAPlayer-Web:github.com/svga/SVGAPl…
SVGAPlayer-WeChat:github.com/svga/SVGAPl…
1)安装SVGAPlayer npm install svgaplayerweb --save
2)导入 import SVGA from 'svgaplayerweb';
3)入需支持音频播放,引入 <script src="https://cdn.jsdelivr.net/npm/howler@2.0.15/dist/howler.core.min.js"></script>
4)添加容器 <div id="testCanvas" style="styles..."></div>
或 <canvas id="testCanvas" width="750" height="750"></canvas>
5)加载动画
var parser = new SVGA.Parser(); // 建立解析器
var player = new SVGA.Player('#testCanvas'); // 建立播放器
// 只能加载跨域容许文件
parser.load("../kingset.svga", videoItem => {
player.setVideoItem(videoItem);
player.startAnimation();
}, error => {
// alert(error.message);
})
复制代码
用于控制动画的播放和中止 1)属性:
loops: number;
// 动画循环次数,默认值为 0,表示无限循环
clearsAfterStop: boolean;
// 默认值为 true,表示当动画结束时,清空画布。
fillMode: "Forward" | "Backward";
// 默认值为 Forward,可选值 Forward / Backward,
// 当 clearsAfterStop 为 false 时,
// Forward 表示动画会在结束后停留在最后一帧,
// Backward 则会在动画结束后停留在第一帧。
复制代码
2)方法: 动态图片(只能加载跨域容许文件)
// setImage(urlORbase64: string, forKey: string)
// urlORbase64:图片地址
// forKey: ImageKey
player.setImage('../avatar.png', '99')
复制代码
动态文本
// setText(textORMap: string | {text: string,size?: string,family?: string,color?: string,offset?: { x: number, y: number }}, forKey: string)
// forKey: ImageKey
// 默认文字样式:14px 黑色
player.setText({
text: '个人女王',
family: 'Arial',
size: "30px",
color: "#fff",
offset: {x: -10, y: 2}
}, 'banner');
复制代码
播放动画
// startAnimation(reverse: boolean = false);
// reverse: 是否反向播放动画
player.startAnimation();
复制代码
播放 [location, location+length] 指定区间帧动画
// startAnimationWithRange(range: {location: number, length: number}, reverse: boolean = false);
// reverse: 是否反向播放
player.startAnimationWithRange({location: 15, length: 35}, false);
复制代码
暂停在当前帧 pauseAnimation();
中止播放动画,若是 clearsAfterStop === true,将会清空画布 stopAnimation();
强制清空画布 clear();
清空全部动态图像和文本 clearDynamicObjects()
3)回调方法: 动画中止播放时回调 onFinished(callback: () => void): void;
动画播放至某帧后回调
// onFrame(callback: (frame: number): void): void;
// frame: 当前帧
player.onFrame(frame=>{
if(frame==50){
// do something
}
});
复制代码
动画播放至某进度后回调 onPercentage(callback: (percentage: number): void): void;
注:
1)什么是apng图片?
APNG是普通png图片的升级版**(能够动的png)**,它的后缀依然是.png,能够展现动态,支持全彩和透明(最重要),向下兼容PNG(包含动态的状况下体积会比普通静态png大出数倍,但文件体积比gif小且效果更好,能够压缩)。
2)apng VS gif
颜色 | 画质 | 透明 | 兼容性 | |
---|---|---|---|---|
gif | 8 位 256 色(色阶过渡糟糕,图片具备颗粒感)GIF 每一个像素只有 8 bit,也就是说只有 256 种颜色,因而不少人误觉得 GIF 不支持 24 bit RGB,但实际上,GIF 的限制是每一帧最多只能有 256 种颜色,可是每种颜色能够是 24 bit 的。 | 差 | 不支持 Alpha 透明通道,边缘有杂边。不支持半透明,只支持彻底透明或者彻底不透明,若是把一个边缘是半透明的图片转换成 GIF,就会出现另外一个答案中提到的杂边问题 。 | ALL |
apng | 24 位真彩色图片 | 好 | 支持 8 位 Alpha 透明通道,透明度能够有 256 级 | Firefox、Safari、Chrome |
3)apng VS 逐帧动画 |
逐帧动画 | apng展现 | |
---|---|---|
文件 | ![]() |
戳此查看apng按扭>> |
文件体积 | 222kb | 压缩前:200kb;压缩后:112kb |
4)一款高效、好用的图片处理工具——Cherry |
官网:yyued.github.io/cherry/ 功能:
1)简单补间动画(位移、旋转、缩放、透明度): 使用css3,保证性能是第一位
2)稍复杂小动画(动画简单、尺寸小,循环无交互): 使用apng,好实现、体验好(设计师导出序列帧或者svga,使用文末提到的工具转成apng图片,注意若是动画复杂且尺寸较大,apng的图片会很是大)
3)复杂动画(动画复杂,尺寸较大,或者有交互): 使用svga,文件体积小,开发成本低,效果好,能实现交互。
做者简介:暴力燕,自如大前端开发中心-客端研发组