当遇到动画需求的时候该使用什么方式实现呢?

更新日期: 2019-10-23阅读: 1.4k标签: 需求

背景

相信大家在平时的需求当中,都会遇到一些动画需求,那么大家又是如何抉择实现方式呢?
这里我大概分为4种情况,gif图/animation/ae导出骨骼动画/ae导出canvas


1. gif图硬核解决

没错!最简单的就是让我们的设计师直接给我们gif图,那么在我们前端看来就是一张图片的问题,只需控制展示和隐藏即可。简直是最舒服的方式了。
那么gif图播放动画有什么缺陷呢?

  1. 在添加一些效果的时候会出现锯齿,并且色值不丰富;
  2. 如果在同一时间存在多个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现象是什么?

  1. 当用户点击了第一个item,此时第一个item的烟花绽放;
  2. 用户在500ms后点击了第二个item,此时第二个item上的烟花会和第一个item的烟花保持一样的节奏,也就是第二个item的烟花从500ms开始播放,持续时间1000ms;
  3. 用户在1000ms后点击了第三个item,此时第三个item的烟花和前面两个item的烟花节奏保持一致,也就是第三个item的烟花从1000ms开始播放,持续时间500ms;
  4. 但是我们的定时器又傻乎乎的设定了1500ms,那么第二个item上的烟花就是绽放一次后,还有500ms的时间播放烟花的前500ms,第三个item上的烟花一样的道理;
  5. 这就存在问题了,不仅不同时间点击的播放的烟花节奏一致,而且会出现错乱播放的情况。

那么我们一定要使用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图实现了。


2. animation

相信大家都已经使用过@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
    }
}

那么此时我们锁定的是图片的宽度,而改变的是纵向的位置,那么问题就得到了解决。


3. ae导出骨骼动画/ae导出canvas

当动画非常复杂的时候,比如我们要实现一个猪的走动,我们要在猪停止走动的的时候停止头的晃动,在猪走路的时候让他的头晃动起来。利用序列帧使用上述animation也可也实现,但是毕竟序列帧图片较大,但是有没有更节省资源的方式呢。

设计师可以利用ae导出一只狗的分部位的动画,就是猪的头/身体/四肢/尾巴都是拆开的素材,这几个部位各自旋转/位移/缩放,组合在一起即可完成一只猪。

这种方式基本上将设计师的素材拿过来修修补补就可以了,而且动画效果很好,缺点就是transform能实现的效果之外的效果就不能使用了,比如形变。

ae导出canvas需要配合lottie库使用,工作量也都是在设计师那边。


链接: https://www.fly63.com/article/detial/6591

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!