前端項目直面客戶,爲了更好的交互體驗,免不了須要使用動畫來增香提味。在此分享自如動畫的嘗試與探索。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,文件體積小,開發成本低,效果好,能實現交互。
做者簡介:暴力燕,自如大前端開發中心-客端研發組