更新记录

1.3.0(2026-10-20)

  • 修复 Android 云打包 compileReleaseKotlin 编译失败(wb-svga-player compile error):
    • UTS 侧 this.$el 编译产物是 FrameLayout?,而 Kotlin 桥形参写的是非空 View, 严格签名匹配下报 Argument type mismatch: FrameLayout? / View。 现把桥上所有容器形参统一为 View?,null 时静默 no-op(不再依赖 UTS 侧逐个 ! 断言)。
    • UTS 的 number 编译成 Kotlin Number,桥里形参写 Int / Double 会报 actual type is 'Number', but 'Int'/'Double' was expected。 现数字形参(loops / volume / fontSize / percentage / frame / startPct / endPct) 统一用 Number 接收,函数体内 toInt() / toDouble() / toFloat() 转换。
    • 行为与 API 无变化,页面代码无需改动。
  • 修复 iOS 云打包(两个坑连着踩,最终形态:内置预编译静态库):
    • 坑 1 OSAtomicCompareAndSwapPtrBarrier:SVGAPlayer 2.5.7 里的 protoc 生成码用了 OSAtomic, Xcode 16+ / iOS 18+ SDK 把该系列挪进 Darwin.libkern.OSAtomicDeprecated 模块, 不显式 import 就报 call to undeclared function / must be imported from module / conflicting types; 报错在 pod 自己的 target 上,插件侧改配置无效,云端也不允许自定义 Podfile。
    • 坑 2 cannot find type 'SVGAPlayer' in scope:把源码放进 app-ios/src/ 后,.m 能编进去, 但 .h 不会写进模块伞头文件(HBuilderX 的 addSourceFile() 追伞头文件那步先于伞头文件创建, 直接跳过),Swift 仍然看不到 ObjC 类;只有 Libs/<dir>/*.h 会被 addLibrary() 写进伞头文件。
    • 最终处理:SVGAPlayer 2.5.7(Apache-2.0)打 3 处补丁后预编译为 utssdk/app-ios/Libs/SVGAPlayer/libSVGAPlayer.a + 15 个公开头文件;补丁与重建命令见 Libs/SVGAPlayer/HOW-TO-BUILD.md(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1、 #import <libkern/OSAtomic.h>、[worker release] 改 ARC 安全)。
    • config.json 的 pod 改为 Protobuf (~> 3.4) + SSZipArchive (>= 1.8.1)(提供 GPB / zip 符号), 补 libz.tbd;SvgaViewBridge.swift 不写 import SVGAPlayer,类型由伞头文件带进来。
    • 已按 HBuilderX 的真实构建方式(伞头文件 + Public Headers + 静态库 + pod 链接)在 Xcode 26.6 / Protobuf 3.29.6 / SSZipArchive 2.4.3 下验证 BUILD SUCCEEDED; 反向验证(伞头文件去掉 import)可精确复现云端那 7 条 cannot find type 报错。
  • 修复 iOS 编译失败(cannot convert value of type 'UTSJSONObject?' to expected argument type '[String : Any]?'): iOS 的 UTSComponent.__$emit 第二个参数是 NSDictionary([String: Any]?), UTSJSONObject 不是字典,直接传编译不过。iOS 侧改为 payload.toMap()(Swift 里就是 toMap() -> [String : Any],已从 DCloudUTSFoundation.swiftinterface 确认); Android 侧保持 UTSJSONObject——toMap() 在 uni-app(非 x)的 Android 上不可用,两端写法必须分开。
  • 修复 Android 编译失败(Unresolved reference 'FrameLayout' / Assignment type mismatch):
    • SvgaViewBridge.kt 新增 SvgaContainer : FrameLayout 时漏了 import android.widget.FrameLayout 和 import android.view.View.MeasureSpec,补齐;
    • SVGAVideoEntity.videoSize 是 SVGARect,宽高都是 Double,赋给 aspectRatio: Float 需要 toFloat();
    • SvgaController.setScaleMode 与 var scaleMode 生成的 setter 撞 JVM 签名,改名 applyScaleMode (与 setLoops/setVolume/setInteractive 同一类问题)。
    • 已用 kotlinc + android-34.jar + SVGAPlayer 2.6.1 classes.jar 真实编译通过(0 error)。
  • 新增 scaleType 属性(默认 fitCenter):centerCrop = 铺满容器、超出裁切(直播 / 抖音全屏特效), 另有 fitXY、centerInside。Android 走 SVGADrawable.scaleType(同时设置 view 和 drawable), iOS 走 player.contentMode;运行期可用 updateScaleType(mode) 切换。
  • 尺寸自适应:Android 承载容器换成 SvgaContainer——样式没写死高度时按素材宽高比撑开 (宽度 ÷ (w/h)),不同尺寸的 svga 都能完整显示;写死高度则以样式为准。iOS 侧 contentMode 固定按比例居中。示例页主播放器高度改由 loaded 事件计算(fitBox),不再固定 320/360。
  • 示例页新增「全屏特效」演示:position: fixed 铺满整屏 + scale-type="centerCrop" + volume=0 + loops=0,附关闭按钮;并演示「遮罩挡住下层 / 点击穿透」两种模式可切换。
  • 文档新增「10.3 全屏后还能点到下面的按钮?」:全屏特效默认 interactive=false 是穿透的; 挡住下层要打开 :interactive="true"(原生 SVGAImageView 铺满整屏并消费触摸), 透明 view 遮罩在 nvue 里拦不住(已废弃该方案)。另附「全屏层必须放在 scroll-view 外面」的 结构要求:放在滚动容器里会被裁剪、且绝对定位子节点拿不到尺寸和触摸。
  • 示例页修正全屏特效结构:根节点改为 view.root,scroll-view 与 .fx-layer 变成兄弟节点; fxBlock 直接驱动 :interactive,不再用遮罩;@event 收到 type=click 即证明触摸被特效层消费。
  • 文档新增「十、尺寸自适应与全屏特效(直播 / 抖音式)」。
  • 修复事件载荷丢字段(页面显示 undefinedxundefined / NaN%):
    • 根因:事件载荷用的是 UTS Map<string,string>,在 nvue(uni-app 非 x)上过桥到 JS 只剩第一个键, 所以 e.type 有值、e.width / e.frames / e.frame / e.percentage 全是 undefined。
    • 现改为把原生 JSON 直接 JSON.parseObject 成 UTSJSONObject 再 $emit,字段与数值类型原样带到 JS; Android 的 loaded 事件同时补上 audio(音频轨条数),两端对齐。
  • 修复 Android 首帧按原始尺寸绘制、底部被裁:setVideoItem 新建的 SVGADrawable 默认 scaleType = MATRIX(不缩放、左上角对齐),要等 play() 才同步;现在挂载后立刻强制 FIT_CENTER。
  • 示例页改为可滚动(根节点 <scroll-view scroll-y>):nvue 页面本身不滚动, 之前底部「静音/音频诊断/强制有声」这一行会被屏幕切掉。
  • Android 可诊断性 + 防闪退护栏:
    • attach / load / loadAssets / applyEntity 加 Log.i/e(tag wb-svga), 能直接看出:容器是否就绪、解析成功与否、videoSize/frames/fps、viewSize; 资源解析失败时会把 APK assets 根目录下能找到的 .svga 列表一起打出来, 一眼区分"资源没打进包"和"文件本身坏了"。
    • 未加载成功(entity 为空)时 play / stepTo / stepToFrame / playRange 直接忽略并打 warn, 不再把空对象递进 SVGAPlayer(stepToFrame 内部会摸 drawable.videoItem.frames)。
    • 所有桥接方法体和事件回调统一用 safe() 包 catch (Throwable): 原生库抛异常(含基座里没有 SVGAPlayer 的 NoClassDefFoundError)只记日志,不再闪退。
  • 不兼容变更:4 个方法改名为 updateXxx(iOS 编译要求,Android 同步): setLoops → updateLoops、setFillMode → updateFillMode、setVolume → updateVolume、 setInteractive → updateInteractive。 原因:UTS 组件编到 iOS 是 @objcMembers 的 Swift 类,prop 自带 ObjC setter setLoops:, 同名方法撞选择器 → method 'setLoops' with Objective-C selector 'setLoops:' conflicts with setter。 setMute / setDynamicImage / setDynamicText / setHidden / setAudioSession 没有同名 prop,保持不变。

平台兼容性

uni-app(5.26)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ √ - - × √ √ √ -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

wb-svga-player · SVGA 动画播放器(Android / iOS)

uni-app 的 SVGA 动画组件,原生渲染(Android 走 SVGAPlayer-Android,iOS 走 SVGAPlayer), 用于直播间礼物特效、加载动画、活动弹窗、播放器贴片等场景。

特性

  • 📱 Android / iOS 双端原生播放,nvue 页面使用
  • ▶️ 播放 / 暂停 / 停止 / 定位到帧或百分比 / 区间播放(支持倒放)
  • 🔁 循环次数、填充模式(Forward / Backward / Clear)
  • 📐 填充方式(完整居中 / 铺满裁切 / 拉伸),支持按素材比例自适应高度
  • 🔊 音量与静音(素材内嵌音频时生效)
  • 🖼 动态替换图片、动态文字、隐藏指定图层
  • 👆 点击穿透开关:盖在按钮上也不挡操作,或反过来吃掉点击
  • 📣 进度 / 结束 / 循环 / 出错等事件回调
  • 🎁 适合做「直播送礼全屏特效」:铺满裁切 + 触摸穿透 + 队列连播

一、平台支持

平台 支持 说明
App-Android ✅ 依赖 SVGAPlayer-Android 2.6.1(JitPack,构建时自动拉取)
App-iOS ✅ 已内置 SVGAPlayer 2.5.7 预编译静态库,无需 CocoaPods 额外配置
H5 / 小程序 / uni-app x ❌ 仅 App 端 · 仅 nvue 页面

使用前提

  1. HBuilderX ≥ 3.7.0(uni_modules + UTS 插件);
  2. 必须使用自定义基座:组件含原生代码,标准基座会提示找不到 SVGAPlayer;
  3. 页面后缀必须是 .nvue:原生组件只能放在 nvue 页面里(.vue 页面不显示);
  4. 组件需要明确的宽高,见「八、尺寸与填充」。

二、安装

  1. 在插件市场点「下载插件并导入 HBuilderX」,选择你的项目;
  2. 确认项目里出现目录:uni_modules/wb-svga-player/;
  3. 不需要 npm 安装、不需要手动配 Podfile;
  4. 首次使用:发行 → 制作自定义基座 → 运行。

升级插件后如果行为没变化,删掉 unpackage/dist/build 再打包一次,避免复用旧产物。


三、3 分钟上手

新建 pages/demo/demo.nvue:

<template>
  <view class="page">
    <view class="box">
      <wb-svga-player
        ref="svga"
        class="player"
        src="rose.svga"
        :loops="0"
        :volume="1"
        @event="onEvent" />
    </view>

    <text class="tip">{{ tip }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      tip: '加载中…'
    }
  },
  methods: {
    onEvent(e) {
      if (e.type == 'loaded') {
        this.tip = `${e.width}x${e.height} · ${e.frames} 帧 · ${e.fps} FPS`
      } else if (e.type == 'error') {
        this.tip = '加载失败:' + e.message
      }
    }
  }
}
</script>

<style>
  .page {
    flex: 1;
    background-color: #f2f3f5;
    padding: 12px;
  }

  /* 父容器负责尺寸,播放器绝对定位铺满 */
  .box {
    position: relative;
    height: 320px;
    background-color: #ffffff;
    border-radius: 12px;
    overflow: hidden;
  }

  .player {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  }

  .tip {
    margin-top: 10px;
    font-size: 13px;
    color: #666666;
  }
