完整代码实现
{"usingComponents": {}}
<view class="nav"style="height:{{statusBarHeight + 44}}px;padding-top:{{statusBarHeight}}px"><view class="nav-title">商品列表</view></view><scroll-viewclass="scroll"style="padding-top:{{statusBarHeight + 44}}px"scroll-yscroll-top="{{scrollValue}}"refresher-enabledrefresher-triggered="{{refreshing}}"bindrefresherrefresh="onRefresh"bindscrolltolower="loadMore"lower-threshold="150"worklet:onscrollupdate="onScrollUpdate"><block wx:for="{{goodsList}}" wx:key="id"><view class="goods-item" bindtap="tapGoods" data-id="{{item.id}}"><image class="goods-img" src="{{item.img}}" mode="aspectFill"></image><view class="goods-info"><view class="goods-name">{{item.title}}</view><view class="goods-price">¥{{item.price}}</view></view></view></block><view wx:if="{{isLoading && !refreshing}}" class="loading-wrap"><view class="dot dot1"></view><view class="dot dot2"></view><view class="dot dot3"></view></view><view wx:if="{{noMore}}" class="nomore">没有更多了</view></scroll-view><view class="back-top" bindtap="tapBackTop"><view class="back-top-icon">↑</view></view>
page {background: #f5f5f5;}.nav {position: fixed;top: 0;left: 0;width: 100%;z-index: 99;display: flex;align-items: center;justify-content: center;background: #ffffff;box-sizing: border-box;}.nav-title {font-size: 32rpx;font-weight: 500;color: #333;}.scroll {width: 100%;height: 100vh;padding-left: 20rpx;padding-right: 20rpx;padding-bottom: env(safe-area-inset-bottom);box-sizing: border-box;}.goods-item {background: #fff;border-radius: 16rpx;margin-bottom: 20rpx;display: flex;padding: 20rpx;}.goods-img {width: 160rpx;height: 160rpx;border-radius: 12rpx;flex-shrink: 0;}.goods-info {flex: 1;margin-left: 20rpx;display: flex;flex-direction: column;justify-content: space-between;}.goods-name {font-size: 28rpx;color: #333;}.goods-price {color: #f56c6c;font-size: 30rpx;font-weight: bold;}.loading-wrap {height: 80rpx;display: flex;align-items: center;justify-content: center;gap: 10rpx;}.dot {width: 12rpx;height: 12rpx;border-radius: 50%;background: #999;animation: jump 0.6s infinite ease-in-out;}.dot1 { animation-delay: 0s; }.dot2 { animation-delay: 0.2s; }.dot3 { animation-delay: 0.4s; }@keyframes jump {0%, 100% { transform: translateY(0); }50% { transform: translateY(-12rpx); }}.nomore {text-align: center;padding: 30rpx 0;color: #999;font-size: 26rpx;}.back-top {position: fixed;right: 30rpx;bottom: calc(140rpx + env(safe-area-inset-bottom));width: 80rpx;height: 80rpx;border-radius: 50%;background: #fff;box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.15);display: flex;align-items: center;justify-content: center;z-index: 100;opacity: 0;}.back-top-icon {font-size: 40rpx;color: #333;font-weight: bold;}
const { shared, runOnJS } = wx.workletPage({data: {goodsList: [],refreshing: false,loading: false,noMore: false,page: 1,statusBarHeight: 0},onLoad() {const info = wx.getSystemInfoSync()this.setData({statusBarHeight: info.statusBarHeight})// 返回顶部按钮透明度共享变量,渲染线程驱动this.backTopOpacity = shared(0)this.applyAnimatedStyle('.back-top', () => {'worklet'return {opacity: this.backTopOpacity.value}})},onReady() {this.loadData()},logScroll(scrollTop, opacity) {console.log('scrollTop=', scrollTop, 'opacity=', opacity)},onScrollUpdate(evt) {'worklet'const scrollTop = evt.detail.scrollTopconst showProgress = Math.min(Math.max((scrollTop - 500) / 100, 0), 1)this.backTopOpacity.value = showProgressrunOnJS(this.logScroll.bind(this))(scrollTop, showProgress)},// 下拉刷新onRefresh() {this.setData({page: 1,noMore: false,refreshing: true,goodsList: []}, () => {this.loadData()})},// 上拉加载loadMore() {if (this.data.loading || this.data.noMore) returnthis.loadData()},loadData() {this.setData({ loading: true })setTimeout(() => {const page = this.data.pageconst mock = []for (let i = 0; i < 10; i++) {mock.push({id: `${page}_${i}`,title: 'Skyline商品测试',price: (Math.random() * 100).toFixed(2),img: 'https://picsum.photos/id/237/200/200'})}const list = [...this.data.goodsList, ...mock]const noMore = page >= 5this.setData({goodsList: list,page: page + 1,loading: false,refreshing: false,noMore})}, 600)},// 返回顶部(稳定版本,type=list虚拟列表,瞬时回顶)tapBackTop() {const query = this.createSelectorQuery()query.select('.scroll').node(res => {if (res?.node) {res.node.scrollTo({top: 0})}}).exec()},goDetail(e) {const id = e.currentTarget.dataset.idwx.showToast({ title: '商品id:' + id })}})
核心要点
type=list:Skyline 原生虚拟列表,自动回收节点,长列表性能更强
worklet:onscrollupdate 在渲染线程监听滚动,不会阻塞主线程
shared + applyAnimatedStyle 驱动返回顶部按钮淡入淡出动画
踩坑小结
worklet 函数第一行必须写 'worklet',Nightly 编译器容错率较低
type=list 虚拟列表中,node.scrollTo 不支持 duration 参数,只能实现瞬时回顶
worklet 内不能直接调用 setData,与主线程通信需使用 runOnJS
#Skyline #前端实战 #虚拟列表 #Worklet

