相信大家在平时的需求当中,都会遇到一些动画需求,那么大家又是如何抉择实现方式呢?
这里我大概分为4种情况,gif图/animation/ae导出骨骼动画/ae导出canvas
没错!最简单的就是让我们的设计师直接给我们gif图,那么在我们前端看来就是一张图片的问题,只需控制展示和隐藏即可。简直是最舒服的方式了。
那么gif图播放动画有什么缺陷呢?
假设现在有这样的一个场景:
在一个列表里,当用户点击每一个列表中的单项,都在该单项中间出现一个绽放的烟花。
简单啊!马上写给你!
export class Item extends Component {
timer: Nodejs.Timeout
playing() {
if (this.timer) {
clearTimeout(this.timer);
}
this.setState({ fire: true });
this.timer = setTimeout(() => {
this.setState({ fire: false });
}, 1500);
}
render() {
return (
<div>
<div>我要放烟花!</div>
{
this.state.fire
? <img src="fire.png“ />
: null
}
</div>
);
}
}
export class List extends Component {
render() {
return (
<div>
{
dataList.map((data) => {
return <Item key={data.id} />
})
}
</div>
);
}
}
假设我们现在的烟花gif是1500ms循环播放,那么用户按照列表顺序点击item现象是什么?
那么我们一定要使用gif图来实现呢,可不可以呢?
可以。由于同一张gif图浏览器只会保存一个播放进度,导致不同时间开始的gif图都是第一张播放的进度。那么我们只需要让这一张烟花gif变成多张gif就可以。
export class Item extends Component {
...
render() {
return (
<div>
<div>我要放烟花!</div>
{
this.state.fire
? <img src={`fire.png?${this.props.id}`} />
: null
}
</div>
);
}
}
export class List extends Component {
render() {
return (
<div>
{
dataList.map((data) => {
return <Item key={data.id} id={data.id} />
})
}
</div>
);
}
}
在gif后面加个唯一的id,那么每一个item上都是一张不同的gif图,这样每个烟花的播放进度各自维护,不会共享,上述问题就解决了。
但是引来了另外一个问题,因为加了id,如果列表有10个单项,就会加载10张fire.png,所以如果是这样的场景,我不推荐大家使用gif图实现了。
相信大家都已经使用过@keyframe和animation实现过动画了,该功能强大无比,能实现绝多数简单的动画需求。
那么我们依旧是拿烟花的例子来分析,既然gif图片有多个播放进度共享的问题,那么使用animation切换background-position是不是ok了。
是的,我们让设计师将烟花导出一个序列帧,我们利用step进行位移背景,可以播放动画
$frame: 20;
$ratio: 100% / $frame;
@keyframes playing {
@for $i from 0 to $frame {
#{$i * $ratio} {
background-position-x: -88px * $i;
}
}
100% {
background-position-x: 0;
}
}
.fire {
width: 88px;
height: 88px;
background: fire.png;
background-size: 88px;
&.play {
animation: playing 1500ms step-end
}
}
是不是也很简单!
不是!这里还是有坑的!
大家想想,序列图跟background-position改变的方向是同一个方向还是不同方向好?(ps:序列图是左往右,background-position-x改变是同向。background-position-y改变是不同向)
答案是不同向的没问题。
在我一开始接触一个需求我们使用以上方式进行实现的时候,郁闷地发现动画播放的时候在一些机型上会左右抖动,马上就知道了是由于编译转化成rem单位,导致在一些宽度的机型上rem转化为px的时候存在无敌的小数点取舍,导致上一帧向左偏移了1px,下一帧向右偏移了1px。
所以我的第一个想法就是不让编译帮忙转化成rem了,但是这样做的话不同机型又不能做到适配。
后面将序列帧的方向改成了纵向的试试,惊奇地发现不抖动了。
突然一道闪电闪过,想明白了,原来是background-size在作祟。因为我们的序列帧图片比较长,所以我们要设置background-size为图片大小,然后改变background-position-x来实现动画。
由于我将序列帧改成了纵向排列
$frame: 20;
$ratio: 100% / $frame;
@keyframes playing {
@for $i from 0 to $frame {
#{$i * $ratio} {
// 纵向改变位置
background-position-y: -88px * $i;
}
}
100% {
background-position-y: 0;
}
}
.fire {
width: 88px;
height: 88px;
background: fire.png;
background-size: 100%;
&.play {
animation: playing 1500ms step-end
}
}
那么此时我们锁定的是图片的宽度,而改变的是纵向的位置,那么问题就得到了解决。
广州设计公司https://www.houdianzi.com 我的007办公资源网站https://www.wode007.com
当动画非常复杂的时候,比如我们要实现一个猪的走动,我们要在猪停止走动的的时候停止头的晃动,在猪走路的时候让他的头晃动起来。利用序列帧使用上述animation也可也实现,但是毕竟序列帧图片较大,但是有没有更节省资源的方式呢。
设计师可以利用ae导出一只狗的分部位的动画,就是猪的头/身体/四肢/尾巴都是拆开的素材,这几个部位各自旋转/位移/缩放,组合在一起即可完成一只猪。
这种方式基本上将设计师的素材拿过来修修补补就可以了,而且动画效果很好,缺点就是transform能实现的效果之外的效果就不能使用了,比如形变。
ae导出canvas需要配合lottie库使用,工作量也都是在设计师那边。
原文:https://www.cnblogs.com/Qooo/p/13809971.html