</style>

三个必须知道的点

  1. 页面必须是 .nvue,.vue 页面里不会渲染;
  2. 播放器要有明确宽高:父容器给 height,播放器用 position: absolute; top/left/right/bottom: 0 铺满。 不要只写 flex: 1 —— Android 上原生组件按 flex 量出来的高度可能是 0,表现是「白盒子在、动画不显示」;
  3. src 支持三种写法:

    • 内置/打包进 apk 的资源名:rose.svga
    • 网络地址:https://xxx.com/a.svga
    • 本地文件:绝对路径或 file:// 开头,例如 file:///data/user/0/包名/files/a.svga。 用 uni.downloadFile 下载后,先转成绝对路径再传:

      uni.downloadFile({
      url: 'https://xxx.com/a.svga',
      success(res) {
       const path = plus.io.convertLocalFileSystemURL(res.tempFilePath)
       that.svgaSrc = path
      }
      })

自带三个示例素材:rose.svga、kingset.svga、heartbeat.svga,装好即可跑通。


四、属性(props)

属性 类型 默认值 说明
src String '' 资源地址,见上「三种写法」
active Boolean true 是否为当前激活项。列表 / swiper 里切页时置 false 暂停、置 true 播放
autoPlay Boolean true 解析完成后自动播放(还需 active 为 true)
loops Number 0 0 = 无限循环;N = 播放 N 次(结束后回调 finished)
fillMode String Forward 停止后的表现:Forward 停在最后一帧 / Backward 回到第一帧 / Clear 清空画布
scaleType String fitCenter 填充方式:fitCenter 完整居中(可能留白)/ centerCrop 铺满裁切 / fitXY 拉伸 / centerInside
volume Number 1 音量 0 ~ 1,0 为静音(素材内嵌音频时才生效)
clearsAfterStop Boolean false 停止后是否清屏
interactive Boolean false false 触摸穿透(推荐,装饰层不挡操作);true 吃掉点击并回调 click
debug Boolean false 打印内部事件到控制台(progress 很密,调试完记得关)
<wb-svga-player
  src="kingset.svga"
  :loops="1"
  fill-mode="Backward"
  scale-type="centerCrop"
  :volume="0"
  :interactive="false" />

