scroll-view 是一个可以滚动的视图区域的容器组件。

一、重要属性

小程序ios滚动透传问题 微信小程序滚动组件_自定义


scroll-view 的滚动属性,实现了两套功能

  1. 左右或上下滚动
  2. 下拉更新
1.1 与滚动有关的属性:
  • scroll-x 允许横向滚动
  • scroll-y 允许纵向滚动

纵向滚动

小程序ios滚动透传问题 微信小程序滚动组件_自定义_02

<scroll-view scroll-y style="height: 300rpx;">
	<view id="demo1" class="scroll-view-item demo-text-1"></view>
	<view id="demo2" class="scroll-view-item demo-text-2"></view>
	<view id="demo3" class="scroll-view-item demo-text-3"></view>
</scroll-view>

横向滚动

小程序ios滚动透传问题 微信小程序滚动组件_ico_03

<scroll-view class="scroll-view_H" scroll-x style="width: 100%">
	<view id="demo1" class="scroll-view-item_H demo-text-1"></view>
	<view id="demo2" class="scroll-view-item_H demo-text-2"></view>
	<view id="demo3" class="scroll-view-item_H demo-text-3"></view>
</scroll-view>

双向滚动

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_04

<scroll-view bindscroll="onScroll" class="scroll-view_H" scroll-x scroll-y style="width: 100%;height:200rpx;">
	<view id="demo1" class="scroll-view-item_H demo-text-1">1</view>
	<view id="demo2" class="scroll-view-item_H demo-text-2">2</view>
	<view id="demo3" class="scroll-view-item_H demo-text-3">3</view>
</scroll-view>
.scroll-view_H {
  white-space: nowrap;
}

.scroll-view-item {
  height: 300rpx;
}

.scroll-view-item_H {
  display: inline-block;
  width: 100%;
  height: 300rpx;
}

.demo-text-1 {
  background-color: 	#E6E6FA;
}

.demo-text-2 {
  background-color: 	#E1FFFF;
}

.demo-text-3 {
  background-color: #FDF5E6;
}
  • scroll-top 、 scroll-left
  • 小程序ios滚动透传问题 微信小程序滚动组件_自定义_05


这两个属性,它们都是可以通过属性绑定、控制组件行为的属性。
如果我们想让内部的滚动实体滚动到某个位置,
并不能直接去调用它的类似于scrollTo的方法。
我们只能在js里面动态改变scroll-top,scroll-left  这两个属性绑定的变量。
然后在视图渲染更新以后,组件会自动发生滚动
  • scroll-into-view 滚动到某个元素,值应为某子元素id。
假如同时开启scroll-x、scroll-y横纵这两个方向的滚动,
当通过scroll-into-view滚动时,
那么它的滚动行为是怎样变化的呢?
![在这里插入图片描述]()
通过测试结果来看,结论很不明确,
如果不加scroll-with-animation的话,
也就是不开启动画,可以同时在x、y方向上瞬时移动到目标位置。

如果开启动画,同一时间只能在一个方向上滚动,
有时候在x方向滚动,有时候在y方向滚动,行为很不明确。

scroll-x scroll-y 最好不要同时开启,
如果功能需要,我们可以基于view实现同样的功能
或者是先在x方向上开启,完成移动后,再在y方向上开启,依次进行
<view class="page-section">
	<view class="page-section-title">
		<text>片9 测试scroll-into-view滚动</text>
	</view>
	<view class="page-section-spacing">
		<scroll-view enable-flex scroll-into-view="{{scrollIntoViewId}}" bindscroll="onScroll" scroll-y scroll-x scroll-with-animation="{{false}}" style="width: 100%;height:300rpx;">
			<view id="childview{{item}}0" wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9,10]}}" class="scroll-row">{{item}}
				<view wx:for="{{[0, 1, 2, 3, 4, 5,6,7,8,9]}}" class="scroll-item" id="childview{{item}}{{item2}}" wx:for-item="item2">{{item}}:{{item2}}</view>
			</view>
		</scroll-view>
	</view>
	<view class="btn-area">
		<button type="primary" bindtap="scrollToView1">滚动到子组件2</button>
	</view>
