小程序开发文档 组件总览

组件总览

容器内置 22 类标签。所有组件都由 Yoga 排版、NanoVG 绘制,不依赖 HTML。 未识别的标签会自动降级为 view。

标签默认尺寸可交互事件
view自适应否bindtap
scroll-view自适应可聚焦子节点bindtap
text按文本测量否bindtap
rich-text宽度 100%否—
image200 × 120rpx否bindtap
icon48 × 48rpx否—
button320 × 88rpx是bindtap
navigator100% × 80rpx是bindtap + 路由
input100% × 80rpx是bindinput / bindconfirm
textarea100% × 160rpx是bindinput / bindconfirm
switch96 × 56rpx是bindchange
checkbox48 × 48rpx是bindchange
radio48 × 48rpx是bindchange
slider100% × 80rpx是bindchange
progress100% × 24rpx否—
swiper100% × 300rpx自动轮播—
swiper-item100% × 100%否—
video100% × 360rpx是bindplay / bindpause / bindended / bindtimeupdate / binderror / bindloadedmetadata / bindwaiting / bindprogress / bindseekcomplete / bindfullscreenchange
form / label / checkbox-group / radio-group / block容器 / 不渲染否—

view / scroll-view

默认 flex-direction: column。scroll-view 通过 scroll-y="{{true}}" 或 scroll-x="{{true}}" 开启滚动; 内容超出时由焦点导航自动滚动到可见区域。鼠标左键与触摸点击会命中渲染树, 触发对应节点的 bindtap 并沿祖先冒泡(catchtap 终止冒泡)。 鼠标滚轮暂未接入。

<scroll-view class="panel" scroll-y="{{true}}" style="height: 100%;">
  <view class="card">...</view>
</scroll-view>

任何可交互节点都可以加 autofocus="{{true}}"(或 autofocus="true"): 该节点在树里新出现时,容器的摇杆焦点会自动移到它身上,常用于自定义弹窗的默认按钮。 焦点范围同时收敛到弹窗内部(该节点最近的绝对定位祖先),摇杆只在弹窗里循环, 不会跑到背后的列表上。 只在节点路径变化时抢一次焦点,用户随后在弹窗内移动焦点不会被拉回; 节点消失(关闭弹窗)后,焦点会还原到打开弹窗之前的那个节点(若它仍存在), 否则回到第一个可交互节点。

<view wx:if="{{modal}}" class="modal">
  <view class="btn" bindtap="onCancel">取消</view>
  <view class="btn primary" autofocus="{{true}}" bindtap="onConfirm">进入</view>
</view>

text / rich-text

text 支持 {{}} 插值与嵌套换行;默认继承父级颜色。 rich-text 只读取 nodes 字符串,当前降级为纯文本:剥离 HTML 标签、 还原常见实体,不渲染内联样式与图片。

<text>共 {{items.length}} 项</text>
<rich-text nodes="&lt;b&gt;标题&lt;/b&gt;,正文"></rich-text>

image / icon

image 支持三种来源:包内路径(以 / 开头)、 启动器缓存的本地文件、以及 http(s):// 远程 URL(下载到缓存后解码)。 支持 scaleToFill(默认)、aspectFit、aspectFill。 远程图可通过 http-headers 附加请求头(如鉴权 Cookie), 格式为 "Name: value"(按第一个冒号分隔名值)。 lazy-load 为 true 时只有滚动到可视区域(含 200px 预取边距)才下载; bindload 在解码成功时触发,binderror 在下载/解码失败时触发。 解码依赖 NanoVG/stb,目前不支持 webp。

<image src="/images/logo.png" mode="aspectFit" class="logo"/>
<!-- 远程鉴权图:带 Cookie 头预览 -->
<image src="{{url}}" http-headers="{{headers}}" mode="aspectFit"/>

icon 为原生绘制:type 可取 success / info / warn / waiting / cancel / clear / search / download, size 为 rpx 数值。

button / navigator

button 默认使用主题绿色,可用 class 或内联样式覆盖。 与 view 一样通过 bindtap 触发事件。 navigator 在激活时直接跳转:默认 navigateTo, open-type 可取 redirect / reLaunch。

<button bindtap="onAdd">+1 计数</button>
<navigator url="/pages/detail/detail?from=list">打开详情</navigator>

input / textarea

激活后调用系统输入法。value 与 placeholder 支持数据绑定, bindinput 在输入确认时触发,bindconfirm 同时触发。

限制:textarea 目前复用单行输入法,不能真正换行; 多行仅体现为更高的默认高度。
<input class="input" value="{{nick}}" placeholder="昵称"
       bindinput="onInput" bindconfirm="onConfirm"/>

switch / checkbox / radio

三者都是原生绘制,A 键切换状态并派发 change,事件对象 detail.value 为布尔值。选中状态保存在页面内,不会自动回写 data,需要在事件里自行 setData。 radio 通过 name 分组,同组只保留一个选中项。

<switch checked="{{enabled}}" bindchange="onSwitch"/>
<checkbox checked="{{accept}}" bindchange="onCheckbox"/>
<radio-group>
  <radio name="plan" value="a" checked="{{plan==='a'}}" bindchange="onRadio"/>
  <radio name="plan" value="b" checked="{{plan==='b'}}" bindchange="onRadio"/>
</radio-group>

slider / progress

slider 支持 min / max / step / value;方向键左右调整,A 键按 step 递增,触发 change。progress 读取 percent(0–100), 可选 activeColor / backgroundColor。

<slider min="0" max="100" step="5" value="{{volume}}" bindchange="onSlider"/>
<progress percent="{{volume}}" activeColor="#22c55e"/>