五、事件(统一 @event)

载荷是一个普通对象,用 type 区分:

type 附带字段 触发时机
loaded width height frames fps audio 素材解析完成、即将开始播放(audio 为音频轨条数,0 = 无声音)
progress frame percentage(0~1) 播放过程中,每帧回调(≈ fps 次/秒)
finished — 播放结束(仅 loops > 0 时才会来,loops = 0 是无限循环)
repeat — 每循环一轮回调一次
pause — 暂停时
click — 仅在 interactive = true 时,点击动画区域触发
error message 资源不存在 / 解析失败 / 播放异常
<wb-svga-player src="rose.svga" @event="onEvent" />
export default {
  methods: {
    onEvent(e) {
      switch (e.type) {
        case 'loaded':
          // e.width / e.height / e.frames / e.fps / e.audio 都是 number
          console.log('尺寸', e.width, e.height, '音频轨', e.audio)
          break
        case 'progress':
          // 别在这里做重活,每秒会来 fps 次
          break
        case 'finished':
          console.log('播完了')
          break
        case 'error':
          console.log('出错:', e.message)
          break
      }
    }
  }
}

做「加载中」提示时,就用 loaded 当开关:它正好在「已有第一帧、正要开播」时发出。


六、方法(this.$refs.xxx.方法())

