书接上回,上一篇我们深入探讨了《变色龙吃虫记》,详细介绍了Lottie提供了一个轻量的高效动画解决方案,基于UI设计与开发者建立相关规范,通过动态修改的方案进一步提升了Lottie动画的应用场景,Lottie动画不仅满足了UI对动画流畅度和色彩搭配的高要求,还极大地提高了开发效率,实现了技术与艺术的完美结合,在业务中的多个场景中落地应用,并且收获了不错的效果反馈。
然而,我们当前的应用场景仅仅是动态修改Lottie动画中的文案、图片资源等内容,动画只是机械的、单一执行重复性的动作,并没有充分发挥Lottie动画的优势,在这个动画的环节中缺失了重要的一环,那就是缺乏用户的交互。在动画流程中增加用户交互可以增强用户参与感、提升用户体验,使用户在动画的过程中增强我们想要表达的信息理解和加深营销效果的印象。
那么,如何赋予Lottie动画以生命,使其在原有呆板、机械的重复性之外增加与用户的交互性呢?这就是我们今天要探讨的话题。
什么叫交互
看电影是交互吗?不是,你的行为影响不了画面(你把屏幕砸坏的行为除外)。
玩游戏是交互吗?是的,你能控制游戏角色的行为。你的特定行为能被电脑感知,是因为触发(emit)了特定事件,同时电脑有监听(on)这一事件,并执行针对这一事件发生后的对应操作(callback)。
你移动鼠标,画面就会有个鼠标跟着你的行为移动;
你点击图标,画面上的图标就会是选中状态;
你的行为,比如移动鼠标、敲下键盘、在摄像头前摇摇头、喊一声Siri,通过输入设备能对画面产生变化的,就叫交互。基于Lottie动画,你的行为对动画的效果产生变化,这就构成了我们今天要介绍的Lottie动画交互。
那么Lottie动画交互分为哪几类呢?查了些资料,并没有找到明确的归类说明,不过没关系,曾经有位伟人说过:"世界上本没有Lottie动画分类的,你创造了这个从此便有了",所以我们按照交互方式触发动画的规则分为:属性驱动交互型动画、API驱动交互型动画。
动画分类
属性驱动交互型动画
属性驱动交互型动画是指鼠标(点击、按下、滑动)、键盘按键等监听相关事件,在事件中不断改变Lottie对象的属性值从而间接驱动动画变化的方式。在这种方式中遵循:交互->属性,通过交互改变属性数据;属性->绘制,根据当前的数据绘制画面。
既然交互驱动属性数据,就需要知道哪些属性是可以控制改变呢?我们知道Lottie是基于AE导出的json文件,支持的属性分类有填充、遮罩、变换、蒙版、形状、其他六大类,详细内容支持可参考:airbnb.io/lottie/#/su…。如图:

为了便于大家理解这里动画的交互,我们通过下面的《变色龙吃虫记》的动画来给大家介绍。

在这个《变色龙吃虫记》例子中,我们可以把动画拆分为三部分,第一部分随着虫子(鼠标)的移动眼睛在不断旋转;第二部分随着虫子(鼠标)飞入不同的环境身体可以伪装成周围环境;第三部分随着虫子(鼠标)的靠近变色龙可以伸出舌头将虫子吃掉;那么这三个动画过程到底跟我们介绍的属性驱动交互型动画有什么关系呢?我们把上面三个过程尝试着用计算机的语言来理解,第一个监听鼠标的mousemove可以知道虫子(鼠标)的位置,这样我们是不是可以根据位置不断更新变色龙眼睛的旋转角度;第二个监听虫子(鼠标)移动到叶子(指定id的dom节点)获取周围环境来更新变色龙的颜色;第三个随着虫子(鼠标)的不断靠近变色龙发起了攻击(改变舌头的长度)吃掉了虫子,这不正是我们上面所列举的Lottie动画的属性吗?通过监听感知不同的事件更新设置Lottie动画中对象的旋转角度、颜色、长度等属性完成了动画的交互。
在这个过程中Lottie提供了一个关键的api: **addValueCallback。**在Lottie-API中,addValueCallback函数用于给Lottie动画中的某个属性添加一个回调函数(callback),以便在动画的某个值发生变化时执行特定的操作。这个函数提供了一种在动画执行过程中动态地修改或响应属性值的方式。
具体来说,addValueCallback函数的作用如下:
-
监听属性值变化:你可以使用**
addValueCallback**来监听Lottie动画中某个特定属性的值的变化。当这个属性的值在动画过程中发生变化时,你提供的回调函数将会被触发。 -
动态修改动画:在回调函数中,你可以根据当前属性值执行特定的操作,比如修改其他属性的值、触发其他动画效果等。这使得你可以在动画执行过程中动态地控制动画的行为。
-
增加交互性:通过**
addValueCallback**,你可以为Lottie动画添加更多的交互性。例如,你可以根据用户的操作(如点击、滑动等)来动态地改变动画的属性值,从而实现更丰富的动画效果和用户体验。
在使用**addValueCallback**时,你需要指定要监听的属性名称以及一个回调函数。当动画中的该属性值发生变化时,Lottie-API会自动调用你提供的回调函数,并将新的属性值作为参数传递给它。你可以在回调函数中根据这个新的属性值来执行相应的操作。