swiper / swiper-item

swiper 只构建当前项的子树,autoplay 与 interval(毫秒)驱动自动轮播,底部绘制指示点。 暂不支持 bindchange、纵向轮播与手势滑动。

<swiper autoplay="{{true}}" interval="{{3000}}" class="swiper">
  <swiper-item><view class="slide">第一屏</view></swiper-item>
  <swiper-item><view class="slide">第二屏</view></swiper-item>
</swiper>

video

支持 src、poster、autoplay、loop、 muted、controls、http-headers、 object-fit(contain / cover / fill)、 initial-time(起播秒数)、playback-rate(倍速)、 volume(0-100)与 subtitle(外挂字幕地址,支持 smb://会话id/路径、sdmc:/... 与 HTTP(S))。 src 同样支持 sdmc:/... 本地绝对路径。 http-headers 会作为 mpv 的 http-header-fields 下发, 用于需要鉴权的直链(如带 Cookie 的 m3u8 / 视频直链),格式 "Cookie: a=b; c=d"。点击画面或按 A 键播放/暂停, 底部控制条显示进度、时间与状态。

播放通过 nxkit::VideoView(wiliwili 风格播放器,底层封装 libmpv):

<video id="demoVideo" class="video" src="/videos/demo.mp4" poster="/images/logo.png"
       autoplay="{{false}}" loop="{{true}}" muted="{{true}}" controls="{{true}}"
       initial-time="{{60}}" playback-rate="{{1.5}}" volume="{{80}}"
       subtitle="smb://s1/movies/demo.zh.srt"
       bindplay="onVideoPlay" bindpause="onVideoPause"
       bindended="onVideoEnded" bindtimeupdate="onVideoTime"
       bindloadedmetadata="onMeta" bindwaiting="onWaiting" bindprogress="onProgress"
       bindseekcomplete="onSeeked" bindfullscreenchange="onFullscreen"
       binderror="onVideoError"/>

脚本侧通过 wx.createVideoContext(id) 控制播放:

const ctx = wx.createVideoContext('demoVideo');
ctx.play();
ctx.pause();
ctx.seek(30);
ctx.setPlaybackRate(1.5);
ctx.setVolume(80);
ctx.addSubtitle('https://example.com/demo.zh.srt');
ctx.requestFullScreen();   // 全屏铺满内容区,按 B 退出
ctx.stop();

事件语义:loadedmetadata 在时长/尺寸就绪时触发一次; waiting 与 progress 对应缓冲开始/恢复; seekcomplete 在 seek 结束后触发;fullscreenchange 带有 { fullScreen }。

全屏播放时视频位于最上层,普通节点会被画面盖住。给需要悬浮在画面上的子树加 data-overlay="true",容器会在视频直出之后单独合成这一层; 普通(非全屏)模式下该属性没有任何影响。

<view class="stage">
  <video id="player" src="{{url}}" />
</view>
<view class="hud" data-overlay="true">
  <text>{{channelName}}</text>
</view>
限制:全局只有一个播放器实例,同一时间只播放一个 video; 视频直出层不参与常规 NanoVG 合成,被 scroll-view 裁剪或完全移出屏幕时暂停绘制 (全屏模式例外)。全屏下只有 data-overlay 子树、toast 与弹窗会显示在画面上层。

canvas

canvas 使用 retained-mode 绘制:脚本通过 wx.createCanvasContext(id) 拿到上下文,所有绘制指令先缓存, 调用 ctx.draw() 提交为当前帧,由 NanoVG 在页面合成时回放。 width / height 是绘图坐标系尺寸(默认 300×150), 实际显示尺寸由 WXSS 决定,容器按两者比例缩放。 默认样式为 width: 100%、height: 300rpx,背景为黑色。

<canvas id="game" class="stage" width="420" height="300"
        bindtouchstart="onTouchStart" bindtouchmove="onTouchMove"
        bindtouchend="onTouchEnd"></canvas>
const ctx = wx.createCanvasContext('game');

function frame(ts) {
  ctx.setFillStyle('#0b0e12');
  ctx.fillRect(0, 0, 420, 300);
  ctx.setFillStyle('#10dc78');
  ctx.fillRect(player.x, player.y, 32, 32);
  ctx.draw();                  // 提交当前帧
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
限制:每帧都是全量回放,没有脏矩形优化; 坐标变换只支持 translate / scale / rotate; 暂不支持渐变、阴影、measureText 与离屏 canvas。

触摸与长按事件

任意节点都可以绑定 bindtouchstart / bindtouchmove / bindtouchend / bindtouchcancel 与 bindlongpress(按住 500ms 触发)。 事件对象包含 touches / changedTouches (每项含 clientX / clientY / pageX / pageY) 以及相对当前节点的 x / y,canvas 可直接换算绘图坐标。 使用 catchtouchstart 等 catch 前缀会阻止事件继续冒泡。

onTouchMove(e) {
  const t = e.touches[0];
  console.log('相对画布', e.x, e.y, '屏幕', t.clientX, t.clientY);
}
说明:Switch 触摸屏、USB 鼠标与桌面鼠标共用同一套指针事件; 摇杆焦点导航与触摸并存,触摸触发后本次不会额外派发 bindtap (长按同理)。

form / label / checkbox-group / radio-group / block

这些标签只承担容器语义:form、label 与分组标签会正常排版子节点, 但不会汇总提交;block 不产生节点,只展开子节点。 表单提交建议在按钮的 bindtap 里手动读取页面状态。

组件实现位于 client/apps/miniapp/source/miniapp_view.cpp 与 layout.cpp。