播放控制

方法 说明
play() 播放 / 继续
pause() 暂停
stop() 停止(按 fillMode 决定停在第一帧还是最后一帧)
stopAndClear() 停止并清空画布
reload() 用当前 src 重新加载并播放(同一素材重播用这个)
load(src) 手动加载另一个动画(不修改 props)
stepTo(percentage) 定位到 0~1 的进度
stepToFrame(frame, andPlay) 定位到指定帧,andPlay 为 true 时从该帧继续播
playRange(startPct, endPct, reverse) 播放 0~1 区间,reverse = true 倒放
isAnimating() 是否正在播放

运行期改参数(等价于改 props,但不触发重新加载)

方法 说明
updateLoops(n) 改循环次数
updateFillMode(mode) 改填充模式
updateScaleType(mode) 改填充方式
updateVolume(v) 改音量 0~1
updateInteractive(boolean) 改点击穿透
setMute(boolean) 静音开关(会记住静音前的音量)

动态元素

方法 说明
setDynamicImage(key, src) 替换素材里 key 对应的图层图片(src 支持本地/网络)
setDynamicText(key, text, fontSize, color) 设置动态文字
setHidden(key, hidden) 隐藏 / 显示某个图层
clearDynamic() 清空所有动态元素

信息读取

方法 说明
getWidth() / getHeight() 素材画布宽高
getFrames() / getFps() 总帧数 / 帧率
hasAudio() 是否含音频轨
audioDebug() 音频诊断信息(排查没声音时用)
<wb-svga-player ref="svga" src="rose.svga" :loops="0" @event="onEvent" />

