微信小程序接入lottie动画
需求
需要把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渲染出来的层级太高,当有弹窗的情况会暴露在弹窗外
模拟器上会有这个问题,线上版本不会有
转载自:https://juejin.cn/post/7352807700180795426