</view>
data: {
  scrollIntoViewId: '',
},
scrollToView1() {
  viewId += 2
  this.setData({
    scrollIntoViewId: 'childview' + viewId
  })
  console.log(this.data.scrollIntoViewId)
},
  • scroll-anchoring 滚动锚定,默认false,控制滚动位置不随内容的变化而抖动的。
滚动锚定是什么?
假设是一个图片瀑布流的页面,
当用户浏览瀑布流页面时,
加入由于网速的原因。
在看下面图片的时候,
上面图片突然加载进来,
这时候会使下方的图片自动往下跑,
这个体验肯定很不好。


滚动锚定的策略
通过一个CSS样式控制滚动实体在内容变化的时候不发生滚动。
scroll-anchoring 就是干这个的。


`这个属性某种情况下,可能会给开发者带来意想不到的bug`,
这个页面可能会陷入一种自循环,表现出一种`抖动不止的现象`。
当出现这个现象的时候,简单的解决方法就是`关闭滚动锚定策略`,
或者`设置一个具有相同效果的样式`
overflow-anchor:none;



scroll-anchoring 该属性目前`小程序只支持IOS`,`android手机需要自己通过CSS处理
overflow-anchor:auto;`

1.2 scroll-view组件的下拉更新属性
- upper-threshold
- lower-threshold  
  这两个属性是为了控制scrolltoupper、scrolltolowe事件何时派发的。默认都是50px
- bindscrolltoupper
- bindscrolltolower 
	这两个事件是状态事件,upper-threshold为50的时候,当scroll-top小于50的时候,
	只要滚动行为发生着bindscrolltoupper事件会多次派发,并且这种派发基本上是随心所欲的,
	派发基本是没有规律的,所以`说基于crolltoupper、scrolltolower这两个事件写业务逻辑的时候,
	我们要注意特别判断一下,是否已经处理过了,以免造成重复的处理。
	不是说滚动一次派发一次,有可能是滚动一次派发多次`。
- bindscroll

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_06

<!-- 测试scrolltoupper的随心所欲  -->

<view class="page-section">
    <view class="page-section-title">
        <text>片12 测试scrolltoupper的随心所欲</text>
    </view>
    <view class="page-section-spacing">
        <scroll-view upper-threshold="50" bindscrolltoupper="viewScrollToUpperEvent" scroll-y style="height: 300rpx">
            <view id="demo1" class="scroll-view-item demo-text-1"></view>
            <view id="demo2" class="scroll-view-item demo-text-2"></view>
            <view id="demo3" class="scroll-view-item demo-text-3"></view>
        </scroll-view>
    </view>
</view>


upper-threshold="50"
scroll-top在小于等于这个值的时候派发bindscrolltoupper事件
.page-section-spacing {
  margin-top: 60rpx;
}
.scroll-view-item {
  height: 300rpx;
}

.demo-text-1 {
  background-color: 	#E6E6FA;
}

.demo-text-2 {
  background-color: 	#E1FFFF;
}

.demo-text-3 {
  background-color: #FDF5E6;
}
viewScrollToUpperEvent(e) {
    console.log('测试scrolltoupper事件', e.detail);
},

二、自定义实现下拉刷新

2.1 与自定义下拉刷新相关的属性
- refresher-enabled      boolean,默认false,是否开启自定义下拉刷新
- refresher-threshold    number,触发下拉更新的临界值
- refresher-triggered    boolean,默认false,它是为了在更新后取消下拉更新的状态,当组件处于下拉更新的状态后,它的值变为true,此时程序要去做一些异步耗时的事情,例如网络加载,待处理完成了,再将这个值设置为false
- bindrefresherpulling
- bindrefresherrefresh
- bindrefresherrestore
- bindrefresherabort   后面4个事件是自定义实现下拉动画效果的关键

