阅读视图

发现新文章,点击刷新页面。

【uniapp】微信小程序实现自定义 tabBar

前言

自定义 tabBar 可以让开发者更加灵活地设置 tabBar 样式,以满足更多个性化的场景,本文分享如何在uniapp vue3 实现自定义微信小程序 tabBar。

配置信息

pages.json 中添加 tabBar 的相关配置,例如

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页"
      }
    },
    {
      "path": "pages/mine/index",
      "style": {
        "navigationBarTitleText": "我的"
      }
    }
  ],
  "tabBar": {
    "custom": true,
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/icon_component.png",
        "selectedIconPath": "static/icon_component_HL.png",
        "text": "首页"
      },
      {
        "pagePath": "pages/mine/index",
        "iconPath": "static/icon_API.png",
        "selectedIconPath": "static/icon_API_HL.png",
        "text": "我的"
      }
    ]
  }
}

添加 tabBar 代码文件

在根目录添加 custom-tab-bar 文件夹,下面包含微信小程序原生文件。具体可以参考 微信小程序官方文档

编写 tabBar 代码

这一步需要获取自定义 tabBar 组件实例,通过示例来更新选中的 tab,微信小程序可以通过 this 操作,uniapp 也支持直接操作微信小程序组件示例,如下代码

<template>
  <view>
    <text>首页</text>
  </view>
</template>

<script setup>
import { getCurrentInstance } from "vue";
import { onShow } from "@dcloudio/uni-app";

const instance = getCurrentInstance();

onShow(() => {
  const tabBar = instance?.proxy?.$scope?.getTabBar?.();  // 获取组件示例函数返回值
  if (tabBar) {
    tabBar.setData({
      selected: 0,
    });
  }
});
</script>

其他 tab 页同理

tabbar.gif

示例项目

代码在下方链接的附件

链接

交流群

我建了一个微信群(非官方),大家可以在群里和我沟通交流 uniapp 开发遇到的问题、uniapp 的源码等问题。

mmqrcode1774407130592.png

uniapp uview-plus 自定义动态验证

以前写的验证都是这样固定的

const rules = ref({ bedUnitTidy: [{ required: true, message: '请选择', trigger: 'change' }]})

单选按钮这些选项是从接口里读出来的数据,所以现在用了动态验证,现在记录下来供自己以后参考

<up-form class="p24 bgf" :model="form" :rules="rules" ref="uFormRef" labelWidth="200" labelPosition="top":borderBottom="true">

    <up-form-item v-for="(item,index) in recordData" :key="index" :label="item.text"
        :prop="`inspectionItems.${item.value}`" required
        :rules="[{ required: true, message: '请选择' + item.text, trigger: 'change' }]">
        <up-radio-group v-model="form.inspectionItems[item.value]">
                <up-radio label="是" name="1">
                </up-radio>
                <up-radio label="否" name="0">
                </up-radio>
        </up-radio-group>
    </up-form-item>
</up-form>

const submitForm = () => {
    uFormRef.value.validate().then(res => {
        console.log(res, '成功');
        handleSubmit()
    }).catch(err => {
        console.log(err, '校验失败');
    })
}

之前一直验证失败是prop路径写错,现在查资料总结到:v-model 绑哪里 prop 就写哪里的完整路径,验证是form表单,所有项应该在form里,之前问题是在于,prop绑定的循环体里的数据,现在通过重组数据,拿到数据项后,放到form对象里,然后在up-form-item 上绑定rules 和prop解决了问题,每天进步一点点,加油!! image.png

【uniapp】小程序支持分包存放微信自定义组件 wxcomponents

问题

在小程序端,不少开发者都有使用小程序原生自定义组件的需求,uniapp 也是支持使用小程序自定义组件的,只不过要放在根目录的 wxcomponents、mycomponents 等下面,详见官方文档

但是,在 5.03 之前,uniapp 仅支持在根目录存放自定义组件,很多开发者面临着包体积超出的问题

image.png

5.03 起,uniapp 开始支持在分包的根目录添加 wxcomponents、mycomponents 等

源码

此部分为源码分析,感兴趣的掘友可以看下

uniapp 仓库 中,支持的每一个小程序都有一个专门的包

image.png

复制操作是通过内部的 vite 插件实现的,具体位置在 packages/uni-cli-shared/src/vite/plugins/copy.ts,感兴趣的掘友可以看下。

框架已经封装好了复制的插件,对于各端来说,只需要做好配置就行。我们是要支持分包能复制 wxcomponents、mycomponents,这看起来就很简单了,只需要处理好分包的路径就行,代码比较简单,直接贴出来了

/**
 * 在将小程序组件相关资源(例如固定目录名下的静态文件)复制到构建产物时,
 * 生成本次复制所需的目录路径与 glob 模式列表。
 *
 * 返回值始终包含:
 * - 项目根(相对复制根目录)下名为 `dir` 的目录。
 * - 每个 `uni_modules` 插件包下对应子目录的 glob:与本函数内局部变量 `uniModulesDir` 相同
 *   (前缀为 `uni_modules`、通配段、`dir`、以及递归匹配尾部)。
 *
 * 当已设置 `UNI_INPUT_DIR`、`UNI_PLATFORM`,且输入目录下存在 `pages.json` 时,会从
 * `subPackages` 或 `subpackages` 读取分包根路径;对每个 `root` 再追加两项:
 * `normalizePath(path.join(root, dir))` 与 `normalizePath(path.join(root, uniModulesDir))`。
 *
 * 若缺少环境变量或不存在 `pages.json`,则只返回上述项目根级别的两项。
 *
 * @param dir - 资源目录名称(例如 `wxcomponents`)
 * @returns 非空数组,元素为规范化后的路径或 glob 字符串,供复制或监听工具使用。
 */
export function createCopyComponentDirs(dir: string) {
  const dirs = [dir]
  const uniModulesDir = 'uni_modules/*/' + dir + '/**/*'
  dirs.push(uniModulesDir)
  const inputDir = process.env.UNI_INPUT_DIR
  const platform = process.env.UNI_PLATFORM
  if (!inputDir || !platform) {
    return dirs
  }
  const pagesJsonFile = path.resolve(normalizePath(inputDir), 'pages.json')
  if (!fs.existsSync(pagesJsonFile)) {
    return dirs
  }
  const { appJson } = parseMiniProgramPagesJson(
    fs.readFileSync(pagesJsonFile, 'utf8'),
    platform,
    { subpackages: true }
  )
  const roots: string[] = Object.values(
    appJson.subPackages || appJson.subpackages || {}
  )
    .map(({ root }) => root)
    .filter(Boolean)
  roots.forEach((root) => {
    dirs.push(
      normalizePath(path.join(root, dir)),
      normalizePath(path.join(root, uniModulesDir))
    )
  })
  return dirs
}

注意事项

wxcomponents、mycomponents 等目录下方文件的处理是 全部拷贝到产物中,没有 treeShaking,因为需要开发者梳理组件的使用和存放。

交流群

我建了一个微信群,大家可以在群里和我沟通交流 uniapp 开发遇到的问题、uniapp 的源码等问题。

mmqrcode1774407130592.png

从零开发一个微信记账小程序,零依赖、附完整源码

本文记录了「简记账」微信小程序的完整开发过程。从需求分析、架构设计到各页面实现,9个技术亮点一一拆解。适合有微信小程序基础的开发者阅读,也适合想找轻量级项目练手的同学。


一、为什么做这个小程序?

市面上的记账 App 动辄要注册账号、开通会员、同步云端——对于只想记个午饭钱的人来说,太重了。

于是我给自己定了一个极简原则:打开即用,不登录,不注册,记一笔只需 3 步

最终做出来的「简记账」是这样的:

  • 首页:余额卡片 + 一键记收入/记支出
  • 统计页:本月收支汇总 + 分类排行
  • 设置页:CSV 数据导出 + 一键清空

零 npm 依赖,纯原生微信小程序 API,包体积极小。

绠€璁拌处鎴浘1.png


二、项目结构

简记账/
├── app.js              # 全局数据服务层(核心)
├── app.json            # 路由 + tabBar 配置
├── app.wxss            # 全局通用样式
├── pages/
│   ├── index/          # 首页(记账 + 流水)
│   ├── stats/          # 统计页
│   └── settings/       # 设置页
└── images/             # tabBar 图标

结构很干净。没有 components 目录,没有 utils 工具库,不引入任何第三方包。


三、架构设计:app.js 作为数据服务层

这是整个项目最关键的设计决策。

微信小程序里各页面之间共享数据,常见做法有两种:

  1. 每个页面自己读写 Storage
  2. 把 Storage 操作统一封装在 app.js,页面通过 getApp() 调用

