前端我的项目直面客户,为了更好的交互体验,免不了须要应用动画来增香提味。在此分享自若动画的尝试与摸索。
一、简略的过渡成果 transition
应用 transition 实现:位移、旋转、缩放、透明度等简略过渡成果。
长处:晦涩、简略。
毛病:仅能实现简略动画,仅能监控动画完结(transitionend),无奈实现用户交互。
transitionend(完结)事件监控:
<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:
除了以上惯例用法,还有一个实用的函数:
阶梯函数:steps(n,direction),这个函数可能起到 定格动画 的成果。阶梯函数不像其余定时函数那样,平滑的过渡,而是以 帧的形式过渡。
n:阶梯数(必须是一个 正整数 ),它将动画的总时长依照阶梯数 等距划分
direction:可选值为 start 或 end,默认end。
start 示意动画的第一帧会被立刻执行, 间接从第二帧开始,而后以第一帧完结;
end 则示意动画从第一帧开始到失常完结;
长处:能实现较简单的动画
毛病:图片资源容易过大,
仅能监控动画开始(transitionstart)、完结(transitionend)、反复(animationiteration),无奈实现用户交互。
1、实现
1)生成雪碧图(横向纵向均可),在线生成雪碧图地址:
https://www.toptal.com/develo…
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'
预览地址:
https://topic.ziroom.com/2021…
css3 动画注意事项:
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 */
}
2、animationstart(开始)、animationend(完结)、animationiteration(反复静止)事件监控:
<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: any = {'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>
打印后果:
注:
让动画停留在某一帧(个别是最初一帧)的办法:
1)监控 animationend,增加新的 class 类,设置 background-position 属性(例如:0 -300%)。(animation-fill-mode:forwards; 针对 transform 形式批改的属性在动画完结时能够保留状态,background-position 的扭转不会起作用)
三、Lottie(扁平化格调,门路剪辑动画)
Lottie 是 Airbnb 推出的反对 Web、Android、iOS 等多平台的动画库。设计师在 AE 上实现动画后,能够应用它在 AE 中的插件 Bodymovin 输入一个 Json 格局的文件,Json 文件中就蕴含了制作动画所蕴含的各种图层元素及成果的动画关键帧等内容。
lottie 官网:https://airbnb.design/lottie/
反对性能列表:https://airbnb.io/lottie/#/su…
四、SVGA
成果演示:
https://topic.ziroom.com/2021…
SVGA 是一种跨平台的开源动画格局,同时兼容 iOS / Android / Flutter / Web。
SVGA 除了应用简略,性能卓越,同时让动画开发分工明确,各自专一各自的畛域,大大减少动画交互的沟通老本,晋升开发效率。动画设计师专一动画设计,通过工具输入 svga 动画文件,提供给开发工程师在集成 svga player 之后间接应用。
SVGA 官网:http://svga.io/index.html
1、集成指南(将播放器集成至 iOS / Android / Flutter / Web / WeChat):
SVGAPlayer-iOS:https://github.com/svga/SVGAP…
SVGAPlayer-Android:https://github.com/svga/SVGAP…
SVGAPlayer-Flutter:https://github.com/svga/SVGAP…
SVGAPlayer-Web:https://github.com/svga/SVGAP…
SVGAPlayer-WeChat:https://github.com/svga/SVGAP…
2、应用办法(Web):
1)装置 SVGAPlayernpm 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("//static8.ziroom.com/topic/2019/svga_test/kingset.svga", videoItem => {player.setVideoItem(videoItem);
player.startAnimation();}, error => {// alert(error.message);
})
3、播放器 SVGA.Player
用于管制动画的播放和进行
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('//static8.ziroom.com/topic/2019/svga_test/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)只能加载跨域容许文件,包含 svga 文件和图片文件(上传到服务器上,近程拜访)
4、apng 图片及好用的辅助工具
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 | |
---|---|---|
文件 | ||
文件体积 | 222kb | 压缩前:200kb;压缩后:112kb |
4)一款高效、好用的图片解决工具——Cherry
官网:https://yyued.github.io/cherry/
性能: