需求
需要把lottie
动画在小程序的环境下进行展示
什么是lottie动画
由Airbnb
开发并开源。允许设计师将复杂的矢量动画导出为JSON
文件,并通过lottie
库在移动应用或者Web
上无缝地渲染这些动画。这些动画可以在iOS
、Android
和Web
等多个平台上使用,并且可以以高性能和高质量进行呈现。
和传统的GIF
、Canvas
有什么区别:
- 矢量动画:
lottie
动画是基于矢量图形的,动画中的所有元素都是以数学形式描述的,可以无限缩放而不会失真。GIF
和Canvas
动画通常是基于位图的,因此在缩放时可能会失去清晰度。 - 文件大小:相同动画的产生的文件,
lottie
的更小 - 可控制性:
lottie
有能够控制播放次数、播放快慢、播放开始和结束的监听等。 - 跨平台:类似
java jvm
,不同的平台有专门的处理,使得lottie
只要数据一致,动画就会一致。
小程序如何引入lottie
安装
使用lottie-miniprogram
库,链接。这个库本身是对lottie-web
的封装,源码也不多,而且这个库不一定能保证实时的跟上lottie-web
的版本更新。有复杂需求的朋友建议直接看下源码怎么对lottie-web
的处理,然后直接使用lottie-web
。
我这里使用的是Taro
做的工程,以下的代码都是React
的代码。
初始化Canvas载体
初始化一个canvas
元素,并type
置为2d
:
<Canvas
type="2d"
disableScroll
id={DIVINATION_CANVAS_ID}
canvasId={DIVINATION_CANVAS_ID}
></Canvas>
写一个函数用于在组件里面存储canvas
实例:
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const initCanvas = () => {
const query = createSelectorQuery()
// weixin的canvas完全实现了HTML上canvas的API,这里就直接用HTMLCanvasElement当做canvas的类型
return new Promise<HTMLCanvasElement>(resolve => {
if (canvasRef.current) {
resolve(canvasRef.current)
return
}
query
.select(`#${DIVINATION_CANVAS_ID}`)
.fields({ node: true, size: true })
.exec(res => {
const canvas = res[0].node
canvasRef.current = canvas
resolve(canvas)
})
})
}
初始化lottie动画
在页面onReady
或者canvas
元素onReady
的时候,使用lottie
加载动画,我这里因为是React
,所以直接使用useEffect
,又因为我的需求上可能有几个动画需要同时load
:
const DIVINATION_STATUS_JSON: { [key in DIVINATION_STATUS_ENUM]: string } = {
[DIVINATION_STATUS_ENUM.X1]:'https://test.com/x1.json',
[DIVINATION_STATUS_ENUM.X2]:'https://test.com/x2.json',
[DIVINATION_STATUS_ENUM.X3]:'https://test.com/x3.json'
}
const animationRef = useRef<
| {
[key in DIVINATION_STATUS_ENUM]: ReturnType<typeof lottie.loadAnimation> | null
}
>({
[DIVINATION_STATUS_ENUM.X1]: null,
[DIVINATION_STATUS_ENUM.X2]: null,
[DIVINATION_STATUS_ENUM.X3]: null
})
const initAnimations = async () => {
const canvas = await initCanvas()
// 载入
lottie.setup(canvas)
const ctx = canvas.getContext('2d')
if (!ctx) return
// load多个动画 用ref存储
Object.entries(DIVINATION_STATUS_JSON).forEach(([key, value]) => {
const animation = lottie.loadAnimation({
loop: false,
autoplay: false,
name: `${DIVINATION_CANVAS_ID}-${key}`,
path: value,
rendererSettings: {
context: ctx
}
})
animationRef.current = {
...(animationRef.current ?? {}),
[key]: animation
}
})
}
注意这里有一个坑点就小程序lottie-miniprogram
库的path在小程序里面只支持在线地址,但是在lottie-web
里面path
是可以放在本地的。所以这里要去研究下。
如果你想放在线上而没有地址或者域名,可以考虑:
- jsdelivr:类似免费
cdn
,或者oss
,只能在开发调试的时候使用,并且勾上“不校验合法域名”,jsdelivr
在国内的备案前几年被吊销了,小程序线上环境访问不了,需要有国内ICP
备案。 - 云厂商OSS:把动画
json
文件传到oss
上,通过公开只读方式拿到线上地址,好处就是肯定有备案,稳定性也能保证,缺点就是要花一小部分钱(量少的时候一个月几块,流量中等的时候也只有十几块左右?)。
组件暴露方法启动动画
组件使用forwardRef
,在组件内部暴露一个方法启动动画,并监听动画播放后的结束,可以监听complete
或者enterFrame
,视实际业务为准。
useImperativeHandle(ref, () =>{
startAnimation: (type: DIVINATION_STATUS_ENUM, onEnd?: () => void) => {
const animations = animationRef.current
const curTypeAnimation = animations[type]
if (!curTypeAnimation) return
// 重置到lottie的第一帧并播放
curTypeAnimation.goToAndPlay(0, true)
const completeLister = () => {
curTypeAnimation.goToAndStop(130, true)
// 这里记得remove listener
curTypeAnimation.removeEventListener('complete', completeLister)
if (onEnd) {
onEnd()
}
}
//监听播放完成
curTypeAnimation.addEventListener('complete', completeLister)
}
)
或者
// 监听播放到某一帧
const completeLister = e => {
const { currentTime } = e
let onceOnKnownResult = false
if (!onceOnKnownResult && Number(currentTime) > 100) {
onceOnKnownResult = true
onKnownResult?.(type)
}
if (Number(currentTime) > 100) {
curTypeAnimation.goToAndStop(100, true)
curTypeAnimation.removeEventListener('enterFrame', completeLister)
onEnd?.(type)
}
}
curTypeAnimation.addEventListener('enterFrame', completeLister)
问题一:如何让path能够本地
通过lottie
的animationData
:
import LottieAim from '@/constants/lottie/sheng'
const animation = lottie.loadAnimation({
loop: false,
autoplay: false,
name: `${DIVINATION_CANVAS_ID}-${key}`,
// 替换为animationData
animationData: LottieAim,
// path: value,
rendererSettings: {
context: ctx
}
})
将lottie
的json
数据复制出来,新建一个文件保存:
//sheng.ts
// 这里一个小tip,先const data = {json数据},会自动将json格式格式化为object,再改成export default
export defaul {}//数据
问题二:lottie在canvas渲染出来后,有失真情况
在获取到canvas
的时候进行放大dpr
再缩小dpr
,原理是canvas
按放大后的尺寸渲染,initCanvas
改造如下:
query
.select(`#${DIVINATION_CANVAS_ID}`)
.fields({ node: true, size: true })
.exec(res => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const systemInfo = getSystemInfoSync()
const dpr = systemInfo.pixelRatio
canvas.width = DIVINATION_CONTAINER_SIZE * dpr
canvas.height = DIVINATION_CONTAINER_SIZE * dpr
ctx.scale(dpr, dpr)
canvasRef.current = canvas
resolve(canvas)
})
问题三:canvas渲染出来的层级太高,当有弹窗的情况会暴露在弹窗外
模拟器上会有这个问题,线上版本不会有