2.2 使用wxs实现自定义下拉刷新
bindrefresherpulling  手指按住往下拉的过程中派发的,自定义的动画效果要在这个事件里面处理

当向下拉动时,区域慢慢的放大,同时箭头图标有一个方向的翻转。

主要是做了三件事情,
第一,计算拉到哪里了,占总量高度80的多少,找到icon图标,设置它的旋转角度
第二,找到下拉动画的容器,设置它的缩放,达到看起来越往下拉,容器越来越大的一个效果。
第三,当拉到refresher-threshold 临界值时,改变下拉更新的提示文本
<!-- 自定义下拉更新 -->
<!-- 使用wxs自定义实现下拉刷新-->
<!--module="refresh"可以让我们在WXML中引用-->
<wxs module="refresh">
    var pullingMessage = "下拉刷新"

    module.exports = {
        // onPulling 下拉的过程当中我们干什么事情
        /*
        主要是做了三件事情,
        第一,计算拉到哪里了,占总量高度80的多少,找到icon图标,设置它的旋转角度
        第二,找到下拉动画的容器,设置它的缩放,达到看起来越往下拉,容器越来越大的一个效果。
        第三,当拉到refresher-threshold 临界值时,改变下拉更新的提示文本
        */
        onPulling: function (e, instance) { // instance 传进来的页面的实例对象
            // 80的高度,因为refresher-threshold设置的是80,手指按住往下拉的状态
            var p = Math.min(e.detail.dy / 80, 1) // 目前拉倒哪里了,进度 不大于1
            // console.log(p)
            // 这里在视图层,不怕频繁操作DOM
            var icon = instance.selectComponent('#refresherIcon') // 图标 WeUi组件
            icon.setStyle({
                opacity: p, // 透明度,越往下拉越清晰
                transform: "rotate(" + (90 + p * 180) + "deg)" // 旋转角度
            })
            var view = instance.selectComponent('.refresh-container') // 拉动的动画本身的容器
            view.setStyle({
                opacity: p,
                transform: "scale(" + p + ")" // 设置缩放
            })
            //拉到80的高度,可以释放状态
            if (e.detail.dy >= 80) {
                if (pullingMessage == "下拉刷新") {
                    pullingMessage = "释放更新"
                    instance.callMethod("setData", { // wxs 调用js的setData方法
                        pullingMessage
                    })
                }
            }
        },
        // 此时手拉开了,进入了加载中的状态
        onRefresh: function (e, instance) {
            // 此时手拉开了,进入了加载中的状态
            pullingMessage = "更新中"
            console.log(pullingMessage)
            instance.callMethod("setData", {
                pullingMessage: pullingMessage,
                refresherTriggered: true
            })
            instance.callMethod("willCompleteRefresh", {}) //调用js方法
            //willCompleteRefresh 方法
        },
        onAbort: function (e, instance) {
            // 异常状态,例如被事件突然打断,事件包括电话等,被迫松手了
            pullingMessage = "下拉刷新"
            console.log(pullingMessage)
        },
        onRestore: function (e, instance) {
            // 回去了,松手了,恢复原位了,不刷了
            pullingMessage = "下拉刷新"
            console.log(pullingMessage)
        },

    }
</wxs>
这是一段WXS代码,是在视图层执行的,在这里基本上可以肆意操作更新视图,
而不用担心更新频繁操作导致开销太大影响性能

在我们的代码里面之所以用callMethod方法,调用页面主体的setData方法,
就是为了曲线救国,达到更新视图的目的。
每个WXS代码里的事件句柄函数,在执行的时候都有两个参数传递进来,
事件对象与当前页面的实例对象,如果没有这两个参数,动画就实现不了。

