首页流程动画
# 实现逻辑
- 先底部贴一张图,然后再贴每个子节点图;
- 因要在图片上做点击事件,要考虑到img热力图描边;
# lottie/svg
开源面向iOS、android、Web的动画库,能分析AE,LottieFile(Figma plugins)导出的动画,并且能让原生APP像使用静态素材一样使用这些动画,完美实现动画效果。
# 优缺点
优点
- 开发无需编写动画,只需要加载
- Android、iOS、Web多端支持
- 可手动设置进度,绑定事件,手势等等
- 可网络加载,动态控制播放进度
- 性能好,显示效果完美
缺点
- 能在AE中实现效果比较有限
- 对AE使用要求较高,动画依赖于设计师
- 使用位图后,资源消耗大
# Lottie与GIF对比
GIF
- 开发无法实现控制
- 有锯齿
- 难以维护,性能较低
- GIF文件太过庞大
- ...
# 使用
# 加载JSON文件
const animation = lottie.loadAnimation({
container:document.querySelector('#lottie'),
renderer:'svg/canvas/html',
path:'data.json',
loop:true,
autoplay:true,
name:'demo',
animationData:{}
})
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
container
:用来渲染的dom元素。path
:AE或者Lottie导出的JSON文件路径。renderer
:svg
|canvas
|html
可以渲染成三种状态loop
:true
|false
是否循环动画。true
->animation-iteration-count:infinite;
。autoplay
:true
|false
是否自动播放。name
:动画名称。animationData
:JSON数据path
和animationData
二选一即可。
# 实例方法
play()
:启动stop()
:停止pause()
:暂停destory()
:销毁setLocationHref(href:string)
:href
作为location.href
,当你在Safari中遇到不带符号的掩码问题时,它非常有用setSpeed(speed:number = 1)
:设置动画速度goToAndStop(value:number,isFrame:boolean = false)
:动画跳到某个进度并停止。value
:进度数值
isFrame:定义
value是基于时间
true还是基于帧
falsegoToAndPlay(value:number,isFrame:boolean = false)
:动画跳到某个进度并播放。setDirection(direction:number = 1)
:设置动画的播放顺序。正数和0
:正序,负数
:倒序getDuration(inFrames:boolean = false)
:获取动画持续事件。
true
->返回以帧为单位的持续时间 false
->返回以秒为单位的持续时间
playSegments(segments:[number,number]|[number,number][],forceFlag:boolean = false)
:segments
:参数指定播放帧[form,to]
,会停留在最后一个二维数组的to
里面 。forceFlag
:参数表示是否立即强制播放该片段 。false
->会等待当前段完成。true
->会立即播放此段。 如:animation.playSegments([15,30],false); // 播放完之前的片段,播放15-30帧
,animation.playSegments([[0,5],[10,20]]); // 直接播放0-5和10-20帧
。setSubframe(boolean = true)
:false
->将尊重原始的AE fps。true
->将使用中间值在每个RequestAnimationFrame上更新。
# 事件
complete
: 播放完成(循环播放下不会触发)loopComplete
: 当前循环下播放(循环播放/非循环播放)结束时触发enterFrame
: 每进入一帧就会触发,播放时每一帧都会触发一次,stop方法也会触发segmentStart
: 播放指定片段时触发,playSegments
、resetSegments
等方法刚开始播放指定片段时会发出,如果playSegments
播放多个片段,多个片段最开始都会触发。data_ready
: 动画数据json文件加载完毕触发data_fail
:动画数据json文件加载失败触发loaded_images
:当所有图片加载成功/失败时触发DOMLoaded
: 动画相关的dom已经被添加到html后触发destroy
: 将在动画删除时触发
// animation 是一个实例对象通过lottie.loadAnimation
animation.addEventlistener('data_ready',res=>{
// handle
console.log('animation file has loaded');
})
1
2
3
4
5
2
3
4
5
# 动画反复播放
// 监听动画完成
this.animationLottie.addEventListener('complete', (res) => {
// 1. 第一种 反复设置播放顺序
// 获取到播放顺序
const dir = this.animationLottie.playDirection;
// 设置播放顺序
this.animationLottie.setDirection(dir === 1 ? -1 : 1);
this.animationLottie.play();
// 2. 播放指定帧, 顺序倒过来
// 获取到动画总时长
// const duraction = this.animationLottie.getDuration(true);
// this.playSegments(this.direction === 1 ? [duraction, 0] : [0, duraction]);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 部分代码实现
initData() {
const data0 = getAnimationData(json0, `movin0/`);
lottie.loadAnimation({
container: document.getElementById('background'), // the dom element
renderer: 'svg',
loop: true,
autoplay: true,
animationData: data0,
});
this.systemData.forEach(item => {
const data = getAnimationData(item.json, `${item.movinImgName}/`);
this[item.id] = lottie.loadAnimation({
container: document.getElementById(item.id), // the dom element
renderer: 'svg',
loop: false,
autoplay: false,
animationData: data,
});
});
}
render() {
const { currentItem } = this.state;
return (
<div className={styles.systemMap}>
<div className={styles.systemCon}>
<div className={styles.mapCon}>
<div id="background" className={styles.background} />
<div style={{ width: 846, position: 'absolute' }}>
{this.systemData.map((item, key) => (
<div
key={item.id}
id={item.id}
className={classnames(styles.block, styles[item.id], currentItem && currentItem.id === item.id ? styles.active : null)}
>
<div className={styles.coverImg}>
<img src={item.img} alt="" width={item.imgWidth} height={item.imgHeight} border="0" useMap={`#Map${item.id}`} />
<map name={`Map${item.id}`} id={`Map${item.id}`}>
<area
aria-label={item.name}
shape="poly"
coords={item.coords}
onMouseOver={() => {
this.mouseEnter(item);
}}
onMouseOut={() => {
this.mouseLeave(item.id);
}}
onClick={() => {
if (!item.linkName) return;
this.setPath(key);
}}
// eslint-disable-next-line no-void
onFocus={() => void 0}
// eslint-disable-next-line no-void
onBlur={() => void 0}
/>
</map>
<span className={styles.nameTip}>
{item.name}
<i className={styles.triangle} />
</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 效果
上次更新: 2023/11/17, 05:08:18