我选了第二种。好处是:页面完全不感知存储细节,未来如果从本地存储升级到云数据库,只改 app.js 就够了,页面代码零改动。

数据结构

每一条记账记录长这样:

{
  id: Date.now(),              // 时间戳作唯一 ID,够用
  type: 'income' | 'expense',
  amount: 58.5,                // 数字,不是字符串
  note: '午餐',                // 用户输入,默认为分类名
  category: 'food',            // 分类 key
  icon: '🍜',                  // emoji 图标
  categoryIcon: 'food',        // CSS 类名(用于背景色)
  date: '2026-03-23T10:30:00Z' // ISO 8601,方便计算
}

五个核心方法

// app.js
App({
  onLaunch() {
    this.checkLocalStorage()
  },

  // 初始化:确保 key 存在
  checkLocalStorage() {
    const transactions = wx.getStorageSync('transactions')
    if (!transactions) {
      wx.setStorageSync('transactions', [])
    }
  },

  // 新记录插到数组头部,保证最新在前
  saveTransaction(transaction) {
    let transactions = wx.getStorageSync('transactions') || []
    transactions.unshift(transaction)
    wx.setStorageSync('transactions', transactions)
    return true
  },

  getTransactions() {
    return wx.getStorageSync('transactions') || []
  },

  // 按 id 过滤,重写全量数组
  deleteTransaction(id) {
    let transactions = wx.getStorageSync('transactions') || []
    transactions = transactions.filter(t => t.id !== id)
    wx.setStorageSync('transactions', transactions)
  },

  // 月度统计:按年月筛选后累加
  getMonthlyStats() {
    const transactions = this.getTransactions()
    const now = new Date()
    let income = 0, expense = 0

    transactions.forEach(t => {
      const date = new Date(t.date)
      if (date.getMonth() === now.getMonth() &&
          date.getFullYear() === now.getFullYear()) {
        if (t.type === 'income') income += t.amount
        else expense += t.amount
      }
    })

    return { income, expense, balance: income - expense }
  },

  // 分类汇总
  getStatsByCategory(type) {
    const transactions = this.getTransactions()
    const now = new Date()
    const stats = {}

    transactions.forEach(t => {
      if (t.type !== type) return
      const date = new Date(t.date)
      if (date.getMonth() !== now.getMonth()) return
      stats[t.category] = (stats[t.category] || 0) + t.amount
    })

    return stats
  }
})

为什么用同步 API(Sync 系列)?

异步 API 需要写回调或 Promise,代码层层嵌套。记账这种轻量场景,数据量小,同步读写完全够用,而且代码清晰很多,不会有回调地狱。


四、首页:记账弹窗的设计细节

绠€璁拌处鎴浘2.png

首页的核心交互是底部弹起的记账面板

弹窗实现

我没用 wx:if 控制显隐,而是用 CSS class 切换:

/* 默认隐藏 */
.modal {
  display: none;
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
  justify-content: center;
  align-items: flex-end; /* 关键:内容贴底部 */
}

/* 激活时显示 */
.modal.active {
  display: flex;
}

/* 弹窗面板:只有上方是圆角 */
.modal-content {
  background: white;
  width: 100%;
  border-radius: 48rpx 48rpx 0 0;
  padding: 48rpx;
  max-height: 80vh;
  overflow-y: auto;
}

WXML 里通过三元表达式动态切换 class:

<view class="modal {{showModal ? 'active' : ''}}" bindtap="closeAddModal">
  <view class="modal-content" catchtap="stopPropagation">
    <!-- 内容 -->
  </view>
</view>

注意 catchtap="stopPropagation" 这里——点击面板内容时,阻止事件冒泡到背景层,否则一碰面板就会关闭弹窗。

为什么不用 wx:if

wx:if 是条件渲染,每次显示/隐藏都会销毁/重建 DOM。用 CSS 切换只是修改 display 属性,性能更好,也不会丢失输入框里已填的内容。

动态分类过滤

记收入和记支出要显示不同的分类选项,我把所有分类存在一个数组里,根据类型实时过滤:

openAddModal(e) {
  const type = e.currentTarget.dataset.type // 'income' 或 'expense'

  const incomeCategories = ['salary', 'bonus', 'investment', 'other_income']

  const filtered = allCategories.filter(c =>
    type === 'income'
      ? incomeCategories.includes(c.value)
      : !incomeCategories.includes(c.value)
  )

  this.setData({
    showModal: true,
    modalType: type,
    categories: filtered,
    selectedCategory: filtered[0].value
  })
}