WXS是在视图层里面执行的,js文件中的js代码是在逻辑层执行的。
WXS是WeXin Script的简写,它有自己的语法。严格按照官方文档去写。
<view class="page-section">
    <view class="page-section-title">自定义下拉刷新</view>
    <!-- 
        bindrefresherpulling  下拉的过程当中我们干什么事情

        bindrefresherrefresh   当它拉到可以松手的状态的时候

        refresher-triggered  为true是小程序设定的,异步操作完成之后,要设置为false,下拉状态就自己回去了
     -->
    <scroll-view scroll-y style="width: 100%; height: 400px;overflow-anchor:auto;" bindscroll="onScroll"
        bindscrolltoupper="onScrolltoupper" scroll-top="{{scrollTopValue}}" scroll-into-view="{{scrollIntoViewId}}"
        scroll-with-animation enable-back-to-top enable-flex scroll-anchoring refresher-enabled
        refresher-threshold="{{80}}" refresher-default-style="none" refresher-background="#FFF"
        bindrefresherpulling="{{refresh.onPulling}}" bindrefresherrefresh="{{refresh.onRefresh}}"
        bindrefresherrestore="{{refresh.onRestore}}" bindrefresherabort="{{refresh.onAbort}}"
        refresher-triggered="{{refresherTriggered}}">
        <!-- slot="refresher"  写死的名字,写其他的不行  -->
        <view slot="refresher" class="refresh-container"
            style="display: block; width: 100%; height: 80px; background: #F8f8f8; display: flex; align-items: center;">
            <view class="view1"
                style="position: absolute; text-align: center; width: 100%;display:flex;align-items:center;justify-content:center;color:#888;">
                <mp-icon id="refresherIcon" icon="arrow" color="#888" size="{{20}}"
                    style="margin-right:5px;transform:rotate(90deg)"></mp-icon>
                <text style="min-width:80px;text-align:left;">{{pullingMessage}}</text>
            </view>
        </view>

        <view wx:for="{{arr}}" id="view{{item+1}}" style="display: flex;height: 100px;">
            <text style="position:relative;top:5px;left:5px;color:black;">{{item+1}}</text>
            <image src="https://p.qqan.com/up/2021-6/16232893151517011.jpg"></image>
            <image src="https://p.qqan.com/up/2021-6/16232893724729414.jpg"></image>
            <image src="https://p.qqan.com/up/2021-6/16232893157513212.jpg"></image>
        </view>
    </scroll-view>
    <view class="btn-area">
        <button bindtap="plusScrollUpValue">向上滚动</button>
        <button bindtap="scrollToView1">滚动到子视图</button>
        <button bindtap="unshiftOnePic">顶部添加一张图</button>
    </view>

</view>

bindrefresherrestore 事件,是状态恢复了,是设置了refresher-triggered 为false动画完成以后派发的事件。
bindrefresherabort 是下拉行为被打断时派发的事件,正常情况下这种事件是不会收到的

data: {
    pullingMessage: '下拉刷新', //下拉刷新,释放更新,加新中...
    refresherTriggered: false, //
  },
    willCompleteRefresh() {
    console.log('更新中')
    // ... 的动画
    let intervalId = setInterval(() => {
      let pullingMessage = this.data.pullingMessage
      console.log(pullingMessage, pullingMessage == '更新中')
      if (pullingMessage.length < 7) {
        pullingMessage += '.'
      } else {
        pullingMessage = '更新中'
      }
      this.setData({
        pullingMessage
      })
    }, 500)
    // 2s 1.清理定时器  2.setData  
    setTimeout(() => {
      console.log('更新完成了')
      clearInterval(intervalId)
      this.setData({
        pullingMessage: "已刷新",
        refresherTriggered: false,   
        // 为true是小程序设定的,异步操作完成之后,要设置为false,下拉状态就自己回去了
      })
    }, 2000)
  },
bindrefresherrefresh事件,它是组件进入更新中状态时派发的事件,我们需要一个定时器,模拟网络异步加载,
但是WXS没有定时器,它只有一个页面实例对象的requestAnimationFrame函数,要么使用requestAnimationFrame方法模拟一个定时器,要么在js中实现

在js中定义willCompleteRefresh的方法,然后在WXS里面,在合适的时机,通过callMethod去调用它。