接下来通过变色龙伪装它(周边环境)看下具体的代码实现,首先我们先查看下四片树叶的dom结构如图所示。我们发现图中每一片叶子的结构都有一个id标识,分别是leaf_1,leaf_2,leaf_3,leaf_4,如果你之前有看过《lottie动画初步认识与动态修改》第一篇文章应该就会大概理解这个id的来源和意义,Lottie渲染的dom结构是可以有id、 class等属性信息的,这样我们就可以通过监听鼠标移动到指定的叶子上是获取当前叶子的颜色,更新设置到变色龙身体的颜色,是不是实现了事件属性交互效果呢
// 监听鼠标移入叶子 mouseover 事件, 改变变色龙的身体颜色
var leaveElement = document.getElementById('#leaf_4');
var colorSelector = document.querySelector(":root");
leaveElement.addEventListener("mouseover", function () {
const colorString = changeColor(leave_name);
//获取变色龙关键帧属性
const chameleonKeyPath = animationAPI.getKeyPath(
"#chameleon',Color"
);
//设置变色龙颜色
animationAPI.addValueCallback(chameleonKeyPath, function (value) {
return colorString
})
});
function changeColor(leave_name) {
var leafColorKey = animationAPI.getKeyPath(
"#leaf_4',Contents,color_group,fill_prop,Color"
);
// 获取到指定关键路径 color属性,一般color获取到的是数组
var colorValue = leafColorKey.getPropertyAtIndex(0).getValue();
var colorString = "rgba(";
colorString += Math.round(colorValue[0]);
colorString += ",";
colorString += Math.round(colorValue[1]);
colorString += ",";
colorString += Math.round(colorValue[2]);
colorString += ",1)";
}
同样的方式我们通过监听鼠标mousemove事件通过虫子(鼠标)与眼睛的坐标换算成角度、虫子(鼠标)与嘴巴的距离(碰撞检测),借助addValueCallback能力设置眼睛旋转角度属性的不断更新和嘴巴伸出舌头(舌头长度属性)吃掉虫子(鼠标)的动画。
API驱动交互型动画
API驱动交互型动画是指通过监听用户的动作调用Lottie提供的一系列api的事件驱动控制动的播放、暂停、频率控制、跳帧播放等形式。Lottie在提供了丰富的api来方便开发者灵活控制Lottie动画的渲染,我们大致可以分为两种过程状态api和事件回调api。
过程状态api主要有如下方法:
animation.play(); // 播放,从当前帧开始播放
animation.stop(); // 停止,并回到第0帧
animation.pause(); // 暂停,并保持当前帧
animation.goToAndStop(value, isFrame); // 跳到某个时间/帧并停止,isFrame(默认false)指示value表示帧还是时间(毫秒)
animation.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并进行播放
animation.playSegments(arr, forceFlag); // arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段
animation.setSubframe(flag); // -- 如果为 false,它将遵循原始 AE fps。如果为 true,它将尽可能多地更新。(默认为 true
animation.setSpeed(speed); // 设置播放速度,speed为1表示正常速度
animation.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放
animation.destroy(); // 删除该动画,移除相应的元素标签等。在unmount的时候,需要调用该方法
事件回调api主要有如下钩子函数:
complete: 完成
loopComplete: 循环完成
enterFrame 进入新播放帧
config_ready: 初始配置完成后
data_ready: 加载动画的所有部分后
DOMLoaded: 当元素已添加到 DOM 时
destroy: 销毁
为了便于理解我们先通过具体的例子来一起学习过程状态api控制交互的例子。对于用户可以翻牌成团的情况,逐步播放发牌、晃动着等待翻牌、翻牌的动画。该系列动画比较复杂,通过分析动画的连贯性、是否存在交互部分,把整个动画分为四个阶段,具体表现为:
-
第一阶段:先在页面中间呈现一堆牌(7张)垒摞的效果,之后进行逐张的发牌动作。页面从上到下,从左到右平铺这7张牌。
-
第二阶段:发完牌,所有的牌呈现一个水波纹的晃动,等待用户翻牌。
-
第三阶段:用户点击了某张牌之后,所有牌归位不再波动。该张牌先进行翻牌的动效,并显示翻得的金额、过一段时间后,其他的牌也执行翻牌效果,显示其他牌的金额信息。
-
第四阶段:用户翻的牌一边放大一边移动到页面中心位置,其他牌过渡效果下移并消失。
在上述动画中,因为翻牌的牌面金额信息是由后端计算下发的,且用户所翻的牌是未知随机的,因此不可能通过简单的一个包含1-4全部阶段的Lottie进行实现。所以计划采用分阶段播放的方式来控制Lottie动画的实现,根据用户的交互动作来依次完成牌的垒摞、牌的水波纹晃动以及翻牌等动画效果。
第一、二阶段,不存在交互过程,因此使用一个发牌的Lottie进行动画的播放。
//首先初始化发牌的Lottie
this.cards = lottie.loadAnimation({
path: 'http://xxxxxxx.json',
container: this.$refs.card, // 发牌过程的dom
renderer: 'svg',
loop: true,
autoplay: false
});
// 初始化完毕,播放发牌和波动的动效
this.cards.playSegments([0, 45], true); // 0,45 为发牌阶段的帧数
this.cards.playSegments([45, 104], true); // 45, 104 为水波纹晃动的帧数
第三阶段,当用户点击牌的瞬间,主要执行了以下操作:
-
网络请求,得到查询获取每张牌的数据,并使用我们《lottie动画初步认识与动态修改》讲的动态替换的方案,替换获取到的数据内容;
-
所有的牌归位(即牌停止水波浮动,等待翻转的状态);
-
点击的牌播放翻牌动效;
-
通过定时器,隔特定时间,执行播放其他牌的翻牌动效;
// 所有牌归位 this.cards.goToAndStop(155, true); // 155 为 牌归位的帧数
// 点击的牌 执行牌的翻转动效。 this.card[i].playSegments([200, 210], true); // 200-210 为执行翻牌的动画帧数
//通过定时器,让其他的牌也执行翻牌动效 setTimeout(() => { this.card[i].playSegments([200, 210], true); }, 200);
这个示例中,基于对Lottie动画机制的了解,我们着重分析了动画效果的构成,拆分了动画的实现过程,明确区分出整个过程中纯展示动画和交互动画几个阶段。在交互部分,我们通过监听用户事件(如点击、移动等)并结合Lottie提供的API来精准控制动画的播放,触发特殊关键帧的播放操作,成功实现了整个动画的控制。这是一个用户交互行为与API调用结合的典型例子,整个抽奖过程不仅流程自然而且充满用户交互的趣味性。
回顾小结
我们以Lottie的动画交互为切入点,了解了什么是交互以及基于Lottie的特殊动画可交互类型,通过对Lottie中属性、方法、钩子函数的理解不断探索出在业务应用中的新形式,通过属性驱动交互型动画、API驱动交互型动画不断打造良好的交互方案,提升用户的体验。如今随着AI技术的发展和Lottie社区的不断创新,未来Lottie与ai技术的探索碰撞也许会迸发出新的火花。因此我们将继续深入探索和优化技术,以期在后续的业务中实现更优更完美的技术沉淀。
预知后事如何,敬请期待我们后续的分享与解说。