<view class="row">
  <text class="btn" @click="$refs.svga.pause()">暂停</text>
  <text class="btn" @click="$refs.svga.play()">继续</text>
  <text class="btn" @click="$refs.svga.stepTo(0.5)">定位 50%</text>
  <text class="btn" @click="$refs.svga.updateLoops(3)">循环 3 次</text>
</view>

方法名为什么是 updateXxx 而不是 setXxx:loops / volume / fillMode / scaleType 这些属性 在原生侧自带 setXxx 的 setter,方法再叫 setXxx 会冲突,所以统一用 updateXxx。


七、常用场景

7.1 固定尺寸的装饰动画

<view style="width: 120px; height: 120px; position: relative;">
  <wb-svga-player class="abs" src="heartbeat.svga" scale-type="fitCenter" />
</view>
.abs { position: absolute; top: 0; left: 0; right: 0; bottom: 0; }

7.2 高度跟着素材走(不同尺寸都完整显示)

素材画布尺寸各不相同(600×600、750×420、1080×1920…),用 loaded 里的宽高比算容器高度:

<view class="box" :style="{ height: boxH + 'px' }">
  <wb-svga-player class="abs" :src="src" @event="onEvent" />
</view>
data() {
  return { src: 'rose.svga', boxH: 320 }
},
methods: {
  onEvent(e) {
    if (e.type != 'loaded') return
    const boxW = uni.getSystemInfoSync().windowWidth - 24  // 页面左右各 12px padding
    this.boxH = Math.max(120, Math.round(boxW * e.height / e.width))
  }
}

7.3 全屏特效(直播间送礼 / 抖音那种)

铺满整屏、超出裁切、触摸穿透(用户还能继续点礼物和点赞):

<!-- 放在页面根 view 的最后,且不要在 scroll-view 里面 -->
<view v-if="fxOn" class="fx-layer">
  <wb-svga-player
    class="abs"
    :src="fxSrc"
    scale-type="centerCrop"
    :interactive="false"
    :loops="1"
    :volume="0"
    @event="onFxEvent" />
</view>
.fx-layer { position: absolute; top: 0; left: 0; right: 0; bottom: 0; }
.abs      { position: absolute; top: 0; left: 0; right: 0; bottom: 0; }

三个要点:

  1. scale-type="centerCrop" —— 全屏必须铺满裁切;用 fitCenter 会留黑边;
  2. interactive="false" —— 特效层不吃触摸,不挡下层按钮和滑动;
  3. 特效层要放在 scroll-view 外面(页面根 view 的最后一个子节点)。放在滚动容器里会被裁剪, 而且绝对定位的子节点拿不到正确的尺寸和触摸。

需要「挡住下层」(模态特效,例如抽奖中不可操作)时,把 interactive 设成 true 即可 —— 因为组件铺满整屏,它就会吃掉整屏的点击,并回调 click 事件。 不要用透明 view 当遮罩:nvue 里它拦不住原生组件的触摸。

7.4 队列连播 + 加载态

连发多个礼物时排队播,一段播完接着下一段,中间不留黑屏。

<view v-if="fxOn" class="fx-layer">
  <wb-svga-player
    ref="fx"
    class="abs"
    :src="fxSrc"
    scale-type="centerCrop"
    :interactive="false"
    :loops="1"
    :volume="0"
    @event="onFxEvent" />

  <!-- 加载态:只在屏幕为空时显示 -->
  <view v-if="fxLoading" class="fx-loading">
    <text class="fx-loading-txt">加载中…</text>
  </view>