在这个组件中,willCompleteRefresh主要做了两件事情,
第一,使用一个定时器模拟实现 "更新中..." 后面的 ... 跳动的动画
第二,通过一个延时定时器在两秒以后设置刷新完成

小程序ios滚动透传问题 微信小程序滚动组件_下拉刷新_07

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_08


小程序ios滚动透传问题 微信小程序滚动组件_ico_09

2.3 总结

关于下拉刷新组件有两个开源项目,

mescroll     https://github.com/mescroll/mescroll
minirefresh   https://github.com/minirefresh/minirefresh

三、最佳实践

使用scroll-view组件有几点需要我们注意:

第一点,启用scroll-anchoring 属性时,同时添加一个overflow-anchor:auto;的样式,来应对Android机型不兼容的情况,

第二点,任何时候只开启一个方向的滚动,scroll-x 或者 scroll-y 只取其一,
当开启scroll-y时,必须给组件一个高度,子组件的高度之和一定要大于这个高度,
当开启scroll-x时,必须给组件一个宽度,一般这个值是100%,等于屏宽,子组件的宽度之和要大于屏宽,
在启用scroll-x时,宽度为100%,如果出现不滚动的现象,可以尝试给滚动容器添加两个这样的样式
white-space:nowrap;  不换行
display:inline-block;  行内块元素
目的是让子元素在横向上排列成一排,

第三点,开启enable-flex,这个属性是在scroll-view组件上启用Flex布局的,相当于添加了一个display:flex这样的样式,但是如果是我们自己添加的话,是添加在了外围的容器上,只有通过这个属性添加才能加到内部真正的容器上,

第四点,如果需要使用refresher-enabled 启用下拉动画的自定义,自定义可以很方便实现一些有创意的交互效果,
下拉动画容器的slot属性要标记为refresher。

第五点,下拉动画组件的背景色用#F8F8F8,前景色包括图标与文本用#888888这个颜色,符合微信设计规范

第六点,尽量不在js代码里面在scroll事件的句柄函数中直接更新视图,`把相关的频繁的更新视图的代码,放在WXS模块中`,
`在大列表视图中`,尤其要这么做。

四、在开发中遇到的问题

4.1 如何优化使用setData向其传递大数据、渲染长列表?scroll-view追加数据会自动回到顶部,怎么解决?
//更新二维数组
const updateList = `tabs[${activeTab}].list[${page}]`
const updatePage = `tabs[${activeTab}].page`
this.setData({
        [updateList]:res.data,
        [updatePage]:page + 1
})

<view wx-for="{{gameListWrap}}" wx:for-item="gameList">
......
</view>

上面的代码中,作者是想实现一个多tab页的功能,
数据是tabs,gameListWrap是对tabs子数据访问的再封装,
activeTab、page是模板字符串中的变量
updateList、updatePage是setData更新的时候用的key,因为是变量,所以需要用中括号

let  tabData = this.data.tabs[activeTab];

tabData.list.push(res.data);
tabData.page=page + 1;

let key = `tabs[${activeTab}]`
this.setData({
        [key]:tabData
})
作者为什么不直接使用push方法呢?
当有新数据进来的时候,直接往tab页数据的底部推入新数据,这样不就可以了吗?
但这种操作有一个问题,setData受限于视图层与逻辑层之间,用于传话的evaluateJavascript函数,
每次携带的数据大小,官方要求在文本序列化以后,大小不能超过256KB,
如果每个tab页是一个瀑布流页面,它的tabData.list可能是一个越来越大的数据,很有可能就超过256KB。



将tab数据与页面数据分开,在当前页面循环渲染时,按照pages[activeTab].page的数字循环,
取数据的时候依照page当前的值,从gameListData[activeTab]中查取,
gameListData这个时候在形式上相当于一个数据,但实际上它是一个map。

另外在渲染长列表的时候,微信在WeUI扩展组件库中,给出了一个长列表组件recycle-view,它用于渲染无限长的列表。