一套数据,两种视图,不用维护两个独立数组。

智能时间显示

交易列表里的时间,我做了语义化处理,比"2026-03-23 10:30"更有温度:

formatDate(isoString) {
  const date = new Date(isoString)
  const now = new Date()
  const diff = now - date
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))

  if (days === 0) {
    const minutes = Math.floor(diff / (1000 * 60))
    if (minutes === 0) return '刚刚'
    const hours = Math.floor(diff / (1000 * 60 * 60))
    if (hours === 0) return `${minutes}分钟前`
    return `今天 ${this.formatTime(date)}`
  }
  if (days === 1) return '昨天'
  if (days < 7) return `${days}天前`
  return `${date.getMonth() + 1}${date.getDate()}日`
}

输出效果:刚刚 / 5分钟前 / 今天 09:30 / 昨天 / 3天前 / 3月15日


五、统计页:分类排行的实现

绠€璁拌处鎴浘3.png

统计页的核心是把原始数据转成可展示的排行列表。

formatCategories(rawStats, type) {
  const categoryMap = {
    food:       { name: '餐饮', icon: '🍜' },
    transport:  { name: '交通', icon: '🚇' },
    shopping:   { name: '购物', icon: '🛒' },
    // ...其他分类
  }

  return Object.entries(rawStats)
    .map(([key, value]) => ({
      key,
      name: categoryMap[key]?.name || key,
      icon: categoryMap[key]?.icon || '📦',
      amount: value.toFixed(2)
    }))
    .sort((a, b) => parseFloat(b.amount) - parseFloat(a.amount)) // 按金额降序
}

Object.entries(){ food: 120, transport: 30 } 这样的对象转成数组,再 map + sort,链式操作很清晰。

结余颜色动态判断:

<view class="stat-value {{balance >= 0 ? 'income' : 'expense'}}">
  ¥{{balance}}
</view>

收支相抵为正显示绿色,亏损显示红色,简单直观。


六、设置页:用剪贴板实现数据导出

微信小程序的文件系统权限比较复杂,直接生成并保存 Excel 文件需要申请额外权限。

我的解法是:生成 CSV 文本,复制到剪贴板,让用户自己粘贴到 Excel

exportData() {
  const transactions = app.getTransactions()
  if (transactions.length === 0) {
    wx.showToast({ title: '暂无数据可导出', icon: 'none' })
    return
  }

  let csv = '类型,金额,备注,分类,日期\n'
  transactions.forEach(t => {
    const date = new Date(t.date)
    const dateStr = `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`
    const type = t.type === 'income' ? '收入' : '支出'
    csv += `${type},${t.amount},${t.note},${t.category},${dateStr}\n`
  })

  wx.setClipboardData({
    data: csv,
    success: () => {
      wx.showModal({
        title: '导出成功',
        content: '数据已复制到剪贴板,请粘贴到Excel中保存',
        showCancel: false
      })
    }
  })
}

这个方案绕开了文件权限的麻烦,对普通用户来说操作也不复杂:复制 → 打开 Excel → 粘贴。


七、UI 设计:用 emoji 代替图标库

整个项目没有引入任何图标字体或 SVG 图标库,全部用 Unicode emoji。

好处:

  • 零包体积增加
  • 天然跨平台兼容
  • 色彩丰富,视觉效果好

每个分类有独立的背景色标:

.transaction-icon.food        { background: #fef3c7; }  /* 暖黄 */
.transaction-icon.transport   { background: #dbeafe; }  /* 浅蓝 */
.transaction-icon.shopping    { background: #fce7f3; }  /* 粉色 */
.transaction-icon.salary      { background: #dcfce7; }  /* 浅绿 */
.transaction-icon.entertainment { background: #e0e7ff; } /* 淡紫 */
.transaction-icon.medical     { background: #fee2e2; }  /* 浅红 */

emoji + 分类色块,不需要设计稿,纯代码实现就有不错的视觉层次。

主色用紫蓝渐变:

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

顶部卡片加了毛玻璃效果:

.balance-info {
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(20rpx);
}

八、数据刷新策略

所有页面都实现了 onLoad + onShow 双钩子刷新:

onLoad() { this.loadData() }
onShow() { this.loadData() }

onLoad 是页面第一次加载时触发,onShow 是每次切换到该页面时触发。

如果只有 onLoad,从统计页切回首页时,余额不会更新。加上 onShow 就解决了多页面数据同步的问题。这是微信小程序开发的标准实践,值得记住。


九、云开发迁移路径

虽然目前用的是本地存储,但项目已经为云开发预留了迁移空间:

app.json 已设置 "cloud": trueapp.js 中有注释掉的初始化代码:

// wx.cloud.init({ env: 'your-env-id', traceUser: true })

迁移时只需修改 app.js 里的五个方法:

当前实现 云开发替换
wx.setStorageSync('transactions', data) db.collection('transactions').add({ data })
wx.getStorageSync('transactions') db.collection('transactions').get()
transactions.filter(t => t.id !== id) + setStorageSync db.collection('transactions').doc(id).remove()

页面代码一行不用改。这就是把数据层抽象到 app.js 的价值所在。


十、总结

这个项目有几个值得借鉴的点:

  1. 全局服务模式app.js 统一管理数据读写,页面解耦
  2. 同步 Storage API:避免异步回调,代码清晰
  3. CSS class 控制弹窗:比 wx:if 性能好,不丢失表单状态
  4. emoji 代替图标库:零依赖,包体积最小
  5. 双钩子刷新onLoad + onShow 保证跨页面数据同步
  6. 剪贴板导出:绕过文件权限限制的轻量方案
  7. 动态分类过滤:一套数据,两种视图
  8. 语义化时间:提升用户体验的小细节
  9. 云开发预留:接口层隔离,未来升级零成本

完整源码已在掘金平台开源,可通过文章开头的链接访问

如果觉得有帮助,点个赞再走~


作者:守(SO) | 2026年3月

微信小程序开发02:原始人也能看懂的着色器与视频处理

往期回顾:

微信小程序开发01:XR-FRAME的快速上手

1、背景

还记得01时,3.3.3章节的成果展示吗?

image.png

虽然图片识别成功了,并且视频加载完毕了

但是视频存在大规模的绿色背景,这是业务不期望展示的

期望的效果是抠除绿色背景,仅保留人物主体,如下图

e1ea2feb31f439a3ea638f80d006b27d.jpg

今天我们就来尝试对视频图层做调整

2、温故知新

为了快速实现MVP,我们忽略了很多信息,但这些信息对我们基于MVP二次开发时,比较重要

我们需要了解一下当前demo工程的结构:

mermaid-1774086497015.png

清晰的分层架构 :

  • Pages 层 (如 xr-template-water/index.wxml ):

    • 负责页面配置和展示
    • 定义标题、介绍等元数据
    • 处理页面级别的交互
  • Components 层 (如 xr-template-water/index.wxml ):

    • 包含实际的 XR 场景逻辑
    • 处理 3D 渲染、AR 追踪等核心功能
    • 实现具体的业务逻辑
  • 共享行为机制 share-behavior.js 的设计 :

    • 提供统一的分享功能实现
    • 统一处理 AR 追踪状态初始化
    • 减少重复代码,提高一致性
    • 被所有 template 组件复用

再来看看数据流向:

用户交互
    │
    ▼
Pages 层 (页面配置)
    │
    ▼
xr-demo-viewer (容器组件)
    │
    ├─► 显示 UI (标题、介绍、代码)
    │
    └─► <slot> (主内容)
            │
            ▼
Components/Template (业务组件)
    │
    ├─► share-behavior (共享功能)
    │       │
    │       ├─► 分享初始化
    │       └─► AR 状态管理
    │
    └─► xr-scene (XR 场景)
            │
            ├─► 资源加载
            ├─► 3D 渲染
            └─► AR 追踪

像我们在第一期做的改造,得益于此demo工程的优秀设计,当我们想要新增功能时,只要做4步操作:

  • 创建 pages/template/xr-template-newFeature
  • 创建 components/template/xr-template-newFeature
  • 使用 xr-demo-viewer 包裹
  • 引入 share-behavior 获得共享功能

3、透明视频

ok,接下来我们进入正题,如何让绿幕视频可以扣除绿幕,实现一些付费AR软件提供的功能?

有两条路:

  1. 直接导入微信小程序支持的透明视频
  2. 通过自定义着色器计算每个像素颜色与绿色背景的距离,使用 smoothstep 函数根据距离动态调整透明度,使绿色背景变为透明而其他内容保持不透明。

第一条路需要使用AE等视频处理软件,导出成果,对素材的质量要求较高,也就是对上游有依赖

因此,不想被上游依赖,我们便选择第二条路,自己实现视频扣除纯色背景的功能

况且,XR FRAME本就支持着色器

// XR-Frame 提供的 API
wx.getXrFrameSystem().registerEffect("chroma-key", createChromaKeyEffect);

scene.createEffect({
  "name": "chroma-key",
  "shaders": [vertexShader, fragmentShader]  // 支持 GLSL 着色器
})

ok,写到这里,大家应该还是困惑,着色器和视频有什么关系?

着色器就像一个超级快的修图师,把视频的每一帧图片都检查一遍,把绿色的像素变成透明,然后把处理好的图片贴在3D模型这块"布料"上,纹理材质就是这块布料和修图师的组合

大白话说完,我们来看看处理的过程

我们创建一个新的资源,让它被包裹在xr-assets下,这个新的资源就是我们刚刚提到的“修图师”,它在小程序里的体现就是“材质”,即xr-asset-material

  <xr-assets>
    <xr-asset-load type="video-texture" asset-id="ayuan-video" src="https:/xxxx.mp4" options="autoPlay:true,loop:true" />
    <xr-asset-material asset-id="chroma-key-mat" effect="chroma-key" />
  <xr-assets>

asset-id我们很熟悉了,对应于材质的名字,就和视频的asset-id一样

effect是效果,即材质的模板

通过对effect的设置,我们可以调整光照模式,等等

image.png

"chroma-key"是我们通过scene.createEffect方法创造出来的一种自定义效果

部分源码如下:

function createChromaKeyEffect(scene) {
  return scene.createEffect({
    "name": "chroma-key",  // 给这个修图师起个名字叫"绿幕扣除"
    
    // 定义要用的工具:视频图片
    "images": [{
      "key": "u_baseColorMap",  // 视频纹理的代号
      "default": "white",
      "macro": "WX_USE_BASECOLORMAP"
    }],
    
    // 定义修图规则(着色器代码)
    "shaders": [
      // 第一个着色器:负责把3D模型放到屏幕上
      `顶点着色器...`,
      
      // 第二个着色器:负责给每个像素上色(这里是关键!)
      `片元着色器...
        vec4 color = texture2D(u_baseColorMap, vTextureCoord);  // 取出视频的像素颜色
        
        vec3 greenKey = vec3(0.055, 0.816, 0.294);  // 绿幕的颜色
        float dist = distance(color.rgb, greenKey);  // 算一下这个像素离绿色有多远
        
        float threshold = 0.40;  // 设定一个距离标准
        float alpha = smoothstep(threshold - 0.005, threshold + 0.005, dist);
        // 如果离绿色很近,透明度就变成0(看不见)
        // 如果离绿色很远,透明度就保持1(看得见)
        
        color.a *= alpha;  // 把算好的透明度应用到像素上
      `
    ]
  })
}

写完之后,别忘了在系统中注册,这样之后到处都可以使用

// 在组件加载时执行
lifetimes: {
  async attached() {
    const xrFrameSystem = wx.getXrFrameSystem();
    
    // 把这个修图师注册到系统里,以后可以随时用
    xrFrameSystem.registerEffect("chroma-key", createChromaKeyEffect);
  }
}

然后我们就要把之前的视频,和我们刚刚创建的材质,组合起来

  • 创建一个3D平面模型
  • 给这个模型穿上"chroma-key-mat"这件衣服
  • 把视频"ayuan-video"贴在衣服上
  • 把模型放到场景里
handleARReady: async function ({ detail }) {
  // 创建一个3D平面(就像一块板子)
  const videoPlane = this.scene.createElement(xr.XRMesh, {
    geometry: 'plane',           // 形状:平面
    material: 'chroma-key-mat',  // 材质:用刚才创建的“布料”
    uniforms: 'u_baseColorMap: video-ayuan-video',  // 把视频贴在布料上
    position: '0 0.5 0',      // 位置
    scale: '0.8 0.45 1',      // 大小
  });
  
  // 把这个平面添加到场景中
  lockItemEle.addChild(videoPlane);
}

一句话总结 :代码先创建了一个"绿幕扣除"的修图方案,然后创建一块用这个方案的布料,最后把视频贴在这块布料上,视频的每一帧都会自动被修图师处理,绿色背景就变透明了!

mermaid-1774089305244.png

附录

架构图

mermaid-1774084518187.png

❌