一、项目概览
这是一个名为「video组件」的微信小程序,核心功能是播放一段视频,并允许用户输入文字发送弹幕。项目结构如下:
text
├── app.js / app.json / app.wxss ├── project.config.json / project.private.config.json ├── sitemap.json ├── components/ │ └── navigation-bar/ # 自定义导航栏组件 └── pages/ └── index/ # 首页(视频 + 弹幕)
基础库版本为3.17.5,渲染器为webview,组件框架为glass-easel。
二、全局配置
app.json
json
{ "pages": ["pages/index/index"], "window": { "navigationBarTextStyle": "black", "navigationStyle": "custom" }, "style": "v2", "renderer": "webview", "componentFramework": "glass-easel", "sitemapLocation": "sitemap.json", "lazyCodeLoading": "requiredComponents" }关键点:
navigationStyle: "custom":隐藏系统默认导航栏,这样才能使用自定义导航栏组件(注意处理安全区域适配)。lazyCodeLoading: "requiredComponents":按需注入组件,减小包体积、提升启动速度。componentFramework: "glass-easel":使用新版组件框架。
app.wxss
定义了两个通用样式类:.box(带橙色边框的容器)和.title(红色加粗居中标题),供页面复用。
三、自定义导航栏组件
由于全局关闭了系统导航栏,项目实现了一个navigation-bar组件,位于components/navigation-bar/。
组件定义(navigation-bar.js)
组件的核心设计:
Properties 配置灵活:支持
title、background、color、back、loading、homeButton、show、delta等属性,基本覆盖常见导航栏需求。多 Slot 支持:通过
multipleSlots: true启用多插槽,左侧、中间、右侧都可自定义内容。状态栏安全区适配:这是自定义导航栏最关键的部分。
js
lifetimes: { attached() { const rect = wx.getMenuButtonBoundingClientRect() const platform = (wx.getDeviceInfo() || wx.getSystemInfoSync()).platform const isAndroid = platform === 'android' const isDevtools = platform === 'devtools' const { windowWidth, safeArea: { top = 0, bottom = 0 } = {} } = wx.getWindowInfo() || wx.getSystemInfoSync() this.setData({ ios: !isAndroid, innerPaddingRight: `padding-right: ${windowWidth - rect.left}px`, leftWidth: `width: ${windowWidth - rect.left}px`, safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${top}px); padding-top: ${top}px` : '' }) } }这里做了几件重要的事:
通过
wx.getMenuButtonBoundingClientRect()获取胶囊按钮位置,保证导航栏内容与胶囊对齐;计算右侧留白
padding-right,避免标题/内容被胶囊遮挡;计算左侧宽度,使左侧返回按钮区域与胶囊对称;
针对 Android / 开发者工具额外处理
safeArea.top,保证状态栏区域不被遮挡。
💡 注意:
wx.getDeviceInfo()和wx.getWindowInfo()是新 API,代码里用|| wx.getSystemInfoSync()做了兼容降级,这是很实用的写法。
返回逻辑
js
back() { const data = this.data if (data.delta) { wx.navigateBack({ delta: data.delta }) } this.triggerEvent('back', { delta: data.delta }, {}) }既执行了返回,又触发了back事件,方便父组件监听并做额外处理。
显示/隐藏动画
js
_showChange(show) { const animated = this.data.animated let displayStyle = '' if (animated) { displayStyle = `opacity: ${show ? '1' : '0'};transition:opacity 0.5s;` } else { displayStyle = `display: ${show ? '' : 'none'}` } this.setData({ displayStyle }) }通过observer监听show属性变化,动态生成透明度过渡或display样式,实现平滑的显示/隐藏效果。
样式(navigation-bar.wxss)
样式部分使用了 CSS 变量控制高度:
css
.weui-navigation-bar { --height: 44px; --left: 16px; } .weui-navigation-bar .android { --height: 48px; }iOS 导航栏高度 44px,Android 48px;
使用
env(safe-area-inset-top)适配刘海屏安全区;返回箭头、loading 图标都使用内联 SVG 作为
mask/background,无需额外图片资源,非常轻量。
组件配置
json
{ "component": true, "styleIsolation": "apply-shared", "usingComponents": {} }styleIsolation: "apply-shared"让组件可以使用外部(app.wxss)定义的样式,便于统一主题。
四、页面实现:视频 + 弹幕
页面结构(index.wxml)
xml
<navigation-bar title="video组件" back="{{false}}" color="black" background="#FFF"></navigation-bar> <scroll-view class="scrollarea" scroll-y type="list"> <view class="box"> <view class="title">华为开发者大会-2026</view> <view class="video-layout"> <video id="my-video" src="{{vieoPath}}" controls="true" danmu-list="{{danmuList}}" enable-danmu="true" danmu-btn="true" /> </view> <view> <view class="txt-style">弹幕内容</view> <input type="text" placeholder="在此处输入弹幕内容" bind:blur="inputBlur" /> <button type="primary" bind:tap="sendDanmu">发送弹幕</button> </view> </view> </scroll-view>要点说明:
back="{{false}}":首页不需要返回按钮;<video>组件通过danmu-list预置弹幕、enable-danmu开启弹幕、danmu-btn显示内置弹幕按钮;使用
scroll-view包裹内容,页面整体采用flex布局让滚动区域占满剩余空间。
⚠️ 代码注释里也提到:
danmu-btn在较高基础库版本中可能不再起作用(由enable-danmu控制),在低版本中才需要显式开启。建议以真机 + 当前基础库版本为准进行验证。
弹幕数据
js
data: { vieoPath: 'https://.../华为开发者大会2026.mp4?sign=...', danmuList: [ { text: '鸿蒙世界,奔腾不息', color: '#ff0000', time: 1 }, { text: 'HarmonyOS 7', color: '#ff00ff', time: 5 }, { text: '华为开发者大会2026', color: '#00ff00', time: 10 }, { text: '携手助力,筑牢金融安全', color: '#00ffff', time: 20 } ] }每条弹幕包含text、color、time(单位:秒),time表示弹幕出现在视频第几秒。
获取 VideoContext 并发送弹幕
这是整个功能的核心。发送弹幕需要通过VideoContext.sendDanmu()完成,而获取VideoContext有两种方式:
js
onLoad() { const query = wx.createSelectorQuery() query.select('#my-video').context() query.exec((res) => { if (res[0]) { this.videoCtx = res[0].context } }) }这里使用了SelectorQuery.context()获取节点对应的 context(在webview渲染下可取到 VideoContext)。
发送逻辑:
js
inputBlur(e) { this.inputValue = e.detail.value }, sendDanmu() { this.videoCtx.sendDanmu({ text: this.inputValue, color: getRandomColor() }) }输入框失焦时记录
inputValue;点击按钮时调用
sendDanmu,文字取输入值,颜色随机生成。
随机颜色生成
js
function getRandomColor() { let rgb = [] for (let index = 0; index < 3; index++) { let color = Math.floor(Math.random() * 256).toString(16) color = color.length == 1 ? '0' + color : color rgb.push(color) } return '#' + rgb.join('') }对每个通道生成 0–255 的随机数并转十六进制,单位数补 0,最终拼接成#RRGGBB。
💡 一个小优化点:如果用
bind:input实时同步输入值,会比bind:blur更即时,避免用户输入后直接点按钮(未触发 blur)导致取到旧值。当然实际点击按钮前通常会触发 blur,但显式同步更稳妥。
页面样式
css
page { height: 100vh; display: flex; flex-direction: column; } .scrollarea { flex: 1; overflow-y: hidden; }page撑满视口并纵向 flex 布局,导航栏固定高度,滚动区域flex: 1占满剩余空间,保证页面不出现整体滚动。
五、开发配置
project.config.json 要点
json
{ "setting": { "es6": true, "postcss": true, "minified": true, "enhance": true, "minifyWXML": true, "minifyWXSS": true, "uploadWithSourceMap": true }, "compileType": "miniprogram", "appid": "wx1b90c2c80eb4d7c0" }开启了 ES6 转 ES5、PostCSS、代码压缩、增强编译等常用选项,适合生产环境。
project.private.config.json
私有配置中指定了libVersion: "3.17.5",并开启了compileHotReLoad(热重载)、urlCheck等开发期选项。注意这个文件通常不纳入版本管理或按团队约定处理。
ESLint 配置
.eslintrc.js中声明了小程序特有的全局变量:
js
globals: { wx: true, App: true, Page: true, getCurrentPages: true, getApp: true, Component: true, requirePlugin: true, requireMiniProgram: true }否则 ESLint 会报wx is not defined之类的错误。
六、踩坑与优化建议
danmu-btn的版本差异:正如注释所说,高版本基础库中danmu-btn可能失效,建议通过enable-danmu控制,并在真机上验证不同基础库版本的表现。VideoContext 获取方式:
SelectorQuery.context()在webview渲染下可用;若切换 Skyline 渲染,需确认 API 兼容性。也可以用wx.createVideoContext('my-video', this)作为替代方案(更简单,推荐)。自定义导航栏的安全区:务必在真机(尤其刘海屏 iPhone 和 Android 挖孔屏)上验证,
getMenuButtonBoundingClientRect的返回值与胶囊对齐是核心。输入值同步:建议用
bind:input实时保存输入内容,避免依赖 blur 时机。视频地址带签名:示例中的云存储 URL 带
sign和t参数,会过期,正式项目应使用长期有效的地址或动态获取。弹幕颜色可读性:随机颜色可能生成接近白色/浅色的颜色,在浅色视频画面上看不清,可考虑限制亮度范围。