</view>
export default {
  data() {
    return {
      fxOn: false,
      fxSrc: 'rose.svga',
      fxLoading: false,
      fxQueue: [],
      fxTimer: null,
      lastFinishAt: 0
    }
  },
  methods: {
    /** 送礼物:入队 */
    sendGift(src) {
      if (this.fxQueue.length < 3) this.fxQueue.push(src)
      this.startIfIdle()
    },

    startIfIdle() {
      if (this.fxOn || this.fxQueue.length == 0) return
      this.fxSrc = this.fxQueue.shift()
      this.fxOn = true
      this.fxLoading = true          // 屏幕是空的,显示加载态
      this.armWatchdog()
    },

    /** 一段播完:队列还有就原地换素材,不卸载组件(所以无黑屏) */
    onFxEvent(e) {
      if (e.type == 'loaded') {       // 解析完成、正要开播 → 收加载态
        this.fxLoading = false
        return
      }
      if (e.type == 'finished' || e.type == 'error') {
        const now = Date.now()
        if (now - this.lastFinishAt < 120) return   // 去重
        this.lastFinishAt = now

        if (this.fxQueue.length > 0) {
          const next = this.fxQueue.shift()
          const same = next == this.fxSrc
          this.fxSrc = next
          if (same) this.$refs.fx.reload()          // 同一素材重播
          this.armWatchdog()
          return
        }
        this.fxOn = false
      }
    },

    armWatchdog() {
      clearTimeout(this.fxTimer)
      this.fxTimer = setTimeout(() => {              // 素材异常不回调时兜底
        this.onFxEvent({ type: 'finished' })
      }, 12000)
    }
  }
}

要点:

  • 全程只用一个组件实例,播完只换 src,不销毁重建 —— 上一帧会留在屏幕上,新素材解析好直接接上;
  • 连续送同一个礼物时 src 没变,props 不会触发重新加载,要手动 reload();
  • 加载态只在「屏幕为空」时显示;续播时别显示,否则会出现「动效已经在播、头上还顶着加载中」。

7.5 swiper / 列表里复用

用 active 控制播停,离开屏幕的项自动暂停,不占 CPU:

<swiper :current="current" @change="onChange" style="flex: 1;">
  <swiper-item v-for="(item, i) in list" :key="i">
    <view class="full">
      <wb-svga-player class="abs" :src="item.src" :active="i == current" :loops="0" />
    </view>
  </swiper-item>
</swiper>
onChange(e) {
  this.current = e.detail.current
}

7.6 动态文字 / 图片 / 隐藏图层

需要素材里已经留好对应的 key(在 SVGA 制作工具里给图层命名):

const svga = this.$refs.svga

svga.setDynamicText('nickname', '小柚子', 24, '#FFD24D')  // key, 文案, 字号, 颜色
svga.setDynamicImage('avatar', 'https://xxx.com/a.png')   // key, 图片地址
svga.setHidden('badge', true)                             // 隐藏某个图层
svga.clearDynamic()                                       // 全部还原

7.7 音量与静音

<wb-svga-player ref="svga" src="with_audio.svga" :volume="1" :loops="0" />
<text class="btn" @click="$refs.svga.setMute(true)">静音</text>
  • 只有素材内嵌音频轨才会出声,可用 loaded 事件的 audio 字段判断(0 = 无声音);
  • Android 的音量是全局的(SVGASoundManager 单例),调小会影响同进程其他 SVGA;
  • iOS 静音键 / 静音开关会影响播放,可用 setAudioSession('playback') 让静音模式下也出声。

八、尺寸与填充

两条规则

  1. 容器必须有明确宽高(父级给 height,播放器 position: absolute 铺满,或直接写死宽高); 高度为 0 时什么都不显示;
  2. 素材比例和容器比例不一致时,由 scaleType 决定怎么放:
scaleType 效果 适用
fitCenter(默认) 完整显示、居中,可能留白 图标、装饰动画
centerCrop 铺满容器,超出部分裁切 全屏特效 / 背景动画
fitXY 拉伸铺满(会变形) 极少用
centerInside 素材比容器大时才缩小 少用
<!-- 完整显示 -->
<wb-svga-player src="rose.svga" scale-type="fitCenter" />
<!-- 铺满裁切 -->
<wb-svga-player src="rose.svga" scale-type="centerCrop" />