`那么这个问题怎么解决呢?`

使用`recycle-view扩展组件`:


https://developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html



这个长列表实现的原理也很简单,通过监听scroll事件,
只渲染当前视图窗口的内的list列表,看不见的地方用空白的占位符代替。

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_10


小程序ios滚动透传问题 微信小程序滚动组件_ico_11


小程序ios滚动透传问题 微信小程序滚动组件_自定义_12

小程序ios滚动透传问题 微信小程序滚动组件_ico_13

在使用recycle-view扩展组件的时候,batch属性的值必须为batchSetRecycleData,这是由组件自动管理的。
在js代码中调用createRecycleContext时,传入的dataKey:recycleList,
这个名称必须与WXML中的wx:for指定的数据名称一致,
如果一个页面中还使用了另外一个长列表,则需要再换一个名字。

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_14

<view class="page-section">
	<view class="page-section-title">使用recycle-view扩展组件</view>
    <recycle-view height="200" batch="{{batchSetRecycleData}}" id="recycleId" batch-key="batchSetRecycleData" style="background:white;">
        <recycle-item wx:for="{{recycleList}}" wx:key="index" class='item'>
            <view>
                {{item.id}}: {{item.name}}
            </view>
        </recycle-item>
    </recycle-view>
</view>

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_15


小程序ios滚动透传问题 微信小程序滚动组件_下拉刷新_16

const createRecycleContext = require('miniprogram-recycle-view')

function rpx2px(rpx) {
  return (rpx / 750) * wx.getSystemInfoSync().windowWidth
}

onReady: function () {
    
    var ctx = createRecycleContext({
      id: 'recycleId',
      dataKey: 'recycleList',
      page: this,
      itemSize: {
        width: rpx2px(650),
        height: rpx2px(100)
      }
    })
    let newList = []
    for (let i = 0; i < 20; i++) {
      newList.push({
        id: i,
        name: `标题${i + 1}`
      })
    }
    ctx.append(newList)
  },

总结

当从后端拉取大数据渲染长列表的时候,大多数情况下卡顿并不是手机真的卡了,
这个时候如果打开App就会发现很流畅。
很可能这个时候只是试图渲染不及时,
影响小程序渲染效率的罪魁祸首就是底层的evaluateJavascript这个通信函数,
它可以说是逻辑层与视图层之间的一个很小的独木桥,
他无法承载过大的数据量,
所以我们要尽量减少大数据的渲染,在视图中的互动操作要尽量在WXS代码中去完成
4.2 如何实现购物类小程序,分类选择物品的页面

小程序ios滚动透传问题 微信小程序滚动组件_下拉刷新_17

从效果图看,需要实现两个功能,
第一,单击左侧菜单,右侧区域自动滚动到相应的位置,
第二,在右侧滚动的时候,左侧菜单自动同步并高亮显示。

小程序ios滚动透传问题 微信小程序滚动组件_下拉刷新_18

<!--实现小程序页面分类选择物品页面  -->

<!-- 左侧菜单 -->
<scroll-view class='nav' scroll-y='true'>
	<view wx-for='{{list}}' wx:key='{{item.id}}'  id='{{item.id}}'
		class='navList{{currentIndex == index ? "active":""}}'  bindTap='menuListOnClick'  data-index='{{index}}'
	>{{item.name}}</view>
</scroll-view>

<!-- 右侧内容 -->
<scroll-view scroll-y='true'  scroll-into-view='{{activeViewId}}' bindscroll='scrollFunc'>
	<view class='fishList' wx:for='{{content}}' id='{{item.id}}' wx:key='{{item.id}}'>
		<p>{{item.name}}</p>
	</view>
