
Skyline
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
Skyline is a skill for developing WeChat mini-program Skyline-engine UI components including scroll lists, swipers, forms, and transition animations.
About
Skyline is a skill for developing WeChat mini-program UI components under the Skyline rendering engine. A developer uses it to build scroll lists and waterfalls, pull-to-refresh, nested scrolling, swiper carousels, form inputs, and shared-element transitions. It enforces Skyline-specific rules such as always setting scroll-view type, using enable-flex for horizontal scroll, and wrapping list-view/grid-view in a custom-mode scroll-view.
- Guides WeChat Skyline component development for lists, swipers, forms, and animations
- MUST/NEVER rules for scroll-view type, enable-flex, and nested scroll
- Covers Skyline-only components like list-view, grid-view, and draggable-sheet
Skyline by the numbers
- 8 all-time installs (skills.sh)
- Ranked #1,729 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
skyline capabilities & compatibility
- Capabilities
- frontend · ui design
- Use cases
- frontend · ui design
What skyline says it does
**scroll-view 必须指定 type**:Skyline 下使用 scroll-view 必须设置 `type` 属性
**NEVER** 在 Skyline 使用 web-view/editor/movable-view 组件
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill skylineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Build WeChat Skyline mini-program UI such as scroll lists, swipers, forms, and shared-element transitions.
Who is it for?
Building Skyline-engine scroll lists, carousels, forms, and page-transition animations in WeChat mini-programs
Skip if: Using WebView-only components like web-view, editor, or movable-view under Skyline
When should I use this skill?
You are developing scroll lists, swipers, forms, or transitions in a WeChat Skyline mini-program
What you get
Correct Skyline component markup for lists, carousels, forms, and animations that follow the engine's MUST/NEVER rules.
- Skyline component markup
- scroll/swiper/form/animation implementations
By the numbers
- 3 scroll-view type modes (list, custom, nested)
- 6 swiper indicator types
Files
Skyline 组件开发指南
适用场景
- 开发滚动列表/瀑布流/网格布局
- 实现下拉刷新、下拉二级功能
- 使用嵌套滚动模式
- 开发轮播图、卡片切换效果
- 处理表单输入(input/textarea)
- 实现共享元素/页面转场动画
- 开发半屏可拖拽面板
组件支持概览
完全支持的高频组件
| 组件 | Skyline 特性增强 |
|---|---|
| scroll-view | type 模式、worklet 回调、嵌套滚动、下拉二级 |
| swiper | layout-type 堆叠/卡片、indicator 动画类型 |
| text | overflow 文本溢出处理、max-lines |
| image | fade-in 渐显、默认懒加载 |
| input/textarea | worklet 键盘回调、输入法事件 |
| view/button | 完全支持 |
Skyline 新增组件
| 组件 | 说明 |
|---|---|
| list-view | 列表布局容器 |
| grid-view | 网格/瀑布流容器 |
| sticky-section/sticky-header | 吸顶布局 |
| nested-scroll-header/body | 嵌套滚动 |
| draggable-sheet | 半屏可拖拽 |
| share-element | 共享元素动画 |
| snapshot | 截图组件 |
| span | 内联混排 |
文档索引
根据需求快速定位(路径相对于 references/):
| 我想要... | 查阅文档 |
|---|---|
| 实现长列表/虚拟列表 | scroll/scroll-view.md |
| 下拉刷新/下拉二级 | scroll/scroll-view.md#下拉刷新 |
| 嵌套滚动 | scroll/nested-scroll.md |
| 列表/网格布局 | scroll/list-grid-view.md |
| 吸顶效果 | scroll/sticky.md |
| 半屏拖拽面板 | scroll/draggable-sheet.md |
| 轮播图/卡片效果 | layout/swiper.md |
| 文本处理 | media/text.md |
| 图片显示 | media/image.md |
| 输入框开发 | form/input.md |
| 共享元素动画 | special/share-element.md |
| 截图功能 | special/snapshot.md |
强制规则
MUST(必须遵守)
1. scroll-view 必须指定 type:Skyline 下使用 scroll-view 必须设置 type 属性
<!-- ✅ 正确 -->
<scroll-view type="list" scroll-y>...</scroll-view>
<!-- ❌ 错误:缺少 type -->
<scroll-view scroll-y>...</scroll-view>2. 横向滚动需 enable-flex:横向滚动需同时开启 enable-flex 以兼容 WebView
<scroll-view scroll-x enable-flex style="flex-direction: row;">3. 文本必须用 text 组件:内联文本只能用 <text> 组件
<!-- ✅ 正确 -->
<text>Hello <text>World</text></text>
<!-- ❌ 错误:内联文本不能用 view -->
<view>Hello <view>World</view></view>4. list-view/grid-view 必须在 custom 模式:
<scroll-view type="custom" scroll-y>
<list-view>...</list-view>
</scroll-view>5. 嵌套滚动必须使用 nested 模式:
<scroll-view type="nested" scroll-y>
<nested-scroll-header>...</nested-scroll-header>
<nested-scroll-body>...</nested-scroll-body>
</scroll-view>NEVER(禁止行为)
1. NEVER 在 scroll-view 外直接放置 list-view/grid-view 2. NEVER 在 list-view 中使用非直接子节点列表项 3. NEVER 在 Skyline 使用 web-view/editor/movable-view 组件 4. NEVER 依赖 image 的 WebView-only 裁剪模式(top/bottom/center/left/right)
scroll-view 核心用法
type 属性详解
| type 值 | 说明 | 使用场景 |
|---|---|---|
| list | 列表模式,按需渲染直接子节点 | 普通长列表 |
| custom | 自定义模式,支持 list-view/grid-view/sticky | 复杂布局 |
| nested | 嵌套模式,处理父子 scroll-view 联动 | Tab + 列表 |
基础列表模式
<!-- 列表模式:直接子节点按需渲染 -->
<scroll-view
type="list"
scroll-y
style="height: 100vh;"
bindscrolltolower="loadMore"
>
<view wx:for="{{list}}" wx:key="id">
{{item.name}}
</view>
</scroll-view>下拉刷新
<scroll-view
type="list"
scroll-y
refresher-enabled="{{true}}"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh"
>
<view slot="refresher" class="custom-refresher">
自定义刷新区域
</view>
<!-- 列表内容 -->
</scroll-view>Page({
data: { refreshing: false },
onRefresh() {
this.setData({ refreshing: true })
// 加载数据...
setTimeout(() => {
this.setData({ refreshing: false })
}, 1000)
}
})嵌套滚动模式
<scroll-view type="nested" scroll-y>
<nested-scroll-header>
<view>顶部固定区域</view>
</nested-scroll-header>
<nested-scroll-body>
<swiper>
<swiper-item wx:for="{{tabs}}" wx:key="id">
<scroll-view
type="list"
scroll-y
associative-container="nested-scroll-view"
>
<!-- 列表内容 -->
</scroll-view>
</swiper-item>
</swiper>
</nested-scroll-body>
</scroll-view>Worklet 滚动回调
<scroll-view
type="list"
scroll-y
worklet:onscrollupdate="onScrollUpdate"
>Page({
onScrollUpdate(e) {
'worklet'
// 在 UI 线程执行,无延迟
console.log(e.detail.scrollTop)
}
})swiper 增强特性
布局类型
<!-- 堆叠效果 -->
<swiper layout-type="stackLeft">
<swiper-item>...</swiper-item>
</swiper>
<!-- 卡片滑动 -->
<swiper layout-type="tinder">
<swiper-item>...</swiper-item>
</swiper>
<!-- 过渡动画 -->
<swiper layout-type="transformer" transformer-type="threeD">
<swiper-item>...</swiper-item>
</swiper>指示器样式
<swiper
indicator-dots
indicator-type="worm"
indicator-color="rgba(0,0,0,0.3)"
indicator-active-color="#000"
indicator-margin="10"
indicator-spacing="4"
>| indicator-type | 效果 |
|---|---|
| normal | 默认圆点 |
| worm | 蠕虫动画 |
| expand | 展开动画 |
| jump | 跳跃动画 |
| slide | 滑动动画 |
| scale | 缩放动画 |
text 组件
文本溢出处理
<!-- 单行省略 -->
<text overflow="ellipsis" max-lines="1">
这是一段很长的文本内容...
</text>
<!-- 多行省略 -->
<text overflow="ellipsis" max-lines="3">
这是一段很长的文本内容...
</text>
<!-- 渐隐效果 -->
<text overflow="fade" max-lines="1">
这是一段很长的文本内容...
</text>内联混排
<!-- 使用 span 实现图文混排 -->
<span>
<image src="icon.png" style="width:20px;height:20px;" />
<text>图文混排内容</text>
</span>input 组件
Skyline 特有事件
<input
value="{{value}}"
bindinput="onInput"
bind:selectionchange="onSelectionChange"
bind:keyboardcompositionend="onCompositionEnd"
worklet:onkeyboardheightchange="onKeyboardHeight"
/>Page({
onKeyboardHeight(e) {
'worklet'
// UI 线程回调,可用于同步调整布局
const { height, pageBottomPadding } = e.detail
}
})draggable-sheet 半屏组件
<draggable-sheet
class="sheet"
initial-child-size="0.5"
min-child-size="0.2"
max-child-size="0.8"
snap="{{true}}"
snap-sizes="{{[0.4, 0.6]}}"
worklet:onsizeupdate="onSizeUpdate"
>
<scroll-view
scroll-y
type="list"
associative-container="draggable-sheet"
>
<!-- 内容 -->
</scroll-view>
</draggable-sheet>Page({
onReady() {
this.createSelectorQuery()
.select('.sheet')
.node()
.exec(res => {
const sheetContext = res[0].node
sheetContext.scrollTo({
size: 0.7,
animated: true,
duration: 300
})
})
}
})share-element 共享元素
页面间共享元素动画
源页面:
<share-element key="hero-image" transform>
<image src="{{item.cover}}" />
</share-element>目标页面:
<share-element key="hero-image" transform transition-on-gesture>
<image src="{{detail.cover}}" />
</share-element>属性说明
| 属性 | 说明 |
|---|---|
| key | 页面内唯一标识,源和目标必须匹配 |
| transform | 是否启用动画 |
| transition-on-gesture | 手势返回时是否动画 |
| rect-tween-type | 动画轨迹(materialRectArc/linear) |
性能优化
列表性能
1. 使用 list 模式:确保只渲染可见区域
<scroll-view type="list" scroll-y cache-extent="500">2. 指定 cache-extent:预渲染视口外区域,优化滚动体验
3. 使用 list-item 属性:启用样式共享
<view wx:for="{{list}}" wx:key="id" list-item>swiper 性能
<!-- 使用 cache-extent 预渲染相邻页 -->
<swiper cache-extent="1">组件差异速查
| 组件 | WebView | Skyline | 备注 |
|---|---|---|---|
| scroll-view | 无 type | 必须 type | Skyline 增强 |
| text | space 属性 | overflow/max-lines | 不同特性 |
| image | 9种裁剪模式 | 5种缩放模式 | 裁剪模式仅 WebView |
| swiper | snap-to-edge | layout-type/indicator-type | 不同特性 |
| input | placeholder-class | worklet 回调 | 不同特性 |
相关技能
| 场景 | 推荐技能 | 说明 |
|---|---|---|
| 滚动 API | skyline-scroll-api | ScrollViewContext 接口 |
| 动画开发 | skyline-worklet | Worklet 动画系统 |
| 样式开发 | skyline-wxss | WXSS 支持情况 |
| 路由转场 | skyline-route | 页面转场动画 |
References 目录结构
references/
├── scroll/ # 滚动相关组件
│ ├── scroll-view.md # scroll-view 完整文档
│ ├── nested-scroll.md # 嵌套滚动模式
│ ├── list-grid-view.md # list-view/grid-view
│ ├── sticky.md # 吸顶布局
│ └── draggable-sheet.md # 半屏可拖拽
├── layout/ # 布局组件
│ └── swiper.md # swiper 增强特性
├── form/ # 表单组件
│ └── input.md # input/textarea
├── media/ # 媒体组件
│ ├── text.md # text/span
│ └── image.md # image
└── special/ # 特殊组件
├── share-element.md # 共享元素
└── snapshot.md # 截图组件input 与 textarea 组件
概述
Skyline 下的 input 和 textarea 组件在保持与 WebView 兼容的基础上,新增了 worklet 键盘回调、输入法组合事件等增强特性,可以实现更流畅的键盘交互体验。
input 组件
基本用法
<input
value="{{value}}"
placeholder="请输入内容"
bindinput="onInput"
bindconfirm="onConfirm"
/>通用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| value | string | - | 输入框初始内容 |
| type | string | text | 输入类型 |
| password | boolean | false | 密码类型 |
| placeholder | string | - | 占位提示 |
| placeholder-style | string | - | 占位符样式 |
| disabled | boolean | false | 禁用 |
| maxlength | number | 140 | 最大长度,-1 为不限制 |
| cursor-spacing | number | 0 | 光标与键盘距离 |
| focus | boolean | false | 获取焦点 |
| confirm-type | string | done | 键盘右下角按钮文字 |
| confirm-hold | boolean | false | 点击完成不收起键盘 |
| cursor | number | - | 光标位置 |
| cursor-color | string | - | 光标颜色 |
| selection-start | number | -1 | 选区起始位置 |
| selection-end | number | -1 | 选区结束位置 |
| adjust-position | boolean | true | 键盘弹起时上推页面 |
| hold-keyboard | boolean | false | 点击页面不收起键盘 |
type 属性值
| 值 | 说明 |
|---|---|
| text | 文本键盘 |
| number | 数字键盘 |
| idcard | 身份证键盘 |
| digit | 带小数点数字键盘 |
| nickname | 昵称键盘(2.21.2+) |
confirm-type 属性值
| 值 | 按钮文字 |
|---|---|
| send | 发送 |
| search | 搜索 |
| next | 下一个 |
| go | 前往 |
| done | 完成 |
通用事件
| 事件 | 说明 | detail |
|---|---|---|
| bindinput | 输入时触发 | { value, cursor, keyCode } |
| bindchange | 内容改变(非聚焦) | { value } |
| bindfocus | 聚焦 | { value, height } |
| bindblur | 失焦 | { value } |
| bindconfirm | 点击完成按钮 | { value } |
| bindkeyboardheightchange | 键盘高度变化 | { height, duration } |
Skyline 特有属性
| 属性 | 类型 | 说明 |
|---|---|---|
| bind:selectionchange | event | 选区改变事件 |
| bind:keyboardcompositionstart | event | 输入法开始输入 |
| bind:keyboardcompositionupdate | event | 输入法输入字符 |
| bind:keyboardcompositionend | event | 输入法输入结束 |
| worklet:onkeyboardheightchange | worklet | 键盘高度变化(UI线程) |
Worklet 键盘回调
<input
value="{{value}}"
worklet:onkeyboardheightchange="onKeyboardHeight"
/>Page({
onKeyboardHeight(e) {
'worklet'
// 在 UI 线程执行,可实现同步动画
const { height, pageBottomPadding } = e.detail
// height: 键盘高度
// pageBottomPadding: 页面上推高度
}
})使用场景:
- 键盘弹起时同步调整其他元素位置
- 实现自定义的键盘跟随动画
输入法组合事件
<input
bind:keyboardcompositionstart="onCompositionStart"
bind:keyboardcompositionupdate="onCompositionUpdate"
bind:keyboardcompositionend="onCompositionEnd"
/>Page({
onCompositionStart(e) {
// 输入法开始新的输入(如拼音输入开始)
this.isComposing = true
},
onCompositionUpdate(e) {
// 输入法正在输入(如拼音字母输入中)
},
onCompositionEnd(e) {
// 输入法输入完成(如选择了中文字符)
this.isComposing = false
}
})使用场景:
- 实时搜索时避免在拼音输入过程中触发搜索
- 联想输入时等待输入法完成再处理
选区变化事件
<input bind:selectionchange="onSelectionChange" />Page({
onSelectionChange(e) {
const { selectionStart, selectionEnd } = e.detail
// 可用于实现富文本工具栏状态同步
}
})textarea 组件
textarea 的使用方式与 input 基本一致,主要区别:
特有属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| auto-height | boolean | false | 自动增高 |
| fixed | boolean | false | 在 position:fixed 区域时需设置 |
| show-confirm-bar | boolean | true | 是否显示完成栏 |
多行输入
<textarea
value="{{content}}"
placeholder="请输入详细描述"
auto-height
maxlength="500"
bindinput="onContentInput"
/>示例代码
搜索框
<view class="search-box">
<input
class="search-input"
type="text"
placeholder="搜索"
confirm-type="search"
value="{{keyword}}"
bindinput="onSearchInput"
bindconfirm="onSearch"
bind:keyboardcompositionend="onCompositionEnd"
/>
<view class="search-btn" bindtap="onSearch">搜索</view>
</view>Page({
data: {
keyword: '',
isComposing: false
},
onSearchInput(e) {
this.setData({ keyword: e.detail.value })
// 非输入法组合状态下才触发实时搜索
if (!this.isComposing) {
this.doSearch(e.detail.value)
}
},
onCompositionEnd(e) {
this.isComposing = false
this.doSearch(this.data.keyword)
},
onSearch() {
this.doSearch(this.data.keyword)
},
doSearch(keyword) {
// 执行搜索逻辑
}
})聊天输入框
<view class="chat-input" style="padding-bottom: {{bottomPadding}}px;">
<input
class="input"
value="{{message}}"
placeholder="输入消息"
confirm-type="send"
hold-keyboard
adjust-position="{{false}}"
bindinput="onMessageInput"
bindconfirm="sendMessage"
worklet:onkeyboardheightchange="onKeyboardHeight"
/>
<view class="send-btn" bindtap="sendMessage">发送</view>
</view>Page({
data: {
message: '',
bottomPadding: 0
},
onKeyboardHeight(e) {
'worklet'
const { height } = e.detail
// 使用 runOnJS 通知逻辑线程更新状态
wx.worklet.runOnJS(() => {
this.setData({ bottomPadding: height })
})()
},
sendMessage() {
if (!this.data.message.trim()) return
// 发送消息
this.setData({ message: '' })
}
})表单验证
<view class="form-item">
<text class="label">手机号</text>
<input
type="number"
maxlength="11"
placeholder="请输入手机号"
value="{{phone}}"
bindinput="onPhoneInput"
bindblur="validatePhone"
/>
<text wx:if="{{phoneError}}" class="error">{{phoneError}}</text>
</view>
<view class="form-item">
<text class="label">验证码</text>
<input
type="number"
maxlength="6"
placeholder="请输入验证码"
value="{{code}}"
bindinput="onCodeInput"
/>
<view
class="code-btn {{countdown > 0 ? 'disabled' : ''}}"
bindtap="getCode"
>
{{countdown > 0 ? countdown + 's' : '获取验证码'}}
</view>
</view>WebView 特有属性
以下属性仅在 WebView 下生效:
| 属性 | 说明 |
|---|---|
| placeholder-class | 占位符样式类 |
| safe-password-* | 安全键盘相关属性 |
注意事项
1. input 是原生组件,字体是系统字体,无法设置 font-family 2. 在 input 聚焦期间,避免使用 CSS 动画 3. 自定义组件中的 input 需要使用 wx://form-field behavior 才能被外层 form 获取 4. 键盘高度变化事件可能多次触发,应忽略相同高度值 5. Skyline 下 cursor-color 无限制,支持任意颜色值
swiper 组件增强特性
概述
Skyline 下的 swiper 组件在保持与 WebView 兼容的基础上,新增了多种布局模式(layout-type)、过渡动画(transformer-type)和指示器动画(indicator-type)等增强特性。
基本用法
<swiper
indicator-dots
autoplay
circular
style="height: 200px;"
>
<swiper-item wx:for="{{banners}}" wx:key="id">
<image src="{{item.image}}" mode="aspectFill" />
</swiper-item>
</swiper>通用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| indicator-dots | boolean | false | 显示指示点 |
| indicator-color | color | rgba(0,0,0,0.3) | 指示点颜色 |
| indicator-active-color | color | #000 | 当前指示点颜色 |
| autoplay | boolean | false | 自动播放 |
| current | number | 0 | 当前滑块索引 |
| interval | number | 5000 | 自动播放间隔(ms) |
| duration | number | 500 | 滑动动画时长(ms) |
| circular | boolean | false | 循环滑动 |
| vertical | boolean | false | 纵向滑动 |
| display-multiple-items | number | 1 | 同时显示滑块数 |
| previous-margin | string | "0px" | 前边距 |
| next-margin | string | "0px" | 后边距 |
| easing-function | string | "default" | 缓动函数 |
Skyline 特有属性
layout-type 布局类型
| 值 | 效果 | 说明 |
|---|---|---|
| normal | 默认 | 标准轮播 |
| stackLeft | 左向堆叠 | 卡片向左堆叠效果 |
| stackRight | 右向堆叠 | 卡片向右堆叠效果 |
| tinder | 滑动卡片 | 类似 Tinder App 的卡片效果 |
| transformer | 过渡动画 | 配合 transformer-type 使用 |
transformer-type 过渡动画
当 layout-type="transformer" 时生效:
| 值 | 效果 |
|---|---|
| scaleAndFade | 缩放淡入淡出 |
| accordion | 手风琴效果 |
| threeD | 3D 翻转 |
| zoomIn | 放大进入 |
| zoomOut | 缩小退出 |
| deepthPage | 深度页面 |
indicator-type 指示器动画
| 值 | 效果 |
|---|---|
| normal | 默认圆点 |
| worm | 蠕虫动画 |
| wormThin | 细蠕虫 |
| wormUnderground | 下沉蠕虫 |
| wormThinUnderground | 细下沉蠕虫 |
| expand | 展开动画 |
| jump | 跳跃动画 |
| jumpWithOffset | 偏移跳跃 |
| scroll | 滚动指示器 |
| scrollFixedCenter | 固定中心滚动 |
| slide | 滑动动画 |
| slideUnderground | 下沉滑动 |
| scale | 缩放动画 |
| swap | 交换动画 |
| swapYRotation | Y轴旋转交换 |
| color | 颜色渐变 |
指示器样式属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| indicator-margin | number | 10 | 指示点四周边距 |
| indicator-spacing | number | 4 | 指示点间距 |
| indicator-radius | number | 4 | 指示点圆角 |
| indicator-width | number | 8 | 指示点宽度 |
| indicator-height | number | 8 | 指示点高度 |
| indicator-alignment | Array/string | auto | 指示点位置 |
| indicator-offset | Array | [0,0] | 指示点偏移 |
indicator-alignment
auto: 横向滑动时居底中,纵向滑动时居中右- 数组:
[x, y],取值范围 [-1, 1],如[0, 1]表示底边中点
其他 Skyline 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| scroll-with-animation | boolean | true | 改变 current 时使用动画 |
| cache-extent | number | 0 | 预渲染区域(1=上下各一屏) |
Worklet 回调
<swiper
worklet:onscrollstart="onScrollStart"
worklet:onscrollupdate="onScrollUpdate"
worklet:onscrollend="onScrollEnd"
>Page({
onScrollStart(e) {
'worklet'
console.log('开始滑动', e.detail.dx, e.detail.dy)
},
onScrollUpdate(e) {
'worklet'
// 可用于同步动画
const { dx, dy } = e.detail
},
onScrollEnd(e) {
'worklet'
console.log('滑动结束', e.detail.dx, e.detail.dy)
}
})示例代码
堆叠效果
<swiper
layout-type="stackLeft"
style="height: 300px;"
>
<swiper-item wx:for="{{cards}}" wx:key="id">
<view class="card" style="background: {{item.color}}">
{{item.title}}
</view>
</swiper-item>
</swiper>Tinder 卡片效果
<swiper
layout-type="tinder"
style="height: 400px;"
bindchange="onCardChange"
>
<swiper-item wx:for="{{users}}" wx:key="id">
<view class="user-card">
<image src="{{item.avatar}}" mode="aspectFill" />
<text>{{item.name}}, {{item.age}}</text>
</view>
</swiper-item>
</swiper>3D 过渡效果
<swiper
layout-type="transformer"
transformer-type="threeD"
style="height: 200px;"
>
<swiper-item wx:for="{{banners}}" wx:key="id">
<image src="{{item.image}}" mode="aspectFill" />
</swiper-item>
</swiper>蠕虫指示器
<swiper
indicator-dots
indicator-type="worm"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#fff"
style="height: 200px;"
>
<swiper-item wx:for="{{banners}}" wx:key="id">
<image src="{{item.image}}" mode="aspectFill" />
</swiper-item>
</swiper>自定义指示器位置
<swiper
indicator-dots
indicator-type="expand"
indicator-alignment="{{[0, 0.9]}}"
indicator-offset="{{[0, -20]}}"
indicator-width="6"
indicator-height="6"
indicator-spacing="6"
>
<!-- ... -->
</swiper>缓存预加载
<!-- 预渲染相邻一屏区域 -->
<swiper cache-extent="1">
<swiper-item wx:for="{{heavyItems}}" wx:key="id">
<!-- 复杂内容 -->
</swiper-item>
</swiper>注意事项
1. layout-type 为 stackLeft、stackRight 和 tinder 时仅支持 indicator-type=normal 2. indicator-type 为 scrollFixedCenter、swap、swapYRotation 时不支持循环模式 circular 3. Skyline 的 previous-margin、display-multiple-items 和 vertical 与 WebView 表现略有不同 4. 当 next-margin > 0 时,Skyline 会将上述属性对齐 WebView 实现
change 事件 source 字段
| 值 | 说明 |
|---|---|
| autoplay | 自动播放导致变化 |
| touch | 用户滑动导致变化 |
| "" | 其他原因 |
Page({
onSwiperChange(e) {
const { current, source } = e.detail
// 避免在 autoplay 时重复设置
if (source === 'touch') {
this.setData({ current })
}
}
})完整示例:图片轮播
<view class="banner-container">
<swiper
class="banner"
indicator-dots
indicator-type="worm"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#fff"
autoplay
circular
interval="{{4000}}"
duration="{{500}}"
>
<swiper-item wx:for="{{banners}}" wx:key="id">
<image
class="banner-image"
src="{{item.image}}"
mode="aspectFill"
bindtap="onBannerTap"
data-url="{{item.url}}"
/>
</swiper-item>
</swiper>
</view>.banner-container {
width: 100%;
padding: 12px;
box-sizing: border-box;
}
.banner {
height: 150px;
border-radius: 8px;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
}示例代码片段
增强特性演示:{% minicode('2XhTe3m67uTb') %} 基础示例:{% minicode('mI88kSm57nJ9') %}
image 组件
概述
Skyline 下的 image 组件默认懒加载,并新增了 fade-in 渐显效果属性。部分 WebView 的裁剪模式在 Skyline 下不支持。
基本用法
<image
src="https://example.com/image.jpg"
mode="aspectFill"
bindload="onImageLoad"
binderror="onImageError"
/>通用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| src | string | - | 图片地址(支持云文件ID) |
| mode | string | scaleToFill | 裁剪/缩放模式 |
| show-menu-by-longpress | boolean | false | 长按显示菜单 |
| binderror | event | - | 加载失败回调 |
| bindload | event | - | 加载完成回调 |
mode 属性
通用缩放模式
| 值 | 说明 | Skyline | WebView |
|---|---|---|---|
| scaleToFill | 拉伸填满(不保持比例) | ✅ | ✅ |
| aspectFit | 保持比例,完全显示(可能有留白) | ✅ | ✅ |
| aspectFill | 保持比例,填满(可能被裁剪) | ✅ | ✅ |
| widthFix | 宽度不变,高度自动 | ✅ | ✅ |
| heightFix | 高度不变,宽度自动 | ✅ | ✅ |
WebView-only 裁剪模式
以下模式仅在 WebView 下支持,Skyline 不支持:
| 值 | 说明 |
|---|---|
| top | 只显示顶部 |
| bottom | 只显示底部 |
| center | 只显示中间 |
| left | 只显示左边 |
| right | 只显示右边 |
| top left | 只显示左上 |
| top right | 只显示右上 |
| bottom left | 只显示左下 |
| bottom right | 只显示右下 |
Skyline 特有属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| fade-in | boolean | false | 图片加载完成后渐显 |
渐显效果
<image
src="{{imageUrl}}"
mode="aspectFill"
fade-in
/>使用场景:
- 图片列表加载
- 大图展示
- 提升视觉体验
WebView 特有属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| webp | boolean | false | 解析 webP 格式(仅网络图片) |
| lazy-load | boolean | false | 懒加载(进入三屏内才加载) |
| forceHttps | boolean | false | 自动将 http 替换为 https |
注意:Skyline 下默认懒加载,无需设置 lazy-load
事件回调
加载完成
Page({
onImageLoad(e) {
const { width, height } = e.detail
console.log(`图片尺寸: ${width} x ${height}`)
}
})加载失败
Page({
onImageError(e) {
console.log('图片加载失败:', e.detail.errMsg)
// 可设置默认图片
this.setData({ imageUrl: '/images/default.png' })
}
})长按菜单
<image
src="{{imageUrl}}"
show-menu-by-longpress
/>支持的菜单选项:
- 发送给朋友
- 收藏
- 保存图片
- 搜一搜
- 打开名片/前往群聊/打开小程序(若图片包含对应二维码)
支持识别的码
| 类型 | 最低版本 |
|---|---|
| 小程序码 | - |
| 微信个人码 | 2.18.0 |
| 企业微信个人码 | 2.18.0 |
| 普通群码 | 2.18.0 |
| 互通群码 | 2.18.0 |
| 公众号二维码 | 2.18.0 |
示例代码
头像显示
<image
class="avatar"
src="{{user.avatar}}"
mode="aspectFill"
binderror="onAvatarError"
/>.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: #f5f5f5;
}图片列表
<view class="image-list">
<image
wx:for="{{images}}"
wx:key="id"
class="image-item"
src="{{item.url}}"
mode="aspectFill"
fade-in
bindtap="previewImage"
data-index="{{index}}"
/>
</view>Page({
previewImage(e) {
const { index } = e.currentTarget.dataset
wx.previewImage({
current: this.data.images[index].url,
urls: this.data.images.map(img => img.url)
})
}
})自适应高度
<!-- widthFix: 宽度固定,高度自适应 -->
<image
class="banner"
src="{{bannerUrl}}"
mode="widthFix"
style="width: 100%;"
/>
<!-- heightFix: 高度固定,宽度自适应 -->
<image
class="icon"
src="{{iconUrl}}"
mode="heightFix"
style="height: 24px;"
/>带占位符的图片
<view class="image-wrapper">
<image
wx:if="{{imageLoaded}}"
src="{{imageUrl}}"
mode="aspectFill"
fade-in
bindload="onImageLoad"
/>
<view wx:else class="placeholder">
<text class="loading-text">加载中...</text>
</view>
</view>Page({
data: {
imageLoaded: false,
imageUrl: 'https://example.com/image.jpg'
},
onImageLoad() {
this.setData({ imageLoaded: true })
}
})错误处理
<image
src="{{imageUrl || defaultImage}}"
mode="aspectFill"
binderror="onImageError"
/>Page({
data: {
imageUrl: '',
defaultImage: '/images/default.png'
},
onImageError() {
this.setData({ imageUrl: '' })
}
})SVG 支持
注意事项
1. SVG 格式使用 mode="scaleToFill" 时:
- WebView:会居中显示(除非 SVG 添加
preserveAspectRatio="none") - Skyline:会撑满容器
2. SVG 格式不支持:
- 百分比单位
<style>元素
性能优化
1. 使用合适的 mode
<!-- 列表缩略图:使用 aspectFill 避免变形 -->
<image src="{{item.cover}}" mode="aspectFill" />
<!-- Banner:使用 widthFix 保持比例 -->
<image src="{{banner}}" mode="widthFix" style="width: 100%;" />2. 利用默认懒加载
Skyline 下图片默认懒加载,无需额外配置。
3. 使用渐显效果
<!-- 大图建议使用 fade-in 改善体验 -->
<image src="{{largeImage}}" mode="aspectFill" fade-in />4. 图片尺寸优化
使用 CDN 时指定合适的尺寸:
// 根据设备像素比获取合适尺寸
const ratio = wx.getSystemInfoSync().pixelRatio
const imageUrl = `${baseUrl}?width=${300 * ratio}`注意事项
1. 默认尺寸:image 组件默认宽度 320px、高度 240px 2. 小数精度:缩放后的宽高可能有小数,不同内核渲染可能会抹去小数 3. Skyline 懒加载:Skyline 默认懒加载,WebView 需要手动设置 lazy-load 4. 裁剪模式兼容:需要 WebView 裁剪模式时,应判断渲染引擎做兼容处理
示例代码片段
{% minicode('NwPpTRmS7AJf') %}
text 与 span 组件
概述
Skyline 对文本处理有特殊要求:内联文本只能使用 text 组件。同时,Skyline 新增了 span 组件用于图文混排,并为 text 组件增加了 overflow 和 max-lines 属性实现文本溢出处理。
核心规则
MUST(必须遵守)
1. 内联文本必须用 text 组件
<!-- ✅ 正确 -->
<text>Hello <text>World</text></text>
<!-- ❌ 错误 -->
<view>Hello <view>World</view></view>2. text 组件内只能嵌套 text
<!-- ✅ 正确 -->
<text>
<text style="color: red;">红色</text>文字
</text>
<!-- ❌ 错误 -->
<text>
<view>不能嵌套 view</view>
</text>text 组件
通用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| user-select | boolean | false | 文本是否可选(会使节点变为 inline-block) |
Skyline 特有属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| overflow | string | visible | 文本溢出处理 |
| max-lines | number | - | 最大行数限制 |
overflow 属性值
| 值 | 效果 |
|---|---|
| visible | 不截断(默认) |
| clip | 直接裁剪 |
| fade | 渐隐效果 |
| ellipsis | 显示省略号 |
WebView 特有属性
| 属性 | 类型 | 说明 |
|---|---|---|
| space | string | 显示连续空格:ensp/emsp/nbsp |
| decode | boolean | 是否解码 HTML 实体 |
文本溢出处理
单行省略
<text
overflow="ellipsis"
max-lines="1"
style="width: 200px;"
>
这是一段很长的文本内容,超出部分会显示省略号
</text>多行省略
<text
overflow="ellipsis"
max-lines="3"
style="width: 200px;"
>
这是一段很长的文本内容,
当文本超过三行时,
第三行末尾会显示省略号,
后面的内容会被截断...
</text>渐隐效果
<text
overflow="fade"
max-lines="1"
style="width: 200px;"
>
这是一段很长的文本,末尾会渐隐消失
</text>span 组件
span 是 Skyline 新增的内联容器组件,用于实现图文混排。
基本用法
<span>
<image src="icon.png" style="width: 16px; height: 16px;" />
<text>图文混排内容</text>
</span>图标 + 文字
<span class="tag">
<image class="tag-icon" src="/images/hot.png" />
<text class="tag-text">热门</text>
</span>.tag {
display: inline-flex;
align-items: center;
padding: 2px 8px;
background: #fff5f5;
border-radius: 4px;
}
.tag-icon {
width: 14px;
height: 14px;
margin-right: 4px;
}
.tag-text {
font-size: 12px;
color: #f5222d;
}文字 + 链接
<span>
<text>同意</text>
<navigator url="/pages/agreement/index">
<text style="color: #1890ff;">《用户协议》</text>
</navigator>
<text>和</text>
<navigator url="/pages/privacy/index">
<text style="color: #1890ff;">《隐私政策》</text>
</navigator>
</span>价格显示
<span class="price">
<text class="currency">¥</text>
<text class="amount">99</text>
<text class="decimal">.00</text>
</span>.price {
display: inline-flex;
align-items: baseline;
color: #f5222d;
}
.currency {
font-size: 12px;
}
.amount {
font-size: 24px;
font-weight: bold;
}
.decimal {
font-size: 12px;
}示例代码
文章内容
<view class="article">
<text class="title" max-lines="2" overflow="ellipsis">
{{article.title}}
</text>
<text class="summary" max-lines="3" overflow="ellipsis">
{{article.summary}}
</text>
<span class="meta">
<image class="avatar" src="{{article.author.avatar}}" />
<text class="author">{{article.author.name}}</text>
<text class="time">{{article.time}}</text>
</span>
</view>.article {
padding: 16px;
}
.title {
display: block;
font-size: 18px;
font-weight: bold;
line-height: 1.4;
color: #333;
}
.summary {
display: block;
margin-top: 8px;
font-size: 14px;
line-height: 1.6;
color: #666;
}
.meta {
display: flex;
align-items: center;
margin-top: 12px;
}
.avatar {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 8px;
}
.author {
font-size: 12px;
color: #999;
margin-right: 12px;
}
.time {
font-size: 12px;
color: #999;
}商品标签
<view class="product-title">
<span wx:for="{{product.tags}}" wx:key="*this" class="tag {{item}}">
<text>{{item === 'new' ? '新品' : item === 'hot' ? '热卖' : '特价'}}</text>
</span>
<text class="name" max-lines="2" overflow="ellipsis">
{{product.name}}
</text>
</view>.product-title {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.tag {
padding: 0 4px;
margin-right: 4px;
border-radius: 2px;
font-size: 10px;
}
.tag.new {
background: #e6f7ff;
color: #1890ff;
}
.tag.hot {
background: #fff2e8;
color: #fa541c;
}
.name {
flex: 1;
font-size: 14px;
color: #333;
line-height: 1.4;
}可选文本
<text user-select class="selectable-text">
这段文字可以被选中复制
</text>注意:设置 user-select 后,text 会变为 inline-block 显示注意事项
1. 文本节点:在 Skyline 中,纯文本必须用 <text> 包裹 2. 嵌套规则:text 内只能嵌套 text,不能嵌套其他组件 3. 长按选中:除了文本节点外的其他节点都无法长按选中 4. 操作系统差异:各系统的空格标准不一致 5. decode:可解析 < > & '    
WebView vs Skyline 对比
| 特性 | WebView | Skyline |
|---|---|---|
| 空格处理 | space 属性 | 不支持 |
| HTML 实体 | decode 属性 | 不支持 |
| 文本溢出 | CSS 实现 | overflow + max-lines |
| 图文混排 | 任意嵌套 | 必须用 span |
示例代码片段
{% minicode('LQxYkQmm7fJj') %}
draggable-sheet 半屏可拖拽组件
概述
draggable-sheet 是 Skyline 新增的半屏可拖拽组件,用于实现类似地图 App 底部面板的效果。用户可以通过拖拽来调整面板的高度。
基本用法
<draggable-sheet
class="sheet"
initial-child-size="0.5"
min-child-size="0.2"
max-child-size="0.8"
>
<scroll-view scroll-y type="list" associative-container="draggable-sheet">
<!-- 内容 -->
</scroll-view>
</draggable-sheet>属性说明
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| initial-child-size | number | 0.5 | 初始占父容器比例 |
| min-child-size | number | 0.25 | 最小占父容器比例 |
| max-child-size | number | 1.0 | 最大占父容器比例 |
| snap | boolean | false | 拖拽后是否自动对齐关键点 |
| snap-sizes | Array | [] | 对齐关键点(不含最小/最大值) |
| worklet:onsizeupdate | worklet | - | 尺寸变化回调(worklet) |
吸附功能
设置 snap 和 snap-sizes 可以让面板在拖拽松手后自动吸附到指定位置:
<draggable-sheet
snap="{{true}}"
snap-sizes="{{[0.3, 0.5, 0.7]}}"
min-child-size="0.2"
max-child-size="0.9"
>上例中,松手后面板会自动吸附到 0.2、0.3、0.5、0.7、0.9 中最近的位置。
配合 scroll-view 使用
重要:内部的 scroll-view 必须设置 associative-container="draggable-sheet" 才能正确联动。
<draggable-sheet class="sheet">
<scroll-view
scroll-y
type="list"
associative-container="draggable-sheet"
bounces="{{true}}"
>
<view wx:for="{{list}}" wx:key="id" class="item">
{{item.name}}
</view>
</scroll-view>
</draggable-sheet>联动策略:
- 向上拖拽时:先展开面板,面板到达最大高度后滚动内容
- 向下拖拽时:先滚动内容到顶部,然后收起面板
DraggableSheetContext API
通过 createSelectorQuery 获取组件节点后,可以使用以下 API:
scrollTo
Page({
onReady() {
this.createSelectorQuery()
.select('.sheet')
.node()
.exec(res => {
const sheetContext = res[0].node
// 滚动到指定位置
sheetContext.scrollTo({
size: 0.7, // 目标尺寸比例
animated: true, // 是否动画
duration: 300, // 动画时长(ms)
easingFunction: 'ease' // 缓动函数
})
})
}
})scrollTo 参数
| 参数 | 类型 | 说明 |
|---|---|---|
| size | number | 目标尺寸比例(0~1) |
| animated | boolean | 是否使用动画 |
| duration | number | 动画时长(毫秒) |
| easingFunction | string | 缓动函数 |
缓动函数可选值:
easeease-inease-outease-in-outlinear
Worklet 回调
使用 worklet:onsizeupdate 可以在 UI 线程监听尺寸变化:
<draggable-sheet
class="sheet"
worklet:onsizeupdate="onSizeUpdate"
>Page({
onSizeUpdate(e) {
'worklet'
// 在 UI 线程执行
const { pixels, size } = e
console.log(`尺寸变化: ${pixels}px, 比例: ${size}`)
// 可以在这里做同步动画
// 例如:根据面板高度调整其他元素透明度
}
})完整示例:地图底部面板
<view class="container">
<!-- 地图区域 -->
<map class="map" latitude="{{latitude}}" longitude="{{longitude}}" />
<!-- 底部面板 -->
<draggable-sheet
class="sheet"
initial-child-size="0.4"
min-child-size="0.15"
max-child-size="0.85"
snap="{{true}}"
snap-sizes="{{[0.4]}}"
worklet:onsizeupdate="onSizeUpdate"
>
<scroll-view
class="sheet-content"
scroll-y
type="list"
associative-container="draggable-sheet"
>
<!-- 拖拽指示条 -->
<view class="drag-handle">
<view class="handle-bar"></view>
</view>
<!-- 搜索框 -->
<view class="search-box">
<input placeholder="搜索地点" />
</view>
<!-- 附近地点列表 -->
<view class="section-title">附近地点</view>
<view
wx:for="{{nearbyPlaces}}"
wx:key="id"
class="place-item"
bindtap="onPlaceTap"
data-place="{{item}}"
>
<view class="place-icon">
<image src="{{item.icon}}" />
</view>
<view class="place-info">
<text class="place-name">{{item.name}}</text>
<text class="place-address">{{item.address}}</text>
</view>
<text class="place-distance">{{item.distance}}</text>
</view>
</scroll-view>
</draggable-sheet>
</view>.container {
position: relative;
height: 100vh;
}
.map {
width: 100%;
height: 100%;
}
.sheet {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 16px 16px 0 0;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
.sheet-content {
height: 100%;
}
.drag-handle {
display: flex;
justify-content: center;
padding: 12px 0;
}
.handle-bar {
width: 40px;
height: 4px;
background: #ddd;
border-radius: 2px;
}
.search-box {
margin: 0 16px 16px;
padding: 8px 12px;
background: #f5f5f5;
border-radius: 8px;
}
.section-title {
padding: 12px 16px;
font-size: 14px;
color: #999;
}
.place-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #eee;
}
.place-icon {
width: 40px;
height: 40px;
margin-right: 12px;
}
.place-icon image {
width: 100%;
height: 100%;
}
.place-info {
flex: 1;
}
.place-name {
display: block;
font-size: 15px;
color: #333;
}
.place-address {
display: block;
font-size: 12px;
color: #999;
margin-top: 4px;
}
.place-distance {
font-size: 12px;
color: #999;
}Page({
data: {
latitude: 39.908823,
longitude: 116.397470,
nearbyPlaces: [
{ id: 1, name: '天安门广场', address: '北京市东城区', distance: '500m', icon: '/images/landmark.png' },
{ id: 2, name: '故宫博物院', address: '北京市东城区景山前街4号', distance: '1.2km', icon: '/images/museum.png' },
// ...
]
},
onSizeUpdate(e) {
'worklet'
console.log('sheet size:', e.size)
},
onPlaceTap(e) {
const place = e.currentTarget.dataset.place
// 处理地点点击
},
// 展开面板
expandSheet() {
this.createSelectorQuery()
.select('.sheet')
.node()
.exec(res => {
res[0].node.scrollTo({
size: 0.85,
animated: true,
duration: 300
})
})
},
// 收起面板
collapseSheet() {
this.createSelectorQuery()
.select('.sheet')
.node()
.exec(res => {
res[0].node.scrollTo({
size: 0.15,
animated: true,
duration: 300
})
})
}
})注意事项
1. 仅限 Skyline:该组件仅在 Skyline 渲染模式下可用 2. 关联容器:内部 scroll-view 必须设置 associative-container="draggable-sheet" 3. 尺寸比例:所有尺寸属性都是相对于父容器的比例(0~1) 4. worklet 回调:onsizeupdate 仅支持 worklet 函数
示例代码片段
{% minicode('K5M2NamJ7ILV') %}
list-view 与 grid-view
概述
list-view 和 grid-view 是 Skyline 新增的布局容器组件,用于实现列表和网格布局。它们必须作为 <scroll-view type="custom"> 或 <sticky-section> 的直接子节点使用。
list-view 列表布局
基本用法
<scroll-view type="custom" scroll-y style="height: 100vh;">
<list-view>
<view wx:for="{{list}}" wx:key="id">
{{item.name}}
</view>
</list-view>
</scroll-view>属性说明
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| padding | Array | [0,0,0,0] | 内边距 [top, right, bottom, left] |
配合吸顶使用
<scroll-view type="custom" scroll-y>
<sticky-section>
<sticky-header>分类 A</sticky-header>
<list-view>
<view wx:for="{{categoryA}}" wx:key="id">{{item.name}}</view>
</list-view>
</sticky-section>
<sticky-section>
<sticky-header>分类 B</sticky-header>
<list-view>
<view wx:for="{{categoryB}}" wx:key="id">{{item.name}}</view>
</list-view>
</sticky-section>
</scroll-view>grid-view 网格/瀑布流布局
基本用法
<scroll-view type="custom" scroll-y>
<grid-view type="aligned" cross-axis-count="2">
<view wx:for="{{list}}" wx:key="id" class="grid-item">
{{item.name}}
</view>
</grid-view>
</scroll-view>属性说明
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string | - | 布局类型:aligned(网格)/masonry(瀑布流) |
| cross-axis-count | number | 2 | 交叉轴元素数量(列数) |
| main-axis-gap | number | 0 | 主轴方向间距 |
| cross-axis-gap | number | 0 | 交叉轴方向间距 |
| padding | Array | [0,0,0,0] | 内边距 |
| max-cross-axis-extent | number | - | 交叉轴元素最大范围 |
type 属性
| 值 | 说明 |
|---|---|
| aligned | 网格布局,所有项等高 |
| masonry | 瀑布流布局,项高度可不同 |
网格布局示例
<scroll-view type="custom" scroll-y>
<grid-view
type="aligned"
cross-axis-count="3"
cross-axis-gap="8"
main-axis-gap="8"
>
<view
wx:for="{{products}}"
wx:key="id"
class="product-card"
>
<image src="{{item.cover}}" mode="aspectFill" />
<text>{{item.name}}</text>
<text class="price">¥{{item.price}}</text>
</view>
</grid-view>
</scroll-view>瀑布流布局示例
<scroll-view type="custom" scroll-y>
<grid-view
type="masonry"
cross-axis-count="2"
cross-axis-gap="12"
main-axis-gap="12"
>
<view
wx:for="{{images}}"
wx:key="id"
class="masonry-item"
>
<!-- 瀑布流中每项高度可以不同 -->
<image
src="{{item.url}}"
mode="widthFix"
style="width: 100%;"
/>
<text>{{item.title}}</text>
</view>
</grid-view>
</scroll-view>list-builder 列表构造器
列表构造器用于实现可回收的虚拟列表,适合超长列表场景。
基本用法
<scroll-view type="custom" scroll-y>
<list-builder
list="{{list}}"
child-count="{{list.length}}"
child-height="200"
bind:itembuild="onItemBuild"
bind:itemdispose="onItemDispose"
>
<view slot:item slot:index style="height: 200px;">
<view>{{index}}</view>
</view>
</list-builder>
</scroll-view>属性说明
| 属性 | 类型 | 说明 |
|---|---|---|
| list | Array | 数据列表 |
| child-count | number | 列表项数量 |
| child-height | number | 列表项高度(定高模式必填) |
事件说明
| 事件 | 说明 | detail |
|---|---|---|
| bind:itembuild | 列表项被创建 | { index } |
| bind:itemdispose | 列表项被回收 | { index } |
完整示例
<scroll-view type="custom" scroll-y style="height: 100vh;">
<list-builder
list="{{list}}"
child-count="{{list.length}}"
child-height="100"
bind:itembuild="onItemBuild"
bind:itemdispose="onItemDispose"
>
<view slot:item slot:index class="list-item">
<image class="avatar" src="{{item.avatar}}" />
<view class="info">
<text class="name">{{item.name}}</text>
<text class="desc">{{item.desc}}</text>
</view>
</view>
</list-builder>
</scroll-view>Page({
data: {
list: Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `用户 ${i}`,
desc: `这是第 ${i} 个用户的描述`,
avatar: `https://example.com/avatar/${i}.jpg`
}))
},
onItemBuild(e) {
console.log('创建列表项:', e.detail.index)
},
onItemDispose(e) {
console.log('回收列表项:', e.detail.index)
}
})注意事项
1. scroll-view 必须设置 type="custom" 2. 列表项默认为定高模式,需通过 child-height 指定 3. 不定高模式会存在滚动条跳动问题 4. 只支持纵向滚动 5. 不支持 scroll-into-view
grid-builder 网格构造器
网格构造器与列表构造器类似,用于可回收的网格布局。
基本用法
<scroll-view type="custom" scroll-y>
<grid-builder
list="{{list}}"
child-count="{{list.length}}"
cross-axis-count="4"
cross-axis-gap="8"
main-axis-gap="8"
>
<view slot:item slot:index class="grid-item">
<view>{{index}}</view>
</view>
</grid-builder>
</scroll-view>属性说明
| 属性 | 类型 | 说明 |
|---|---|---|
| list | Array | 数据列表 |
| child-count | number | 网格项数量 |
| cross-axis-count | number | 交叉轴元素数量(列数) |
| cross-axis-gap | number | 交叉轴间距 |
| main-axis-gap | number | 主轴间距 |
使用规则
MUST(必须遵守)
1. list-view/grid-view 必须在 type="custom" 的 scroll-view 下使用 2. 使用 list-builder 时必须指定 child-height(定高模式) 3. 网格布局必须指定 cross-axis-count
NEVER(禁止行为)
1. 不要在 type="list" 的 scroll-view 中使用 list-view/grid-view 2. 不要在 list-builder/grid-builder 外直接放置列表项
性能对比
| 方案 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| scroll-view type="list" | 普通列表 | 简单易用 | 大数据量性能一般 |
| list-view | 分组列表 | 配合吸顶 | 需要 custom 模式 |
| list-builder | 超长列表(1000+) | 真正虚拟列表 | 配置复杂,定高限制 |
| grid-view | 网格/瀑布流 | 布局灵活 | 需要 custom 模式 |
| grid-builder | 超长网格 | 虚拟网格 | 配置复杂 |
示例代码片段
列表构造器:{% minicode('wiAQEwmN7VRz') %} 网格构造器:{% minicode('GSAP4wms7LRe') %}
嵌套滚动模式
概述
在 Skyline 渲染模式下,当存在两个 scroll-view 相互嵌套的场景时(如 Tab 列表页),两者的滚动不能很丝滑地衔接。使用嵌套模式(type="nested")可以让父子 scroll-view 的滚动无缝联动。
基本结构
<!-- 外层 scroll-view -->
<scroll-view type="nested" scroll-y>
<nested-scroll-header>
<view>头部区域(会被滚动走)</view>
</nested-scroll-header>
<nested-scroll-body>
<!-- 内层可滚动区域 -->
</nested-scroll-body>
</scroll-view>组件说明
nested-scroll-header
头部区域组件,会随着外层 scroll-view 滚动而滚走。
- 只能渲染在
nested-scroll-body上面 - 只能有一个子节点
- 可以有多个
nested-scroll-header
nested-scroll-body
主体区域组件,包含内层可滚动内容。
- 外层 scroll-view 只能有一个
nested-scroll-body - 只能有一个子节点
典型应用:Tab + 列表
<scroll-view type="nested" scroll-y style="height: 100vh;">
<!-- 头部:Banner + Tab -->
<nested-scroll-header>
<view class="banner">
<image src="banner.jpg" />
</view>
</nested-scroll-header>
<nested-scroll-header>
<view class="tabs">
<view
wx:for="{{tabs}}"
wx:key="id"
class="tab {{currentTab === index ? 'active' : ''}}"
bindtap="switchTab"
data-index="{{index}}"
>
{{item.name}}
</view>
</view>
</nested-scroll-header>
<!-- 主体:Tab 内容 -->
<nested-scroll-body>
<swiper
current="{{currentTab}}"
bindchange="onSwiperChange"
style="height: 100%;"
>
<swiper-item wx:for="{{tabs}}" wx:key="id">
<!-- 内层列表必须关联外层嵌套滚动 -->
<scroll-view
type="list"
scroll-y
associative-container="nested-scroll-view"
style="height: 100%;"
>
<view wx:for="{{item.list}}" wx:key="id" wx:for-item="listItem">
{{listItem.name}}
</view>
</scroll-view>
</swiper-item>
</swiper>
</nested-scroll-body>
</scroll-view>Page({
data: {
currentTab: 0,
tabs: [
{ id: 1, name: '推荐', list: [] },
{ id: 2, name: '热门', list: [] },
{ id: 3, name: '最新', list: [] }
]
},
switchTab(e) {
this.setData({ currentTab: e.currentTarget.dataset.index })
},
onSwiperChange(e) {
this.setData({ currentTab: e.detail.current })
}
})滚动策略
嵌套滚动的联动策略:
| 滚动方向 | 策略 |
|---|---|
| 向下滚动 | 先滚动外层 scroll-view,再滚动内层 scroll-view |
| 向上滚动 | 先滚动内层 scroll-view,再滚动外层 scroll-view |
这种策略确保了:
- 下滑时先看到完整头部消失
- 上滑时先让列表滚到顶部,再显示头部
配合下拉刷新
<scroll-view
type="nested"
scroll-y
refresher-enabled="{{true}}"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh"
>
<view slot="refresher">自定义 refresher</view>
<nested-scroll-header>
<view>头部</view>
</nested-scroll-header>
<nested-scroll-body>
<scroll-view
type="list"
scroll-y
associative-container="nested-scroll-view"
>
<!-- 列表内容 -->
</scroll-view>
</nested-scroll-body>
</scroll-view>注意:下拉刷新的 slot="refresher" 与 nested-scroll-header、nested-scroll-body 是平级的。
使用规则
MUST(必须遵守)
1. 外层 scroll-view 必须设置 type="nested" 2. 内层 scroll-view 必须设置 associative-container="nested-scroll-view" 3. nested-scroll-header 和 nested-scroll-body 只能有一个子节点 4. 外层只能有一个 nested-scroll-body
NEVER(禁止行为)
1. 不要在 nested-scroll-body 下面放 nested-scroll-header 2. 不要有多个 nested-scroll-body 3. 不要忘记给内层 scroll-view 设置 associative-container
完整示例
<!-- index.wxml -->
<scroll-view
class="container"
type="nested"
scroll-y
refresher-enabled="{{true}}"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh"
>
<view slot="refresher" class="refresher">
<view class="loading">{{refreshing ? '刷新中...' : '下拉刷新'}}</view>
</view>
<nested-scroll-header>
<view class="header">
<image class="banner" src="/images/banner.jpg" mode="aspectFill" />
</view>
</nested-scroll-header>
<nested-scroll-header>
<view class="tab-bar">
<view
wx:for="{{tabs}}"
wx:key="id"
class="tab-item {{currentTab === index ? 'active' : ''}}"
bindtap="onTabTap"
data-index="{{index}}"
>
{{item.title}}
</view>
</view>
</nested-scroll-header>
<nested-scroll-body>
<swiper
class="tab-content"
current="{{currentTab}}"
bindchange="onSwiperChange"
>
<swiper-item wx:for="{{tabs}}" wx:key="id">
<scroll-view
class="list-scroll"
type="list"
scroll-y
associative-container="nested-scroll-view"
bindscrolltolower="onLoadMore"
data-tab="{{index}}"
>
<view
wx:for="{{item.list}}"
wx:key="id"
wx:for-item="card"
class="card"
>
<image class="card-cover" src="{{card.cover}}" />
<text class="card-title">{{card.title}}</text>
</view>
<view wx:if="{{item.loading}}" class="loading-more">
加载中...
</view>
</scroll-view>
</swiper-item>
</swiper>
</nested-scroll-body>
</scroll-view>/* index.wxss */
.container {
height: 100vh;
}
.banner {
width: 100%;
height: 200px;
}
.tab-bar {
display: flex;
background: #fff;
position: sticky;
top: 0;
}
.tab-item {
flex: 1;
text-align: center;
padding: 12px 0;
font-size: 14px;
}
.tab-item.active {
color: #07c160;
border-bottom: 2px solid #07c160;
}
.tab-content {
height: calc(100vh - 44px); /* 减去 tab-bar 高度 */
}
.list-scroll {
height: 100%;
}
.card {
padding: 12px;
border-bottom: 1px solid #eee;
}// index.js
Page({
data: {
refreshing: false,
currentTab: 0,
tabs: [
{ id: 1, title: '推荐', list: [], loading: false },
{ id: 2, title: '关注', list: [], loading: false },
{ id: 3, title: '热榜', list: [], loading: false }
]
},
onLoad() {
this.loadData(0)
},
onRefresh() {
this.setData({ refreshing: true })
this.loadData(this.data.currentTab).then(() => {
this.setData({ refreshing: false })
})
},
onTabTap(e) {
const index = e.currentTarget.dataset.index
this.setData({ currentTab: index })
if (!this.data.tabs[index].list.length) {
this.loadData(index)
}
},
onSwiperChange(e) {
const index = e.detail.current
this.setData({ currentTab: index })
if (!this.data.tabs[index].list.length) {
this.loadData(index)
}
},
onLoadMore(e) {
const tabIndex = e.currentTarget.dataset.tab
this.loadMore(tabIndex)
},
async loadData(tabIndex) {
// 模拟加载数据
const list = await this.fetchList(tabIndex, 0)
this.setData({
[`tabs[${tabIndex}].list`]: list
})
},
async loadMore(tabIndex) {
const tab = this.data.tabs[tabIndex]
if (tab.loading) return
this.setData({ [`tabs[${tabIndex}].loading`]: true })
const newList = await this.fetchList(tabIndex, tab.list.length)
this.setData({
[`tabs[${tabIndex}].list`]: [...tab.list, ...newList],
[`tabs[${tabIndex}].loading`]: false
})
},
fetchList(tabIndex, offset) {
return new Promise(resolve => {
setTimeout(() => {
const list = Array.from({ length: 10 }, (_, i) => ({
id: `${tabIndex}-${offset + i}`,
title: `Tab ${tabIndex + 1} - Item ${offset + i + 1}`,
cover: 'https://example.com/image.jpg'
}))
resolve(list)
}, 1000)
})
}
})示例代码片段
{% minicode('1IaEOym777Mx') %}
scroll-view 组件详解
概述
scroll-view 是 Skyline 中最核心的滚动容器组件。相比 WebView,Skyline 的 scroll-view 增加了多种渲染模式,支持按需渲染、嵌套滚动、下拉二级等高级功能。
必要配置
<!-- Skyline 必须指定 type 属性 -->
<scroll-view type="list" scroll-y style="height: 100vh;">
<!-- 内容 -->
</scroll-view>⚠️ MUST: Skyline 下使用 scroll-view 必须设置 type 属性,否则性能会退化。type 属性
| 值 | 说明 | 适用场景 |
|---|---|---|
| list | 列表模式,根据直接子节点是否在屏按需渲染 | 普通长列表 |
| custom | 自定义模式,支持 list-view/grid-view/sticky-section | 复杂布局 |
| nested | 嵌套模式,处理父子 scroll-view 滚动联动 | Tab + 列表 |
list 模式
<scroll-view type="list" scroll-y>
<!-- 直接子节点会按需渲染 -->
<view wx:for="{{items}}" wx:key="id">{{item.name}}</view>
</scroll-view>注意事项:
- 只会渲染在屏节点
- 若只有一个直接子节点,性能会退化
- 列表项必须是 scroll-view 的直接子节点
custom 模式
<scroll-view type="custom" scroll-y>
<sticky-section>
<sticky-header>分类标题</sticky-header>
<list-view>
<!-- 列表内容 -->
</list-view>
</sticky-section>
</scroll-view>支持的子组件:
sticky-section/sticky-headerlist-view/grid-viewlist-builder/grid-builder
nested 模式
<scroll-view type="nested" scroll-y>
<nested-scroll-header>
<view>头部区域(会被滚动走)</view>
</nested-scroll-header>
<nested-scroll-body>
<scroll-view type="list" associative-container="nested-scroll-view">
<!-- 内层可滚动内容 -->
</scroll-view>
</nested-scroll-body>
</scroll-view>通用属性
滚动控制
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| scroll-x | boolean | false | 允许横向滚动 |
| scroll-y | boolean | false | 允许纵向滚动 |
| scroll-top | number | - | 设置竖向滚动位置 |
| scroll-left | number | - | 设置横向滚动位置 |
| scroll-into-view | string | - | 滚动到指定 id 元素 |
| scroll-into-view-offset | number | 0 | scroll-into-view 额外偏移 |
| scroll-with-animation | boolean | false | 滚动时使用动画 |
| upper-threshold | number | 50 | 触发 scrolltoupper 的距离 |
| lower-threshold | number | 50 | 触发 scrolltolower 的距离 |
滚动事件
| 事件 | 说明 | detail |
|---|---|---|
| bindscrolltoupper | 滚动到顶部/左边 | - |
| bindscrolltolower | 滚动到底部/右边 | - |
| bindscroll | 滚动时触发 | scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY |
Skyline 特有属性
渲染优化
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| cache-extent | number | - | 视口外预渲染距离(px) |
| reverse | boolean | false | 反向滚动(初始在底部) |
| clip | boolean | true | 是否裁剪溢出内容 |
| min-drag-distance | number | 18 | 触发滚动的最小拖动距离 |
| padding | Array | [0,0,0,0] | 内边距 [top, right, bottom, left] |
scroll-into-view 增强
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| scroll-into-view-alignment | string | start | 目标节点位置:start/center/end/nearest |
| scroll-into-view-within-extent | boolean | false | 仅滚动到 cache-extent 内的节点 |
Worklet 回调
| 属性 | 说明 |
|---|---|
| worklet:onscrollstart | 滚动开始,仅支持 worklet 回调 |
| worklet:onscrollupdate | 滚动中,仅支持 worklet 回调 |
| worklet:onscrollend | 滚动结束,仅支持 worklet 回调 |
| worklet:adjust-deceleration-velocity | 调整惯性滚动初速度 |
Page({
onScrollUpdate(e) {
'worklet'
// 在 UI 线程执行
const { scrollTop, scrollLeft, isDrag } = e.detail
}
})关联容器
| 属性 | 类型 | 说明 |
|---|---|---|
| associative-container | string | 关联的滚动容器 |
合法值:
draggable-sheet: 关联半屏拖拽组件nested-scroll-view: 关联嵌套滚动父容器pop-gesture: 关联页面手势返回
下拉刷新
基础配置
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| refresher-enabled | boolean | false | 开启下拉刷新 |
| refresher-threshold | number | 45 | 刷新阈值 |
| refresher-default-style | string | "black" | 默认样式:black/white/none |
| refresher-background | string | - | 刷新区域背景色 |
| refresher-triggered | boolean | false | 当前刷新状态 |
刷新事件
| 事件 | 说明 |
|---|---|
| bindrefresherpulling | 下拉中 |
| bindrefresherrefresh | 触发刷新 |
| bindrefresherrestore | 刷新复位 |
| bindrefresherabort | 刷新中止 |
| bind:refresherwillrefresh | 即将触发刷新(Skyline) |
| bind:refresherstatuschange | 刷新状态变化(Skyline) |
自定义刷新区域
<scroll-view
type="list"
scroll-y
refresher-enabled="{{true}}"
refresher-default-style="none"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh"
bind:refresherstatuschange="onStatusChange"
>
<view slot="refresher" class="custom-refresher">
<view wx:if="{{status === 0}}">下拉刷新</view>
<view wx:elif="{{status === 1}}">松手刷新</view>
<view wx:elif="{{status === 2}}">刷新中...</view>
<view wx:elif="{{status === 3}}">刷新完成</view>
</view>
<!-- 列表内容 -->
</scroll-view>Page({
data: {
refreshing: false,
status: 0 // RefreshStatus 枚举值
},
onStatusChange(e) {
this.setData({ status: e.detail.status })
},
onRefresh() {
this.setData({ refreshing: true })
// 加载数据
setTimeout(() => {
this.setData({ refreshing: false })
}, 2000)
}
})RefreshStatus 枚举
const RefreshStatus = {
Idle: 0, // 空闲
CanRefresh: 1, // 可刷新(超过阈值)
Refreshing: 2, // 刷新中
Completed: 3, // 刷新完成
Failed: 4, // 刷新失败
CanTwoLevel: 5, // 可进入二级
TwoLevelOpening: 6,// 二级打开中
TwoLeveling: 7, // 二级已打开
TwoLevelClosing: 8 // 二级关闭中
}下拉二级
下拉二级是下拉刷新的扩展,继续下拉可进入"二楼"页面。
配置
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| refresher-two-level-enabled | boolean | false | 开启下拉二级 |
| refresher-two-level-triggered | boolean | false | 设置打开/关闭二级 |
| refresher-two-level-threshold | number | 150 | 二级阈值 |
| refresher-two-level-close-threshold | number | 80 | 关闭二级阈值 |
| refresher-two-level-scroll-enabled | boolean | false | 二级状态可滑动 |
| refresher-two-level-pinned | boolean | false | 即将打开二级时是否定住 |
| refresher-ballistic-refresh-enabled | boolean | false | 惯性滚动触发刷新 |
示例
<scroll-view
type="list"
scroll-y
refresher-enabled="{{true}}"
refresher-two-level-enabled="{{true}}"
refresher-two-level-scroll-enabled="{{true}}"
refresher-two-level-threshold="{{150}}"
bind:refresherstatuschange="onStatusChange"
>
<view slot="refresher" class="refresher-container">
<!-- 根据 status 显示不同内容 -->
<view wx:if="{{status < 5}}">下拉刷新区域</view>
<view wx:else class="two-level-content">
二级页面内容
</view>
</view>
<!-- 列表内容 -->
</scroll-view>相关 API
ScrollViewContext.triggerRefresh()- 触发下拉刷新ScrollViewContext.closeRefresh()- 关闭下拉刷新ScrollViewContext.triggerTwoLevel()- 触发下拉二级ScrollViewContext.closeTwoLevel()- 关闭下拉二级
横向滚动
<!-- 横向滚动需同时开启 enable-flex -->
<scroll-view
scroll-x
enable-flex
style="flex-direction: row; white-space: nowrap;"
>
<view style="display: inline-block; width: 200px;">Item 1</view>
<view style="display: inline-block; width: 200px;">Item 2</view>
</scroll-view>⚠️ MUST: 横向滚动需开启 enable-flex 以兼容 WebView。性能优化建议
1. 指定 cache-extent
<!-- 预渲染视口外 500px 区域 -->
<scroll-view type="list" cache-extent="500">适当的 cache-extent 可优化滚动体验,但会增加内存占用。
2. 使用 list-item 属性
<scroll-view type="list" scroll-y>
<view wx:for="{{list}}" wx:key="id" list-item>
<!-- 列表项内容 -->
</view>
</scroll-view>list-item 属性可启用样式共享优化。
3. 避免单一子节点
list 模式下若只有一个直接子节点,按需渲染会退化:
<!-- ❌ 性能退化 -->
<scroll-view type="list">
<view>
<view wx:for="{{list}}">{{item}}</view>
</view>
</scroll-view>
<!-- ✅ 正确做法 -->
<scroll-view type="list">
<view wx:for="{{list}}">{{item}}</view>
</scroll-view>Bug & Tip
1. 不支持嵌套 textarea/map/canvas/video(基础库 2.4.0 以下) 2. scroll-into-view 优先级高于 scroll-top 3. 滚动 scroll-view 会阻止页面回弹,无法触发 onPullDownRefresh 4. 自定义下拉刷新节点需声明 slot="refresher" 5. 滚动条长度是预估的,子节点高度差异大时可能不准确
吸顶布局:sticky-section 与 sticky-header
概述
sticky-section 和 sticky-header 是 Skyline 新增的吸顶布局组件,用于实现分组列表的吸顶效果。它们必须在 <scroll-view type="custom"> 模式下使用。
组件说明
sticky-section
吸顶区段容器,包含一个 sticky-header 和列表内容。
sticky-header
吸顶头部组件,当滚动到该区段时会固定在顶部。
基本结构
<scroll-view type="custom" scroll-y>
<sticky-section>
<sticky-header>区段 1 标题</sticky-header>
<view>区段 1 内容</view>
</sticky-section>
<sticky-section>
<sticky-header>区段 2 标题</sticky-header>
<view>区段 2 内容</view>
</sticky-section>
</scroll-view>典型应用:通讯录
<scroll-view type="custom" scroll-y style="height: 100vh;">
<sticky-section wx:for="{{contacts}}" wx:key="letter">
<sticky-header class="letter-header">
{{item.letter}}
</sticky-header>
<list-view>
<view
wx:for="{{item.list}}"
wx:for-item="contact"
wx:key="id"
class="contact-item"
>
<image class="avatar" src="{{contact.avatar}}" />
<text>{{contact.name}}</text>
</view>
</list-view>
</sticky-section>
</scroll-view>Page({
data: {
contacts: [
{
letter: 'A',
list: [
{ id: 1, name: 'Alice', avatar: '...' },
{ id: 2, name: 'Amy', avatar: '...' }
]
},
{
letter: 'B',
list: [
{ id: 3, name: 'Bob', avatar: '...' },
{ id: 4, name: 'Bill', avatar: '...' }
]
}
// ...
]
}
}).letter-header {
height: 32px;
line-height: 32px;
padding: 0 16px;
background: #f5f5f5;
font-size: 14px;
color: #999;
font-weight: bold;
}
.contact-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #eee;
}
.avatar {
width: 44px;
height: 44px;
border-radius: 4px;
margin-right: 12px;
}商品分类列表
<scroll-view type="custom" scroll-y>
<sticky-section wx:for="{{categories}}" wx:key="id">
<sticky-header class="category-header">
<image class="category-icon" src="{{item.icon}}" />
<text>{{item.name}}</text>
</sticky-header>
<grid-view type="aligned" cross-axis-count="3" cross-axis-gap="8" main-axis-gap="8">
<view
wx:for="{{item.products}}"
wx:for-item="product"
wx:key="id"
class="product-card"
>
<image src="{{product.cover}}" mode="aspectFill" />
<text class="name">{{product.name}}</text>
<text class="price">¥{{product.price}}</text>
</view>
</grid-view>
</sticky-section>
</scroll-view>属性说明
sticky-section
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| push-pinned-header | boolean | true | 是否推动之前吸顶的头部 |
sticky-header
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| padding | Array | [0,0,0,0] | 内边距 |
使用规则
MUST(必须遵守)
1. 必须在 type="custom" 的 scroll-view 中使用 2. sticky-header 必须是 sticky-section 的第一个子元素 3. 每个 sticky-section 只能有一个 sticky-header
NEVER(禁止行为)
1. 不要在 type="list" 的 scroll-view 中使用 2. 不要将 sticky-header 放在 sticky-section 之外
配合 list-view 使用
<scroll-view type="custom" scroll-y>
<sticky-section>
<sticky-header>今日推荐</sticky-header>
<list-view padding="{{[12, 16, 12, 16]}}">
<view wx:for="{{todayList}}" wx:key="id" class="item">
{{item.title}}
</view>
</list-view>
</sticky-section>
<sticky-section>
<sticky-header>历史记录</sticky-header>
<list-view padding="{{[12, 16, 12, 16]}}">
<view wx:for="{{historyList}}" wx:key="id" class="item">
{{item.title}}
</view>
</list-view>
</sticky-section>
</scroll-view>与 WebView position: sticky 的区别
| 特性 | WebView sticky | Skyline sticky-header |
|---|---|---|
| 实现方式 | CSS 属性 | 组件 |
| 推动效果 | 需要 JS 处理 | 内置支持 |
| 性能 | 可能有闪烁 | 原生流畅 |
| 使用场景 | 任意元素 | 仅限 custom 模式 |
样式建议
吸顶头部样式
sticky-header {
/* 背景必须设置,否则会透出下层内容 */
background: #fff;
/* 可添加阴影增强层次感 */
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
/* 设置较高 z-index 确保在上层 */
z-index: 10;
}区段间距
sticky-section {
margin-bottom: 12px;
}
sticky-section:last-child {
margin-bottom: 0;
}完整示例:外卖菜单
<view class="container">
<!-- 左侧分类导航 -->
<scroll-view class="category-nav" scroll-y>
<view
wx:for="{{categories}}"
wx:key="id"
class="nav-item {{currentCategory === index ? 'active' : ''}}"
bindtap="scrollToCategory"
data-index="{{index}}"
>
{{item.name}}
</view>
</scroll-view>
<!-- 右侧商品列表 -->
<scroll-view
class="product-list"
type="custom"
scroll-y
scroll-into-view="{{scrollToId}}"
bindscroll="onScroll"
>
<sticky-section
wx:for="{{categories}}"
wx:key="id"
id="category-{{index}}"
>
<sticky-header class="category-title">
{{item.name}}
</sticky-header>
<list-view>
<view
wx:for="{{item.products}}"
wx:for-item="product"
wx:key="id"
class="product-item"
>
<image class="product-image" src="{{product.image}}" />
<view class="product-info">
<text class="product-name">{{product.name}}</text>
<text class="product-price">¥{{product.price}}</text>
</view>
</view>
</list-view>
</sticky-section>
</scroll-view>
</view>.container {
display: flex;
height: 100vh;
}
.category-nav {
width: 80px;
background: #f5f5f5;
}
.nav-item {
padding: 12px 8px;
text-align: center;
font-size: 12px;
}
.nav-item.active {
background: #fff;
color: #07c160;
}
.product-list {
flex: 1;
}
.category-title {
padding: 8px 12px;
background: #f9f9f9;
font-size: 14px;
font-weight: bold;
}
.product-item {
display: flex;
padding: 12px;
border-bottom: 1px solid #eee;
}
.product-image {
width: 80px;
height: 80px;
border-radius: 4px;
margin-right: 12px;
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.product-name {
font-size: 14px;
color: #333;
}
.product-price {
font-size: 16px;
color: #f5222d;
font-weight: bold;
}share-element 共享元素动画
概述
share-element 是用于实现页面间共享元素动画的组件,类似于 Flutter 的 Hero 动画。元素看起来像是在页面间"穿越",可以创造流畅的过渡体验。
使用场景
- 图片详情页转场
- 商品详情页转场
- 卡片展开效果
- 任意需要元素连续性动画的场景
基本原理
1. 在源页面放置 share-element,设置唯一的 key 2. 在目标页面放置相同 key 的 share-element 3. 页面跳转时,元素会从源位置动画过渡到目标位置
基本用法
源页面
<!-- pages/list/index.wxml -->
<view class="card" bindtap="goDetail" data-id="{{item.id}}">
<share-element key="image-{{item.id}}" transform>
<image src="{{item.cover}}" mode="aspectFill" />
</share-element>
<text>{{item.title}}</text>
</view>// pages/list/index.js
Page({
goDetail(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({
url: `/pages/detail/index?id=${id}`
})
}
})目标页面
<!-- pages/detail/index.wxml -->
<share-element key="image-{{detail.id}}" transform>
<image class="detail-image" src="{{detail.cover}}" mode="aspectFill" />
</share-element>
<view class="content">
<text class="title">{{detail.title}}</text>
<text class="desc">{{detail.desc}}</text>
</view>通用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| key | string | - | 必填,页面内唯一标识 |
| transform | boolean | false | 是否启用动画 |
| duration | number | 300 | 动画时长(ms) |
| easing-function | string | ease-out | CSS 缓动函数 |
Skyline 特有属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| transition-on-gesture | boolean | false | 手势返回时是否动画 |
| shuttle-on-push | string | to | push 阶段飞跃物 |
| shuttle-on-pop | string | to | pop 阶段飞跃物 |
| rect-tween-type | string | materialRectArc | 动画轨迹类型 |
| worklet:onframe | callback | - | 动画帧回调 |
shuttle-on-push / shuttle-on-pop
| 值 | 说明 |
|---|---|
| from | 使用源页面节点作为飞跃物 |
| to | 使用目标页面节点作为飞跃物 |
rect-tween-type 动画轨迹
| 值 | 效果 |
|---|---|
| materialRectArc | 矩形对角动画(默认) |
| materialRectCenterArc | 径向动画 |
| linear | 线性动画 |
| elasticIn | 弹性进入 |
| elasticOut | 弹性退出 |
| elasticInOut | 弹性进出 |
| bounceIn | 弹跳进入 |
| bounceOut | 弹跳退出 |
| bounceInOut | 弹跳进出 |
| cubic-bezier(x1,y1,x2,y2) | 自定义贝塞尔曲线 |
手势返回动画
开启 transition-on-gesture 后,用户侧滑返回时也会触发共享元素动画:
<share-element
key="hero-image"
transform
transition-on-gesture
>
<image src="{{imageUrl}}" />
</share-element>Worklet 动画帧回调
使用 worklet:onframe 可以在动画过程中同步其他动画:
<share-element
key="card"
transform
worklet:onframe="onShareElementFrame"
>
<view class="card">...</view>
</share-element>
<view class="backdrop" style="opacity: {{backdropOpacity}};"></view>Page({
data: {
backdropOpacity: 0
},
onShareElementFrame(e) {
'worklet'
// e.progress: 动画进度 0-1
// 可用于同步其他元素的动画
}
})配合 page-container 使用
在同一页面内使用 page-container 实现模态转场:
<view class="page">
<view
wx:for="{{items}}"
wx:key="id"
class="card"
bindtap="showDetail"
data-index="{{index}}"
>
<share-element key="item-{{item.id}}" transform="{{currentId === item.id}}">
<image src="{{item.cover}}" />
</share-element>
<text>{{item.title}}</text>
</view>
</view>
<page-container
show="{{showContainer}}"
overlay
duration="{{300}}"
bindbeforeenter="onBeforeEnter"
bindafterleave="onAfterLeave"
>
<view class="detail">
<share-element key="item-{{detail.id}}" transform>
<image class="detail-image" src="{{detail.cover}}" />
</share-element>
<view class="detail-content">
<text class="title">{{detail.title}}</text>
<text class="desc">{{detail.desc}}</text>
</view>
</view>
</page-container>Page({
data: {
items: [],
detail: null,
currentId: null,
showContainer: false
},
showDetail(e) {
const { index } = e.currentTarget.dataset
const item = this.data.items[index]
this.setData({
detail: item,
currentId: item.id,
showContainer: true
})
},
onBeforeEnter() {
// 容器进入前
},
onAfterLeave() {
this.setData({ currentId: null })
}
})完整示例:图片详情
列表页
<!-- pages/gallery/index.wxml -->
<view class="gallery">
<view
wx:for="{{images}}"
wx:key="id"
class="gallery-item"
bindtap="goDetail"
data-item="{{item}}"
>
<share-element key="gallery-{{item.id}}" transform>
<image
class="thumb"
src="{{item.thumb}}"
mode="aspectFill"
/>
</share-element>
</view>
</view>// pages/gallery/index.js
Page({
data: {
images: [
{ id: 1, thumb: '...', full: '...', title: '图片1' },
{ id: 2, thumb: '...', full: '...', title: '图片2' },
// ...
]
},
goDetail(e) {
const { item } = e.currentTarget.dataset
wx.navigateTo({
url: `/pages/gallery/detail?id=${item.id}`
})
}
})详情页
<!-- pages/gallery/detail.wxml -->
<view class="detail-page">
<share-element
key="gallery-{{image.id}}"
transform
transition-on-gesture
rect-tween-type="materialRectCenterArc"
>
<image
class="full-image"
src="{{image.full}}"
mode="widthFix"
bindtap="toggleInfo"
/>
</share-element>
<view class="info-panel {{showInfo ? 'show' : ''}}">
<text class="title">{{image.title}}</text>
<text class="desc">{{image.desc}}</text>
</view>
</view>/* pages/gallery/detail.wxss */
.detail-page {
position: relative;
min-height: 100vh;
background: #000;
}
.full-image {
width: 100%;
}
.info-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
background: rgba(0, 0, 0, 0.7);
transform: translateY(100%);
transition: transform 0.3s ease;
}
.info-panel.show {
transform: translateY(0);
}
.title {
display: block;
font-size: 18px;
color: #fff;
font-weight: bold;
}
.desc {
display: block;
margin-top: 8px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
}// pages/gallery/detail.js
Page({
data: {
image: null,
showInfo: false
},
onLoad(options) {
const { id } = options
// 获取图片详情
this.loadImage(id)
},
loadImage(id) {
// 模拟加载
const image = {
id: parseInt(id),
thumb: '...',
full: '...',
title: '风景照片',
desc: '拍摄于 2024 年春天'
}
this.setData({ image })
},
toggleInfo() {
this.setData({ showInfo: !this.data.showInfo })
}
})注意事项
1. key 必须匹配:源页面和目标页面的 share-element 必须有相同的 key 2. transform 属性:两边都需要设置 transform 才会触发动画 3. 内容变化:如果源和目标的内容不同,动画会在两者间过渡 4. 性能考虑:共享元素内容不宜过于复杂
Skyline vs WebView
| 特性 | Skyline | WebView |
|---|---|---|
| 基础动画 | ✅ | ✅ |
| 手势返回动画 | ✅ | ❌ |
| worklet 帧回调 | ✅ | ❌ |
| 飞跃物选择 | ✅ | ❌ |
| 动画轨迹类型 | 丰富 | 有限 |
示例代码片段
Skyline:{% minicode('t584gymu7VMM') %} WebView:{% minicode('NqVP7ImA73ou') %}
snapshot 截图组件
概述
snapshot 是 Skyline 新增的截图组件,可以将组件内的内容渲染为图片。适用于生成分享海报、保存页面截图等场景。
基本用法
<snapshot id="my-snapshot">
<view class="poster">
<image src="{{productImage}}" />
<text>{{productName}}</text>
<text>¥{{productPrice}}</text>
</view>
</snapshot>
<button bindtap="takeSnapshot">生成图片</button>Page({
takeSnapshot() {
this.createSelectorQuery()
.select('#my-snapshot')
.node()
.exec(res => {
const snapshotNode = res[0].node
snapshotNode.takeSnapshot({
type: 'arraybuffer',
format: 'png',
success: (res) => {
// res.data 是图片的 ArrayBuffer
const fs = wx.getFileSystemManager()
const filePath = `${wx.env.USER_DATA_PATH}/snapshot.png`
fs.writeFile({
filePath,
data: res.data,
encoding: 'binary',
success: () => {
// 保存到相册
wx.saveImageToPhotosAlbum({
filePath,
success: () => {
wx.showToast({ title: '保存成功' })
}
})
}
})
},
fail: (err) => {
console.error('截图失败', err)
}
})
})
}
})takeSnapshot 方法
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| type | string | file | 输出类型:file/arraybuffer |
| format | string | png | 图片格式:png/jpg |
| quality | number | 1.0 | jpg 质量(0-1) |
| success | function | - | 成功回调 |
| fail | function | - | 失败回调 |
返回值
| 属性 | 类型 | 说明 |
|---|---|---|
| data | string/ArrayBuffer | type=file 时为临时文件路径,type=arraybuffer 时为 ArrayBuffer |
| width | number | 图片宽度 |
| height | number | 图片高度 |
示例代码
生成分享海报
<view class="container">
<!-- 海报预览 -->
<snapshot id="poster" class="poster">
<view class="poster-content">
<!-- 商品图片 -->
<image class="product-image" src="{{product.image}}" mode="aspectFill" />
<!-- 商品信息 -->
<view class="product-info">
<text class="product-name">{{product.name}}</text>
<text class="product-desc" max-lines="2" overflow="ellipsis">
{{product.desc}}
</text>
<view class="price-row">
<text class="price">¥{{product.price}}</text>
<text class="original-price">¥{{product.originalPrice}}</text>
</view>
</view>
<!-- 底部信息 -->
<view class="poster-footer">
<image class="qrcode" src="{{qrcodeUrl}}" />
<view class="footer-text">
<text>长按识别小程序码</text>
<text>立即购买</text>
</view>
</view>
</view>
</snapshot>
<!-- 操作按钮 -->
<view class="actions">
<button class="save-btn" bindtap="saveToAlbum">保存到相册</button>
<button class="share-btn" open-type="share">分享给好友</button>
</view>
</view>.poster {
width: 300px;
margin: 20px auto;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.poster-content {
background: #fff;
}
.product-image {
width: 100%;
height: 300px;
}
.product-info {
padding: 16px;
}
.product-name {
display: block;
font-size: 16px;
font-weight: bold;
color: #333;
}
.product-desc {
display: block;
margin-top: 8px;
font-size: 12px;
color: #999;
line-height: 1.5;
}
.price-row {
display: flex;
align-items: baseline;
margin-top: 12px;
}
.price {
font-size: 20px;
font-weight: bold;
color: #f5222d;
}
.original-price {
margin-left: 8px;
font-size: 12px;
color: #999;
text-decoration: line-through;
}
.poster-footer {
display: flex;
align-items: center;
padding: 16px;
border-top: 1px solid #eee;
}
.qrcode {
width: 60px;
height: 60px;
}
.footer-text {
margin-left: 12px;
}
.footer-text text {
display: block;
font-size: 12px;
color: #666;
}
.actions {
display: flex;
padding: 20px;
}
.save-btn,
.share-btn {
flex: 1;
margin: 0 8px;
}Page({
data: {
product: {
image: 'https://example.com/product.jpg',
name: '精选商品名称',
desc: '这是商品的详细描述信息,可能会很长,需要截断显示...',
price: '99.00',
originalPrice: '199.00'
},
qrcodeUrl: '' // 小程序码
},
onLoad() {
this.generateQrcode()
},
generateQrcode() {
// 调用云函数生成小程序码
wx.cloud.callFunction({
name: 'getQrcode',
data: { page: 'pages/product/index', scene: this.data.product.id },
success: res => {
this.setData({ qrcodeUrl: res.result.url })
}
})
},
saveToAlbum() {
wx.showLoading({ title: '生成中...' })
this.createSelectorQuery()
.select('#poster')
.node()
.exec(res => {
const snapshotNode = res[0].node
snapshotNode.takeSnapshot({
type: 'arraybuffer',
format: 'png',
success: (result) => {
const fs = wx.getFileSystemManager()
const filePath = `${wx.env.USER_DATA_PATH}/poster_${Date.now()}.png`
fs.writeFile({
filePath,
data: result.data,
encoding: 'binary',
success: () => {
wx.saveImageToPhotosAlbum({
filePath,
success: () => {
wx.hideLoading()
wx.showToast({ title: '已保存到相册' })
},
fail: (err) => {
wx.hideLoading()
if (err.errMsg.includes('auth deny')) {
wx.showModal({
title: '提示',
content: '请授权保存图片到相册',
success: (res) => {
if (res.confirm) {
wx.openSetting()
}
}
})
}
}
})
}
})
},
fail: (err) => {
wx.hideLoading()
wx.showToast({ title: '生成失败', icon: 'error' })
console.error(err)
}
})
})
}
})隐藏截图区域
如果不想在页面上显示截图内容,可以将其移到屏幕外:
<view class="snapshot-container" style="position: fixed; left: -9999px;">
<snapshot id="hidden-snapshot">
<view class="content">
<!-- 截图内容 -->
</view>
</snapshot>
</view>注意事项
1. 仅限 Skyline:snapshot 组件仅在 Skyline 渲染模式下可用 2. 内容限制:截图内容必须在 snapshot 组件内部 3. 图片加载:确保组件内的图片已加载完成再截图 4. 性能考虑:大尺寸截图可能耗时较长,建议显示加载状态 5. 权限:保存到相册需要用户授权
与 canvas 截图对比
| 特性 | snapshot | canvas |
|---|---|---|
| 使用复杂度 | 简单(直接包裹内容) | 复杂(需要绑制代码) |
| 性能 | 较好 | 一般 |
| 内容支持 | 任意 WXML 内容 | 需要手动绘制 |
| 兼容性 | 仅 Skyline | 通用 |
| 定制能力 | 使用 WXSS 样式 | 完全自定义 |
最佳实践
1. 等待图片加载
Page({
data: {
imagesLoaded: 0,
totalImages: 3
},
onImageLoad() {
const loaded = this.data.imagesLoaded + 1
this.setData({ imagesLoaded: loaded })
if (loaded === this.data.totalImages) {
this.setData({ canSnapshot: true })
}
}
})2. 使用 jpg 减小文件大小
snapshotNode.takeSnapshot({
type: 'arraybuffer',
format: 'jpg',
quality: 0.8, // 80% 质量
success: (res) => {
// ...
}
})3. 处理授权被拒
wx.saveImageToPhotosAlbum({
filePath,
fail: (err) => {
if (err.errMsg.includes('auth deny')) {
wx.showModal({
title: '需要授权',
content: '保存图片需要您授权访问相册',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
wx.openSetting()
}
}
})
}
}
})app.json Skyline 配置详解
概述
小程序全局配置文件 app.json 中,以下配置项与 Skyline 渲染引擎直接相关。本文档仅列出 Skyline 相关配置,完整 app.json 配置请参阅官方文档。
renderer
基础库 2.30.4+
指定小程序全局的默认渲染后端。
| 可选值 | 说明 |
|---|---|
webview | 默认值,使用 WebView 渲染 |
skyline | 使用 Skyline 渲染引擎 |
{
"renderer": "skyline"
}componentFramework
基础库 2.30.4+
指定小程序使用的组件框架。Skyline 项目必须配置为 glass-easel。
| 可选值 | 说明 |
|---|---|
exparser | 默认值,传统组件框架 |
glass-easel | 新组件框架,Skyline 必需 |
{
"componentFramework": "glass-easel"
}📌 glass-easel 迁移详情请参阅:skyline-glass-easel
lazyCodeLoading
基础库 2.11.1+
配置自定义组件代码按需注入。目前仅支持值 requiredComponents。
{
"lazyCodeLoading": "requiredComponents"
}开启后,小程序仅在页面实际使用到某组件时才注入该组件代码,显著优化启动性能。
rendererOptions
基础库 2.31.1+
Skyline 渲染引擎的配置选项,嵌套在 rendererOptions.skyline 下。
完整配置项
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| defaultDisplayBlock | boolean | false | 开启默认 Block 布局。影响 display(flex→block)、flex-direction(column→row)、align-items(stretch→normal),对齐 WebView 行为 |
| defaultContentBox | boolean | false | 开启默认 ContentBox 盒模型。Skyline 默认 border-box,开启后对齐 WebView 的 content-box |
| tagNameStyleIsolation | string | "isolated" | 控制标签选择器的作用域。"isolated" 仅匹配当前组件,"legacy" 全局匹配(对齐 WebView) |
| enableScrollViewAutoSize | boolean | false | 开启 scroll-view 自动撑开。开启后 scroll-view 不需要显式设置高度 |
| disableABTest | boolean | false | 关闭 Skyline AB 实验。发布上线时建议开启,确保所有用户使用 Skyline |
defaultDisplayBlock 效果对比
/* 默认值 false:Skyline 默认 flex 布局 */
view {
display: flex;
flex-direction: column;
align-items: stretch;
}
/* 设为 true:对齐 WebView 行为 */
view {
display: block;
flex-direction: row; /* 虽然 display:block 时不生效,但默认值变化 */
align-items: normal; /* 虽然 display:block 时不生效,但默认值变化 */
}⚠️ 迁移项目强烈建议开启此选项,否则大量使用 display: block 假设的样式将失效。defaultContentBox 效果对比
/* 默认值 false:Skyline 默认 border-box */
view { box-sizing: border-box; }
/* 设为 true:对齐 WebView 行为 */
view { box-sizing: content-box; }tagNameStyleIsolation 说明
| 值 | 行为 | 适用场景 |
|---|---|---|
"isolated" | 标签选择器(如 view {})仅匹配当前组件内的节点 | 新项目,推荐 |
"legacy" | 标签选择器全局匹配,与 WebView 行为一致 | 迁移项目 |
disableABTest 说明
Skyline 通过 AB 实验逐步扩大用户覆盖范围。开启 disableABTest: true 后:
- 所有用户均使用 Skyline 渲染
- 不再参与灰度实验
- 发布上线时建议开启,确保行为一致性
{
"rendererOptions": {
"skyline": {
"disableABTest": true,
"defaultDisplayBlock": true,
"defaultContentBox": true,
"tagNameStyleIsolation": "legacy",
"enableScrollViewAutoSize": true
}
}
}window.navigationStyle
app.json 的 window 字段中,navigationStyle 控制导航栏样式。Skyline 下必须设为 "custom"。
{
"window": {
"navigationStyle": "custom"
}
}在 window 中设置的 navigationStyle 作为全局默认值,仍需在每个页面 json 中显式声明(确保明确性)。
convertRpxToVw
基础库 3.3.0+
配置是否将 rpx 单位转换为 vw 单位,开启后能修复某些 rpx 下的精度问题。
{
"convertRpxToVw": true
}页面级配置详解
概述
每个页面的 .json 文件可以对当前页面进行独立配置,覆盖 app.json 中的全局设置。本文档聚焦于 Skyline 相关的页面级配置项。
navigationStyle(必需)
| 值 | 说明 |
|---|---|
default | 默认样式,显示原生导航栏 |
custom | 自定义导航栏,只保留右上角胶囊按钮 |
Skyline 下必须设为 `"custom"`。Skyline 不支持原生导航栏渲染,未配置将导致编译错误:
getAppConfig error: the "navigationStyle" configuration for the page should be set to "custom"{
"navigationStyle": "custom"
}⚠️ 即使在app.json的window中已设置"navigationStyle": "custom",每个页面 json 仍建议显式声明,确保配置明确。
自定义导航栏实现
配置 "navigationStyle": "custom" 后,页面顶部没有默认导航栏,需要自行实现:
<!-- 自定义导航栏组件 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-bar__title">页面标题</view>
</view>
<scroll-view type="list" scroll-y style="height: 100vh;">
<!-- 页面内容 -->
</scroll-view>disableScroll
| 值 | 说明 |
|---|---|
false | 默认值,页面可整体滚动 |
true | 页面整体不能上下滚动 |
注意:此配置只在页面 json 中有效,无法在 app.json 中设置。
Skyline 不支持页面级全局滚动,建议配置 "disableScroll": true 并使用 scroll-view 组件管理滚动:
{
"navigationStyle": "custom",
"disableScroll": true
}backgroundColorContent
Skyline 特有属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| backgroundColorContent | HexColor | #RRGGBBAA | 页面容器背景色 |
用于设置页面容器的背景色,支持带透明度的颜色值。常用于自定义路由中设置页面透明背景:
{
"navigationStyle": "custom",
"backgroundColorContent": "#00000000"
}📌 透明页面背景详情请参阅:skyline-route - 自定义路由
renderer(页面级覆盖)
基础库 2.30.4+
页面级的 renderer 配置可覆盖 app.json 中的全局设置,实现混合渲染:
// 某个页面使用 Skyline(即使全局是 WebView)
{
"renderer": "skyline",
"navigationStyle": "custom",
"componentFramework": "glass-easel"
}// 某个页面回退到 WebView(即使全局是 Skyline)
{
"renderer": "webview"
}混合渲染策略
| 策略 | app.json renderer | 页面 json renderer | 适用场景 |
|---|---|---|---|
| 全局 Skyline | "skyline" | 不设置 | 新项目,全部页面用 Skyline |
| 全局 WebView + 部分 Skyline | 不设置/"webview" | "skyline" | 渐进式迁移 |
| 全局 Skyline + 部分 WebView | "skyline" | "webview" | 个别页面不兼容时回退 |
rendererOptions(页面级覆盖)
基础库 3.1.0+
页面级可以覆盖 app.json 中的 rendererOptions 配置:
{
"navigationStyle": "custom",
"rendererOptions": {
"skyline": {
"defaultDisplayBlock": false
}
}
}componentFramework(页面级覆盖)
基础库 2.30.4+
页面级可以覆盖组件框架配置。混合渲染中使用 Skyline 的页面必须同时配置:
{
"renderer": "skyline",
"componentFramework": "glass-easel",
"navigationStyle": "custom"
}标准页面 json 模板
基础页面
{
"navigationStyle": "custom",
"usingComponents": {
"nav-bar": "/components/nav-bar/index"
}
}可滚动页面
{
"navigationStyle": "custom",
"disableScroll": true,
"usingComponents": {
"nav-bar": "/components/nav-bar/index"
}
}透明背景页面(用于自定义路由)
{
"navigationStyle": "custom",
"backgroundColorContent": "#00000000",
"disableScroll": true
}配置模板
模板 1:新项目 app.json
适用于从零创建的 Skyline 项目,包含所有推荐配置。
{
"pages": [
"pages/index/index",
"pages/detail/detail"
],
"window": {
"navigationStyle": "custom",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f5f5"
},
"renderer": "skyline",
"componentFramework": "glass-easel",
"lazyCodeLoading": "requiredComponents",
"rendererOptions": {
"skyline": {
"defaultDisplayBlock": true,
"defaultContentBox": true,
"tagNameStyleIsolation": "legacy",
"enableScrollViewAutoSize": true,
"disableABTest": true
}
}
}模板 2:迁移项目 app.json
适用于从 WebView 迁移到 Skyline 的项目,包含所有兼容配置。
{
"pages": [
"pages/index/index",
"pages/list/list",
"pages/detail/detail"
],
"window": {
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f5f5"
},
"renderer": "skyline",
"componentFramework": "glass-easel",
"lazyCodeLoading": "requiredComponents",
"rendererOptions": {
"skyline": {
"defaultDisplayBlock": true,
"defaultContentBox": true,
"tagNameStyleIsolation": "legacy",
"enableScrollViewAutoSize": true,
"disableABTest": true
}
},
"sitemapLocation": "sitemap.json"
}迁移要点
defaultDisplayBlock: true— 对齐 WebView 的display: block默认行为defaultContentBox: true— 对齐 WebView 的box-sizing: content-box默认行为tagNameStyleIsolation: "legacy"— 标签选择器全局匹配,对齐 WebViewdisableABTest: true— 关闭灰度实验,确保所有用户体验一致
模板 3:混合渲染 app.json
适用于渐进式迁移,全局 WebView + 部分页面 Skyline。
{
"pages": [
"pages/index/index",
"pages/skyline-page/skyline-page",
"pages/webview-page/webview-page"
],
"window": {
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f5f5"
},
"lazyCodeLoading": "requiredComponents",
"sitemapLocation": "sitemap.json"
}对应的 Skyline 页面 json:
// pages/skyline-page/skyline-page.json
{
"renderer": "skyline",
"componentFramework": "glass-easel",
"navigationStyle": "custom",
"disableScroll": true,
"rendererOptions": {
"skyline": {
"defaultDisplayBlock": true,
"defaultContentBox": true
}
}
}对应的 WebView 页面 json(无需特殊配置):
// pages/webview-page/webview-page.json
{
"navigationBarTitleText": "WebView 页面"
}模板 4:标准页面 json
基础页面
{
"navigationStyle": "custom",
"usingComponents": {
"nav-bar": "/components/nav-bar/index"
}
}可滚动页面
{
"navigationStyle": "custom",
"disableScroll": true,
"usingComponents": {
"nav-bar": "/components/nav-bar/index"
}
}透明背景页面(自定义路由)
{
"navigationStyle": "custom",
"backgroundColorContent": "#00000000",
"disableScroll": true
}配置检查清单
新项目检查清单
| # | 检查项 | 文件 | 状态 |
|---|---|---|---|
| 1 | "renderer": "skyline" | app.json | □ |
| 2 | "componentFramework": "glass-easel" | app.json | □ |
| 3 | "lazyCodeLoading": "requiredComponents" | app.json | □ |
| 4 | rendererOptions.skyline 已配置 | app.json | □ |
| 5 | "disableABTest": true | app.json rendererOptions | □ |
| 6 | 所有页面 json 包含 "navigationStyle": "custom" | 页面 json | □ |
| 7 | 可滚动页面配置 "disableScroll": true | 页面 json | □ |
| 8 | "skylineRenderEnable": true | project.config.json | □ |
迁移项目额外检查
| # | 检查项 | 说明 |
|---|---|---|
| 1 | defaultDisplayBlock: true | 避免 display 从 block 变为 flex、flex-direction 从 row 变为 column、align-items 从 normal 变为 stretch |
| 2 | defaultContentBox: true | 避免盒模型从 content-box 变为 border-box |
| 3 | tagNameStyleIsolation: "legacy" | 避免标签选择器作用域变化 |
| 4 | 所有页面已添加自定义导航栏 | 替代原生导航栏 |
| 5 | 页面滚动已改用 scroll-view | 替代页面级滚动 |
project.config.json 配置
概述
project.config.json 是微信开发者工具的项目配置文件,其中包含影响 Skyline 调试和开发的设置项。
配置文件优先级
| 文件 | 说明 | 优先级 |
|---|---|---|
project.config.json | 公共配置,提交版本管理 | 低 |
project.private.config.json | 个人配置,加入 .gitignore | 高 |
project.private.config.json 中的相同设置优先级高于 project.config.json。
Skyline 相关设置
setting.skylineRenderEnable
| 字段 | 类型 | 允许私有设置 | 说明 |
|---|---|---|---|
| skylineRenderEnable | Boolean | 是 | 是否开启 Skyline 渲染调试 |
在开发者工具中启用 Skyline 渲染调试:
{
"setting": {
"skylineRenderEnable": true
}
}⚠️ 此设置仅影响开发者工具的调试行为,不影响线上表现。线上渲染器由app.json的renderer字段决定。
其他相关设置
以下设置虽非 Skyline 专属,但在 Skyline 项目中常用:
| 字段 | 类型 | 说明 |
|---|---|---|
es6 | Boolean | 是否启用 ES6 转 ES5 |
postcss | Boolean | 上传代码时样式是否自动补全 |
minified | Boolean | 上传代码时是否自动压缩脚本 |
compileHotReLoad | Boolean | 是否开启文件保存后自动热重载 |
bigPackageSizeSupport | Boolean | 主包/分包体积上限调整为 4M |
完整示例
{
"appid": "wx1234567890",
"compileType": "miniprogram",
"setting": {
"skylineRenderEnable": true,
"es6": true,
"postcss": true,
"minified": true,
"compileHotReLoad": true
},
"libVersion": "3.3.0"
}libVersion
基础库版本设置。Skyline 项目建议设置为 2.30.4 以上版本:
| Skyline 特性 | 最低基础库 |
|---|---|
| renderer + componentFramework | 2.30.4 |
| rendererOptions | 2.31.1 |
| 页面级 rendererOptions 覆盖 | 3.1.0 |
| convertRpxToVw | 3.3.0 |
wx.getSkylineInfo / wx.getSkylineInfoSync
获取当前运行环境对于 Skyline 渲染引擎的支持情况。
基础信息
| 项目 | 说明 |
|---|---|
| 基础库版本 | 2.26.2+ |
| 小程序插件 | 支持 |
| 鸿蒙 OS | 支持 |
wx.getSkylineInfo (异步)
参数
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| success | function | 否 | 成功回调 |
| fail | function | 否 | 失败回调 |
| complete | function | 否 | 完成回调 |
返回值 (res)
| 属性 | 类型 | 说明 |
|---|---|---|
| isSupported | boolean | 是否支持 Skyline |
| version | string | Skyline 版本号,如 0.9.7 |
| reason | string | 不支持的原因(仅当 isSupported 为 false) |
reason 可能值
| 值 | 说明 | 解决方案 |
|---|---|---|
client not supported | 微信客户端不支持 | 升级微信客户端 |
baselib not supported | 基础库不支持 | 升级微信客户端(基础库自动更新) |
a-b test not enabled | 未命中 AB 实验 | 配置 We 分析 AB 实验 |
SwitchRender option set to webview | 强切为 WebView | 切换回 Auto 或 Skyline |
示例
wx.getSkylineInfo({
success(res) {
console.log('Skyline 支持:', res.isSupported)
console.log('Skyline 版本:', res.version)
if (!res.isSupported) {
console.log('不支持原因:', res.reason)
}
},
fail(err) {
console.error('获取 Skyline 信息失败:', err)
}
})wx.getSkylineInfoSync (同步)
返回值
与异步版本的 res 相同。
示例
const info = wx.getSkylineInfoSync()
console.log('Skyline 支持:', info.isSupported)
console.log('Skyline 版本:', info.version)
if (!info.isSupported) {
console.log('不支持原因:', info.reason)
}使用场景
判断是否启用 Skyline 特性
Page({
onLoad() {
const info = wx.getSkylineInfoSync()
if (info.isSupported) {
// 使用 Skyline 专属特性
this.initWorkletAnimation()
} else {
// 降级方案
this.initFallbackAnimation()
}
}
})日志上报
App({
onLaunch() {
const info = wx.getSkylineInfoSync()
// 上报 Skyline 使用情况
wx.reportAnalytics('skyline_status', {
supported: info.isSupported,
version: info.version || 'N/A',
reason: info.reason || 'N/A'
})
}
})调试信息展示
Page({
data: {
debugInfo: ''
},
onLoad() {
const info = wx.getSkylineInfoSync()
this.setData({
debugInfo: `Skyline: ${info.isSupported ? '✓' : '✗'} v${info.version || 'N/A'}`
})
}
})与 this.renderer 的区别
| 对比项 | wx.getSkylineInfo | this.renderer |
|---|---|---|
| 调用时机 | 任何时候 | 页面/组件实例化后 |
| 返回信息 | 详细信息(版本、原因) | 仅当前渲染器类型 |
| 用途 | 全局能力检测 | 页面级别判断 |
Page({
onLoad() {
// API 方式 - 获取全局支持情况
const info = wx.getSkylineInfoSync()
console.log('全局支持:', info.isSupported)
// 实例属性 - 获取当前页面实际使用的渲染器
console.log('当前页面:', this.renderer) // 'skyline' 或 'webview'
}
})注意事项
1. 版本要求:需要基础库 2.26.2+ 2. 不支持 Promise:异步版本不支持 Promise 风格调用 3. AB 实验影响:即使 isSupported 为 true,实际渲染器仍受 AB 实验控制
wx.preloadSkylineView
预加载下个页面所需要的 Skyline 运行环境。
基础信息
| 项目 | 说明 |
|---|---|
| 基础库版本 | 2.24.7+ |
| 小程序插件 | 支持 |
| Promise 风格 | 不支持 |
功能说明
微信客户端默认预加载 WebView 环境(因为大多数小程序使用 WebView),不会自动预加载 Skyline 环境。
调用此接口可以提前预加载 Skyline 运行环境,使后续跳转到 Skyline 页面时更快。
参数
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| success | function | 否 | 成功回调 |
| fail | function | 否 | 失败回调 |
| complete | function | 否 | 完成回调 |
基础示例
wx.preloadSkylineView({
success() {
console.log('Skyline 环境预加载成功')
},
fail(err) {
console.error('Skyline 环境预加载失败:', err)
}
})最佳实践
1. 在 onShow 中延迟调用
Page({
onShow() {
// 延迟调用,避免阻塞当前页面渲染
setTimeout(() => {
wx.preloadSkylineView()
}, 500)
}
})说明:在 onShow 而非 onLoad 中调用,确保页面返回时也能重新预加载。
2. 在可能跳转的页面调用
// pages/list/list.js
// 列表页,用户可能点击进入 Skyline 渲染的详情页
Page({
onShow() {
// 预加载 Skyline 环境
setTimeout(() => {
wx.preloadSkylineView()
}, 300)
},
onItemTap(e) {
const { id } = e.currentTarget.dataset
// 跳转到 Skyline 详情页
wx.navigateTo({
url: `/pages/detail/detail?id=${id}`
})
}
})3. 条件预加载
Page({
onShow() {
// 只在支持 Skyline 的环境预加载
const info = wx.getSkylineInfoSync()
if (info.isSupported) {
setTimeout(() => {
wx.preloadSkylineView()
}, 500)
}
}
})4. 结合路由判断
// app.js
App({
onLaunch() {
// 记录即将访问的页面
this.nextPage = null
},
preloadIfNeeded(nextPage) {
// 根据目标页面决定是否预加载
const skylinePages = [
'pages/detail/detail',
'pages/animation/animation'
]
if (skylinePages.includes(nextPage)) {
wx.preloadSkylineView()
}
}
})调用时机建议
| 场景 | 推荐时机 | 延迟时间 |
|---|---|---|
| 首页加载 | onShow | 500ms |
| 列表页 | onShow | 300ms |
| 详情页返回后 | onShow | 200ms |
| 用户操作后 | 操作回调中 | 立即 |
性能影响
正面影响
- 首次跳转 Skyline 页面时间减少
- 减少白屏时间
- 提升用户体验
注意事项
- 预加载会占用一定内存
- 频繁调用不会有额外收益(环境已加载)
- 建议配合条件判断,避免不必要的预加载
配合其他优化
1. 配合资源预加载
Page({
onShow() {
// 预加载 Skyline 环境
wx.preloadSkylineView()
// 预加载图片资源
wx.getImageInfo({
src: 'https://example.com/hero.jpg'
})
}
})2. 配合数据预取
Page({
onShow() {
// 预加载 Skyline 环境
wx.preloadSkylineView()
// 预取下一页数据
this.prefetchDetailData()
},
prefetchDetailData() {
// 提前获取详情页数据
}
})与预加载分包配合
// 先预加载分包,再预加载 Skyline
wx.preloadSubPackage({
package: 'packageA',
success() {
// 分包加载完成后预加载 Skyline
wx.preloadSkylineView()
}
})注意事项
1. 调用时机:避免在关键渲染路径上同步调用 2. 延迟执行:使用 setTimeout 延迟,避免影响当前页面 3. 条件判断:只在需要跳转 Skyline 页面时预加载 4. 重复调用:多次调用不会报错,但无额外收益
Skyline 更新日志
Skyline 渲染引擎的版本号可通过 wx.getSkylineInfo() 获取。
1.4.15 (2026-01-09)
新增
- image 组件的 preload 属性,用于图片预加载
优化
- 当页面被遮挡时自动停止动画,减少资源消耗
修复
- iOS 上某个导致崩溃的问题
- 鸿蒙平台无障碍功能导致的闪退
- fixed 定位元素移动后未重新计算层级的问题
- box-shadow 在 CSS 动画中不生效及导致的闪退问题
- gif/apng 动图帧率错误的问题
- swiper 组件在某些情况下未触发 change 事件的问题
- 在 font 和 animation 简写属性中无法使用 CSS 变量的问题
- 安卓平台无障碍功能导致的闪退
- 图片预加载配置项不生效的问题
- scroll-view 组件在首次加载时出现跳动的问题
1.4.14 (2025-12-18)
新增
- text 组件支持行内显示
- image 组件支持 loadstart 事件
优化
- scroll-view 的滚动锚定行为,提升内容变化时的稳定性
- HTTP 客户端,移除并发限制以提升网络请求性能
修复
- image 组件在特定模式下图片不显示的问题
- 组件事件回调中潜在的崩溃问题
- iOS 上 JavaScript 回调导致的崩溃
- 文本节点更新时可能引发的死锁问题
- 键盘高度变化事件输出错误值的问题
- IntersectionObserver 在未设置 thresholds 时无法触发回调的问题
- 移除子组件操作时可能发生的崩溃
- 布局节点访问父节点时可能导致的崩溃
- 无障碍功能在鸿蒙系统上的崩溃问题
- swiper 组件应始终触发 change 事件
- scroll-view 滑动时首次加载内容跳动的问题
1.4.13 (2025-11-25)
新增
- 支持 CSS :host 选择器
优化
- scroll-view 滚动性能,提升渲染效率
修复
- gap 属性与 CSS 变量结合时失效的问题
- flex 布局中使用 gap 导致元素展示不全的问题
- snapshot 组件 pointer-events 属性不生效的问题
- 图片在尺寸为零时渲染错误的问题
- em 单位计算中基准 fontSize 错误的问题
- Intersection Observer 在某些情况下崩溃的问题
- svg 图片加载时可能出现的死循环问题
- 使用 mask-image 展示 svg 图片时出现灰色边框的问题
- open-container 组件纵向测量不准确的问题
- 图片闪烁问题
- 动图设置目标尺寸后显示异常的问题
- 伪元素节点连接问题导致交互失效
1.4.12 (2025-10-31)
新增
- layout paragraph 支持无障碍功能
优化
- 文本空白字符处理流程
修复
- grid-view 布局异常
- IntersectionObserver attached 时机失效
- scroll-view 下拉刷新动画异常
- iOS 平台原生视图异常消失
- 横向手势返回操作异常
- IntersectionObserver target 节点异常时的崩溃
1.4.11 (2025-09-15)
修复
- 文本末行换行符溢出仍出省略号
- input 取消 composite 后草稿字符丢失
- scroll-view scroll-anchoring 偶现意外跳动
- swiper 开启循环显示后 animateTo 动画错误
- scroll-view 自动撑高问题
- 自定义路由 barrierDismissible 两次返回
- 伪元素节点消失仍在播放 css animation
- 文本中 span 意外换行
- scroll-view 不足一屏时不触发 lower/upper 事件
- input/textarea maxlength 输入 emoji 闪退
1.4.10 (2025-08-28)
新增
- text 增加 trailing-spaces 属性支持多行文本末行末尾预留空间
- IntersectionObserver 支持多次监听
- open-container 支持通过接口方式触发打开
优化
- css animation 在节点不可见时停止动画
- scroll-view scroll-anchoring 支持度优化
- open-container 手势返回时支持上下拖动页面
修复
- swiper 开启自动播放后,隐藏 & 显示会失效
- image gif 动画修改 src 后动画速度异常
- word-break: break-all 需要断开数字、英文、符号
- grid-view 增加子节点后白屏
- swiper 更新高度后动画失效
- scroll-view 嵌套 swiper 时可能导致切换无动画
- 图片渲染变形
- open-container 手势返回动画消失
- 键盘上推后无法恢复
1.4.9 (2025-08-06)
新增
- large-image 支持大图渲染
优化
- css animation 无限循环动画自动开启 repaint boundary 避免大面积重绘
- text 绘制性能
- image 对 svg 格式的判断
- 渲染树结构优化
修复
- span 丢失问题
- sticky-header 动态增加内容崩溃
- gif 动画消失
- swiper animation 被打断时 bind:change 和 bind:animationfinished 没有回调
- grid-view 删除并交换元素后布局错位
- css 文本 baseline shortcut 问题
- line-height 无法更新回 normal 值
- swiper current 更新问题
- picker-view indicator-style 闪退
- iOS input 失焦的同时无法 focus
- 某些白屏及 crash 问题
1.4.6 (2025-04-08)
新增
- HarmonyOS 支持
优化
- 自定义字体隔离
- 开发者工具内核升级
修复
- 两个 sticky-section 滚动速度不一致
- 调整文字选区的默认背景色
- 若干 IntersectionListener 相关接口表现异常及 crash
- picker-view 设置非法值出现滚动
- swiper animationfinish 返回 current 参数不正确
- input 字体样式错误
- picker-view-column 无子项时崩溃
- 循环动画时无法触发 scroll-into-view
- swiper 更新高度后动画失效
1.4.1 (2024-10-16)
新增
- flex 布局支持 gap
- worklet 中 scroll-view scrollTo 支持传递 velocity 参数
优化
- jsbinding 调用耗时
- swiper 内嵌 scroll-view 滚动切换体验问题
修复
- css transition delay 动画闪烁问题
- swiper 设置 next margin/snap-to-edge 后隐藏再显示时会消失
- swiper 开启自动播放后隐藏再显示会失效
- picker-view 样式设置失败
- 键盘上推无法恢复
- 若干其他问题
1.4.0 (2024-09-06)
新增
- sticky-header 支持吸顶与否的状态回调
- scroll-view 支持 scroll-anchoring
- list-view/*-builder 支持设置 background-color
- swiper 支持 snap-to-edge
优化
- 图片布局尺寸变化时使用布局尺寸渲染
- 弱网下图片加载优化
- 内存释放优化
- 布局节点内存大小、缓存性能优化
- 布局精度
1.3.0 (2024-04-19)
新增
- 支持一般兄弟节点选择器(a ~ b {})
- 支持紧邻兄弟节点选择器(a + b {})
- 支持 css :not() 伪类
- 支持 css :only-child() 伪类
- 支持 css :empty() 伪类
- 支持 css inline-flex 布局
- 开发者工具支持 DarkMode 调试
优化
- position 布局增加 cache
- wxss 解析耗时
- transform paint 耗时
- transition/animation 事件派发机制
- 字体模块预热
- 内存占用
1.2.0 (2024-01-08)
新增
- 开发者工具支持真机调试
- CSS 支持 flex order
- CSS 支持 will-change: contents
- 支持全局跨页面组件
- 支持 apng 动图
- scroll-view 组件支持 builder 模式
- picker-view 组件支持 indicator-style 属性
- input 键盘动画提供 worklet 回调
- textarea 组件支持 linechange 事件
- worklet 增加 ref 机制
- worklet 支持 scrollTo 接口
1.1.0 (2023-11-06)
新增
- CSS 支持 position fixed
- span/text 组件里的布局节点支持 display inline-block
- draggable-sheet 滚动容器组件
- swiper 组件支持新的交互动画类型
- scroll-view 组件支持 type="nested"
- input 组件支持 cursor-color 属性
- input 组件支持 composition 事件
- input 组件支持 selectionchange 事件
- 自定义路由增加 fullscreenDrag 配置项
- 支持页面级别配置 rendererOptions
1.0.0 (2023-05-11)
新增
- CSS 支持 calc 函数
- CSS 支持伪元素 before 和 after
- CSS 支持 var 函数
- CSS 支持 mask-image 属性
- 支持 picker-view 组件
- scroll-view 组件支持 clip 属性
- scroll-view/grid-view/list-view/sticky-header/sticky-section 组件支持 padding 属性
- scroll-view 组件直接子节点支持 CSS margin
- scroll-view 组件支持 min-drag-distance 属性
- text/span 组件支持内联 view 等普通节点
- 支持新版本组件框架 glass-easel
---
更多历史版本请查看官方文档。
Skyline 组件支持情况
通用特性支持
| 特性 | 支持情况 |
|---|---|
| 无障碍访问 | 支持 aria-role / label / hidden / disabled |
| DarkMode | 支持 |
| 原生组件同层渲染 | 均支持 |
| WeUI v2 | 支持 |
组件支持总表
完全支持的组件
| 组件 | 备注 |
|---|---|
| view / cover-view | 涉及文本需用 text 组件 |
| button | - |
| scroll-view | 需显式指定 type="list",支持大量新特性 |
| swiper / swiper-item | 增强大量特性 |
| input / textarea | 光标选区、菜单略有不同 |
| navigator | 只能嵌套 text 组件或文本节点 |
| map | 开发者工具暂不支持,使用真机预览 |
| canvas | 开发者工具暂不支持,使用真机预览 |
| radio / radio-group | - |
| label | - |
| checkbox / checkbox-group | - |
| picker | - |
| camera | 开发者工具暂不支持,使用真机预览 |
| root-portal | - |
| form | - |
| ad | - |
| official-account | - |
| live-player / live-pusher | - |
| voip-room | - |
| icon | - |
| slider | - |
| switch | - |
| share-element | 与 WebView 使用方式有异,特性有所增强 |
| page-container | - |
基本支持的组件
| 组件 | 支持情况 | 差异说明 |
|---|---|---|
| text | 基本支持 | 内联文本只能用 text 组件;可通过 span 组件与 text/image 内联 |
| image / cover-image | 基本支持 | SVG 支持已完善;部分低频 mode 未支持 |
| video | 基本支持 | 全屏已支持,投屏暂未支持 |
| picker-view | 基本支持 | indicator-class/mask-style 属性暂未支持 |
| rich-text | 完全支持 | 渲染结果可能略有不同;mode=web 时完全对齐 webview |
| page-meta | 基本支持 | 与全局滚动相关的属性不支持 |
暂不支持的组件
| 组件 | 状态 | 替代方案 |
|---|---|---|
| web-view | 暂不考虑 | 该页面配置 "renderer": "webview" |
| movable-area / movable-view | 暂不考虑 | 手势 + worklet 动画方案 |
| editor | 暂不考虑 | - |
| progress | 暂不考虑 | - |
| match-media | 待考虑 | - |
| keyboard-accessary | 待考虑 | input 的 worklet:onkeyboardheightchange 回调 |
| navigation-bar | 不考虑 | Skyline 只能用自定义导航 |
| xr-frame | 暂未支持 | - |
Skyline 新增组件
布局组件
| 组件 | 说明 |
|---|---|
| span | 支持内联文本和 image/navigator 的混排 |
| sticky-header | 吸顶布局容器 |
| sticky-section | 吸顶布局区域 |
| list-view | 列表布局容器,作为 scroll-view type="list" 的直接子节点 |
| grid-view | 网格布局 / 瀑布流布局容器 |
| nested-scroll-header | 嵌套滚动头部 |
| nested-scroll-body | 嵌套滚动主体 |
| draggable-sheet | 半屏可拖拽组件 |
截图组件
| 组件 | 说明 |
|---|---|
| snapshot | 截图组件,可将 WXML 内容导出为图片 |
手势组件
| 组件 | 触发条件 |
|---|---|
| tap-gesture-handler | 点击 |
| double-tap-gesture-handler | 双击 |
| long-press-gesture-handler | 长按 |
| pan-gesture-handler | 拖动(横向/纵向) |
| scale-gesture-handler | 多指缩放 |
| horizontal-drag-gesture-handler | 横向滑动 |
| vertical-drag-gesture-handler | 纵向滑动 |
| force-press-gesture-handler | iPhone 重按 |
组件使用注意事项
text 组件
<!-- ✅ 正确:纯文本用 text 包裹 -->
<view>
<text>这是文本内容</text>
</view>
<!-- ❌ 错误:直接放文本 -->
<view>这是文本内容</view>
<!-- ✅ 文本省略:必须用 text 组件 -->
<text style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
很长的文本...
</text>
<!-- ✅ 多行省略:使用 max-lines -->
<text max-lines="2" style="overflow: hidden;">
很长的多行文本...
</text>scroll-view 组件
<!-- ✅ 正确:指定 type="list" -->
<scroll-view type="list" scroll-y style="height: 100%;">
<view wx:for="{{list}}" wx:key="id">{{item}}</view>
</scroll-view>
<!-- 横向滚动需配合 flex 布局 -->
<scroll-view type="list" scroll-x enable-flex style="display: flex;">
<view wx:for="{{list}}" wx:key="id" style="flex-shrink: 0;">{{item}}</view>
</scroll-view>span 组件(内联混排)
<!-- 文本与图片内联 -->
<span>
<text>前置文本</text>
<image src="/images/icon.png" style="width: 20px; height: 20px;" />
<text>后置文本</text>
</span>navigator 组件
<!-- ✅ 正确:只嵌套 text -->
<navigator url="/pages/detail/detail">
<text>点击跳转</text>
</navigator>
<!-- ❌ 错误:嵌套其他组件 -->
<navigator url="/pages/detail/detail">
<view>不能这样用</view>
</navigator>原生组件调试
以下组件在开发者工具暂不支持调试,请使用真机预览:
- map
- canvas
- video
- camera
// 判断是否开发者工具环境
const systemInfo = wx.getSystemInfoSync()
if (systemInfo.platform === 'devtools') {
console.log('原生组件请使用真机预览')
}ScrollViewContext
基础库 3.3.0+
概述
worklet.scrollViewContext 提供在 worklet 函数内操作 scroll-view 组件的能力。通过 NodesRef.ref 获取 scroll-view 的引用,即可在 UI 线程中直接控制滚动。
scrollViewContext.scrollTo(object)
滚动至指定位置。
签名:worklet.scrollViewContext.scrollTo(ref, Object object)
参数
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
| top | number | - | 否 | 顶部距离 |
| left | number | - | 否 | 左边界距离 |
| duration | number | - | 否 | 滚动动画时长(毫秒) |
| animated | boolean | - | 否 | 是否启用滚动动画 |
| easingFunction | string | - | 否 | 动画曲线 |
用法示例
const { shared, scrollViewContext } = wx.worklet
Page({
onLoad() {
this.scrollRef = shared()
this.createSelectorQuery()
.select('.scrollable')
.ref((res) => {
this.scrollRef.value = res.ref
})
.exec()
},
onTap() {
'worklet'
scrollViewContext.scrollTo(this.scrollRef.value, {
top: 200,
duration: 2000,
animated: true,
easingFunction: 'ease'
})
}
})要点
- 需要先通过
createSelectorQuery().select().ref()获取 scroll-view 的引用 - 引用存储在 SharedValue 中,以便在 worklet 函数内访问
- 可配合手势事件在 UI 线程中实现自定义滚动控制
Related skills
FAQ
Does scroll-view need a type in Skyline?
Yes, Skyline requires setting the type attribute (list, custom, or nested) on every scroll-view.
Which components are unavailable in Skyline?
web-view, editor, and movable-view must never be used under Skyline.