九、触摸与层级

组件是普通 View(Android ImageView / iOS UIView),不会浮到 Window 层,也不影响其它组件层级。

需求 写法
纯装饰,不挡点击(默认) :interactive="false"
吃掉点击(模态特效、点特效关闭) :interactive="true",并在 @event 里判断 e.type == 'click'
<view class="fx-layer" v-if="fxOn">
  <wb-svga-player
    class="abs"
    src="rose.svga"
    scale-type="centerCrop"
    :interactive="true"
    @event="onFxEvent" />
  <!-- 关闭按钮要写在组件**后面**(DOM 顺序在后 = 层级在上),否则会被整屏吃掉点不到 -->
  <text class="close" @click="fxOn = false">关闭</text>
</view>

想让特效下面还能操作(抖音式礼物特效),保持 interactive="false" 就好, 不要额外加透明遮罩 —— nvue 里 view 不绑事件不吃触摸,绑了也常常拦不住原生组件。


十、常见问题

1. Android 上只有白底,动画完全不显示

容器没给播放器明确宽高。检查是否有 position: absolute; top/left/right/bottom: 0 或写死的宽高; 只写 flex: 1 在 Android 上可能量出 0 高度(iOS 反而正常,容易误判成"Android 不支持")。

2. 运行时提示找不到 SVGAPlayer / ClassNotFound

用的是标准基座。组件含原生代码,必须 制作自定义基座 后运行。

3. 事件里 e.width、e.frame 全是 undefined

旧版本在 nvue 下事件载荷会丢字段,1.3.0 已修复(改为传 JSON 对象)。升级插件并重新打包即可。

4. iOS 云打包报 OSAtomicCompareAndSwapPtrBarrier 或 cannot find type 'SVGAPlayer' in scope

1.3.0 起已内置打补丁的静态库,不再依赖 CocoaPods 拉 SVGAPlayer,正常不会出现。 若仍报错:删掉 unpackage/dist/build 后重新打包;还是不行请带上完整日志反馈。

5. 用自己的 svga 素材

三种方式,任选:

  • 网络地址(推荐):上传到 CDN,src="https://xxx.com/gift.svga";
  • 本地文件:uni.downloadFile 下载后,用 plus.io.convertLocalFileSystemURL(res.tempFilePath) 转成绝对路径再传给 src(临时路径是 _doc/... 相对形式,原生层认不出来);
  • 打进安装包:把文件放进 uni_modules/wb-svga-player/utssdk/app-android/assets/(Android) 和 utssdk/app-ios/Resources/(iOS),之后用文件名引用。注意这样每次换素材都要重新打包。

6. loops 设了没反应 / 收不到 finished

loops = 0 是无限循环,永远不会结束;要播 N 次就设 loops 为 N,才会在最后回调 finished。

7. 没有声音

只有「内嵌音频轨」的 svga 才有声音(多数礼物素材是无声的)。 loaded 事件的 audio 字段为 0 就是没音频轨;hasAudio() 等价判断。

8. 同屏多个大动画卡顿

每个实例都会独立解码。列表 / 轮播里用 active 控制只让当前项播放; 尽量复用同一个实例切 src,而不是同时挂很多个。

9. iOS 模拟器链接失败

内置静态库只有 arm64 真机切片,请用真机或自定义基座运行。


十一、版本与许可

  • 当前版本:1.3.0(更新记录见同目录 changelog.md)
  • 组件代码:Apache-2.0
  • 内置播放内核(均为 Apache-2.0):
    • Android:com.github.svga:SVGAPlayer-Android:2.6.1(JitPack)
    • iOS:SVGAPlayer 2.5.7 预编译静态库(已修补 Xcode 16+ 的编译问题)
  • 插件不采集任何用户数据;使用网络地址时由播放器直接向该地址请求资源。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android:INTERNET、ACCESS_NETWORK_STATE(网络加载动画与动态图)。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件本身不采集任何数据。动画资源由使用者提供;使用网络地址时由 SVGAPlayer 直接向该地址发起请求,插件不向其他服务器发送数据。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。