</scroll-view>
//单击左侧菜单
  menuListOnClick(e){
    let me = this;
    me.setData({
      activeViewId:e.target.id,
      currentIndex:e.target.dataset.index
    })
  },
   //滚动时触发,计算当前滚动到的位置对应的菜单是哪个
  scrollFunc(e){
    //看向上滚动了多少
    this.setData({
      scrollTop:e.detail.scrollTop
    });

    // 右侧的内容区域有一个heightList
    // 每一块区域高度我们一个一个去对比
    // 看目前滚动大概是处于哪一个高度的范围之内

    //我们要事先在渲染的时候,heightList区域高度要事先计算出来并存储出来
    for(let i= 0;i<this.data.heightList.length;i++){
      let height1 = this.data.heightList[i];
      let height2 = this.data.heightList[i + 1];

      if(!height2  ||  (e.detail.scrollTop >= height1 && e.detail.scrollTop < height2) ){
          this.setData({
            currentIndex:i  // 设置当前选择的是哪一个
          });
      }
      return ;
    }

    this.setData({
      currentIndex: 0
    });
  }
4.3 WeUI组件库中有一个vtabs组件,实现效果同问题 4.2,是一个有侧边栏的浏览组件,使用该组件。

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_19

案例

小程序ios滚动透传问题 微信小程序滚动组件_小程序ios滚动透传问题_20


小程序ios滚动透传问题 微信小程序滚动组件_ico_21

vtabs.wxml

<mp-vtabs 
  vtabs="{{vtabs}}" 
  activeTab="{{activeTab}}" 
  bindtabclick="onTabCLick"
  bindchange="onChange"
  class="test"
>
  <block wx:for="{{vtabs}}" wx:key="title" >
    <mp-vtabs-content tabIndex="{{index}}">
      <view class="vtabs-content-item">我是第{{index + 1}}项: {{item.title}}</view>
    </mp-vtabs-content>
  </block>
</mp-vtabs>

vtabs.js

Page({
  data: {
    vtabs: [],
    activeTab: 0,
  },

  onLoad() {
    const titles = ['热搜推荐', '手机数码', '家用电器',
      '生鲜果蔬', '酒水饮料', '生活美食', 
      '美妆护肤', '个护清洁', '女装内衣', 
      '男装内衣', '鞋靴箱包', '运动户外', 
      '生活充值', '母婴童装', '玩具乐器', 
      '家居建材', '计生情趣', '医药保健', 
      '时尚钟表', '珠宝饰品', '礼品鲜花', 
      '图书音像', '房产', '电脑办公']
    const vtabs = titles.map(item => ({title: item}))
    this.setData({vtabs})
  },

  onTabCLick(e) {
    const index = e.detail.index
    console.log('tabClick', index)
  },

  onChange(e) {
    const index = e.detail.index
    console.log('change', index)
  }

})

vtabs.json

{
  "usingComponents": {
    "mp-vtabs": "../../components/vtabs/index",
    "mp-vtabs-content": "../../components/vtabs-content/index"
  }
}

vtabs.wxss

@import '../common.wxss';

page{
    background-color: #FFFFFF;
    height: 100%;
}

.vtabs-content-item {
    width: 100%;
    height: 300px;
    box-sizing: border-box;
    border-bottom: 1px solid #ccc;
    padding-bottom: 20px;
}

common.wxss

@import '../components/weui-wxss/dist/style/weui.wxss';

page{
    background-color: #F8F8F8;
    font-size: 16px;
    font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif;
}
.page__hd {
    padding: 40px;
}
.page__bd {
    padding-bottom: 40px;
}
.page__bd_spacing {
    padding-left: 15px;
    padding-right: 15px;
}

.page__ft{
    padding-bottom: 10px;
    text-align: center;
}

.page__title {
    text-align: left;
    font-size: 20px;
    font-weight: 400;
}

.page__desc {
    margin-top: 5px;
    color: #888888;
    text-align: left;
    font-size: 14px;
}
.weui-cell_example:before{
    left:52px;
}
/* .weui-btn{width:184px;} */

组件源码

小程序ios滚动透传问题 微信小程序滚动组件_自定义_22


小程序ios滚动透传问题 微信小程序滚动组件_下拉刷新_23