前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微信小程序实践:2.3 可滚动的容器组件之 scroll-view

微信小程序实践:2.3 可滚动的容器组件之 scroll-view

作者头像
LIYI
发布2020-04-15 17:11:09
14K1
发布2020-04-15 17:11:09
举报
文章被收录于专栏:艺述论专栏艺述论专栏

目录

代码语言:javascript
复制
1、scroll-view 相关问题
2、应用场景
3、主要属性讲解
  3.1,scroll-x、scroll-y,scroll-top等
  3.2,滚动锚定:scroll-anchoring
  3.3,upper-threshold、lower-threshold等
  3.4,refresher-enabled、refresher-threshold等
4、示例代码与最佳实践
  4.1、示例代码
  4.2、最佳实践
5、开发者经常遇到哪些问题?
  5.1,使用 scroll-view 时,如何优化使用 setData 向其传递大数据、渲染长列表?
  5.2,scroll-view 开启自定义下拉刷新,scroll-view 里面内容太少无法触发刷新?
  5.3,scroll-view 在 ios 中下拉刷新,触发两次 bindscrolltoupper 事件?
  5.4,scroll-view 组件为什么有时候 scroll-x 不作用?
  5.5,scroll-view 中两个 scroll-x 和 scroll-y 同时启用有 bug?
  5.6,什么情况下需要使用 scroll-view 的下拉刷新,而不使用页面本身的下拉刷新?
  5.7,scroll-view 内不支持嵌套原生组件吗?
  5.8、如何实现购物类小程序分类选物品页面?
6、如何在小程序中使用 WeUI 组件库?
阶段源码
参考文献

文 / 石桥码农

本文约 21126 字,阅读 22 分钟

说什么真理无穷,进一寸有一寸的欢喜。大家好,我是石桥码农,今天继续为大家分享微信小程序实践相关的技术内容。

一个框架内每个组件的设计,都有设计者的考虑,每个组件都有其特殊的用途。如果说view的存在,主要是为了实现各种常见的ui布局,那么今天分享的,是「三动」容器组件之一的scroll-view。它与movable-viewcover-view,是三动组件,都是为了方便开发者实现特定场景下的特殊业务功能而设计的。

没有这些组件,开发者自己通过view也能实现这些功能;但有了这些组件,实现起来简单了,学习成本也高了。特别当组件的设计过于随心所欲时,学习者的学习负担也更大了

1、scroll-view 相关问题

scroll-view是可滚动视图区域组件。这个组件几乎是每一个复杂的多页面小程序都会用的,是使用最广泛的组件之一,但也是在社区被开发者最广为诟病的组件之一。

关于这个组件,有以下几个问题值得思考:

1,当我们说滚动时,涉及到外面的滚动容器与里面的可滚动实体两个对象,我们说滚动到顶部、滚动到底部,指的是什么呢?是什么到顶部,什么到底部了?

2,当滚动事件派发时,滚动到顶部是一个状态,还是一个单一的事件,它会触发多次吗?

3,scrolltoupper事件、scrolltolower事件是什么时候触发的?直接改变scroll-top属性可以触发吗?

4,设置scroll-into-view这个属性,可以将内容盒子滚动到某个子元素处,具体是滚动到哪里呢?如何理解这个属性?

5,如果一个瀑布流页面中有许多图,上面的图比下面的图加载慢,当看到下面图的时候,上面的图突然加载出来,把下面的图挤跑了,这种情况有没有办法解决?是什么技术?

6,有时候在一个后台vue页面中,没有人动它,它自己抖动不止,这可能是什么情况?

7,如何在scroll-view中自定义实现一个下拉刷新交互动画?

8,使用scroll-view实现瀑布流功能时,如果页面比较卡顿,可以朝哪个方向优化?

9,在一些列表中,有时候出于性能考虑,可能需要故意放置一个空白、不显示的子项。空白子项虽然无形中增加了软件包的size,但是也默默提高了性能。

10,在一些购物类或订餐类小程序中,左侧有物品分类,左侧是物品列表,单击分类,右侧自动滚动到相关位置,右侧列表上下滚动,左侧分类菜单自动切换,获得高亮焦点,这样的功能是怎么实现的?

如果这些问题你都比较明白,这个组件相关的内容就没必要看了。

2、应用场景

在某购物App上,有这样一个功能:

因为导航按钮太多,产品人员将非常用的按钮放在了第二屏,需向左滚动才可以看到。

在这个地方,有一个实际内容宽度大于手机屏幕的容器,它支持用户用手指左右滑动。下方还有一个滚动提示条,这是根据滚动位置计算出来的。这是自定义实现的效果,相当于浏览器的滚动条,效果是通过css样式控制的。

3、主要属性讲解

scroll-view是一个略显复杂的组件。它的属性主要支持了两个功能:左右滚动与下拉更新。

3.1,scroll-x、scroll-y,scroll-top、scroll-left、scroll-into-view

scroll-x、scroll-y默认都是false,不开启滚动。当scroll-y为真时,允许纵向滚动;当添加scroll-x属性时,允许横向滚动。

在这张动图中,上面启用的是scroll-x,下方启用的是scroll-y。由于手机屏幕比较窄,横向滚动需求比较常见。

从实践结果看,scroll-xscroll-y不是一对互斥的属性,并不是设置了scroll-y,就不能设置scroll-x。两个方向的滚动可以同时开启,但在操作时,只能同时朝一个方向滚动。

scroll-top指内部的滚动实体,高于顶部边缘多少距离。单位默认是px,也可以传入rpx。默认情况下scroll-top0,当实体向上滚动时,其值慢慢增加。

同理,scroll-left类似。当开启的是横向滚动时,scroll-left是距离左边界、子实体向左滚动的距离。

我们一般说「滚动到顶部、滚动到底部」,指的还不是内部滚动实体滚动到了它所能达到的最大值、最小值,而是指滚动实体顶部边缘到达了滚动外框的顶部,及底滚动实体底部边缘到达了滚动外框的底部。都是以滚动外框为参照物的。

同样scroll-top、scroll-left这两个属性,它们也是以滚动外框的位置为参照物的。

scroll-top、scroll-left这两个属性,它们是通过属性绑定、控制组件行为的属性。如果我们想让滚动实体滚动到某个位置,并不能直接调用它的一个类似于scrollTo()的方法。我们只能在JS里动态改变scroll-top、scroll-left这两个属性绑定的变量,然后视图渲染后,组件会自动发生滚动。

vue、小程序中到处都是这样的响应式控制机制,不是直接去调用页面上组件的方法,而只是给组件属性设置一个值,然后静静地等待组件自己更新。

在软件设计中,一般我们为一个对象定义一个类,这个类既有方法,又有属性。我们将这个类实例化,既可以改变实例的属性,又可以调用实例的方法;并且在大多数情况下,我们改变属性时,并不会使实例发生什么行为,而只有明确调用它的方法时,它才会有所动作。

现在在前端这一块,像vue、小程序这样的框架,把这个传统给颠覆了。直接传一个值,让组件自己负责更新,这样看起来更简单。但是在复杂的业务逻辑中,如果能直接能调用组件的方法,可能会更简单一些,因为那样连用于属性绑定的变量都不需要声明了。有时候这种声明是完全没有必要的。

scroll-top、scroll-left类似的属性,还有scroll-into-view,它用于滚动到某个元素。这个属性很好理解,它的值必须是一个子视图的id,滚动时微信小程序是以子视图的上、左边界为测算依据的。也就是说,纵向滚动,使scroll-top等于子视图的上边界;横向滚动,使scroll-left等于子视图的左边界。

这是一个语法糖属性,它帮助开发者做了一些事情。没有这个属性,我们通过id查找组件,找到组件的上、左边距离上、左滚动边框的距离,通过设置scroll-top、scroll-left属性,同样可以达到目的。

官方文档说,在使用scroll-into-view时,「设置哪个方向可滚动,则在哪个方向滚动到该元素」。

这里有一个问题,前在我们知道了scroll-x、scroll-y这两个布尔属性并不互斥,假如我们同时开启横向、纵向滚动,当通过scroll-into-view向某个子view滚动时,滚动行为是怎么样的?

是先向x方向滚动,还是先向y方向滚动?还是两个方向同时滚动?

答案是小程序错乱了,它既不会同时滚动,也不会先后依次滚动。

程序都是人编出来的,功能也都是有边界的,没有编写过那部分代码,自然也不会那部分功能。

3.2,滚动锚定 scroll-anchoring

这个属性非常值得一提。它是控制「滚动锚定」特征的,即控制滚动位置不随内容变化而抖动,这种情况据说在用户浏览行为中占比1%。这个属性默认是false,添加后,功能才会开启。

什么是滚动锚定?

假设我们有一个图片瀑布流页面,这样的页面在网站上有许多,随处在一个设计网站上都可以看到。

用户浏览瀑布时,假如由于网速原因,在看下面的图片时,上面的图片突然加载出来。这时候因为上面的图片会使下方的图片自动往下跑。

这个体验肯定很不好。

为了解决这个1%的问题。谷歌提出了「滚动锚定」策略,即通过一个css样式,控制滚动实体在内容变化时不发生滚动

微信小程序scroll-anchoring这个属性,就是干这个用的。它是一个布尔属性,添加它以后,当上面内容扩充时,微信会自动向上滚动一段扩充的距离。这就是「滚动锚定」策略。不是没有滚动,而是滚动冲抵了,scroll-top已经不一样了。

但是这个属性在某种情况下会给开发者带来意想不到的bug

vue作为响应式框架,视图自动响应数据更新而重新渲染。假设在某个后台vue项目中,如果恰巧某个滚动实体监听了滚动事件,在滚动发生时自动干了一个改变滚动内容的事。这件事可能很小,只是改变一个边框、或一处字体1px的大小,但是由于启用了滚动锚定,这个页面可能陷入一种自循环,发生抖动不止的现象。

当出现这样的「抖动永动机」时,简单解决的方法,就是关闭「滚动锚定」策略,或设置一个这样的样式:

代码语言:javascript
复制
overflow-anchor:none;

同时,开启这个策略才可以通过样式开启。scroll-anchoring这个属性,目前小程序只支持iOS手机,在Android手机上需要开发者自己处理。在Android手机上可以添加这样的样式实现相同的功能:

代码语言:javascript
复制
overflow-anchor: auto;

3.3,upper-threshold、lower-threshold、bindscrolltoupper、bindscrolltolower、bindscroll

upper-threshold、lower-threshold这两个属性,是用于控制scrolltoupperscrolltolower事件的,默认都是50px

scroll-top小于upper-threshold时,组件派发scrolltoupper事件;同理,当scroll-top小于lower-threshold时,派发scrolltolower事件。这是纵向滚动的情况,当是横向滚动时,是拿scroll-left作为比对值。

这里需要注意,这两个事件不是点事件,而是状态事件。也就是说,upper-threshold50,当scroll-top小于50时,只要滚动行为在发生,scrolltoupper事件会多次派发。

并且派发的是随心所欲。滚动事件是scroll,并不是scroll派发一次,scrolltoupper派发一次;也不是scroll派发三次或五次,scrolltoupper派发一次。是毫无规律可言。

在这里我们看小程序组件的属性命名,也是随心所欲,毫无章法。

flex布局里,我们知道当flex-direction的样式值为不同的rowcolumn时,样式值flex-startflex-end分别也代表了不同的含义。这种思维更像是程序员的思维模式。

但是你看小程序组件的属性是怎么命名的?

scroll-x、scroll-y本应该是两个互斥的属性,结果不互斥;这两个属性应该合并为一个scroll-direction属性,值应该参照css,取rowcolumn。为什么要给程序额外制造心智负担呢?

还有scroll-topscroll-left,也应该合并为scroll-start

既然upper-threshold代表了距离顶部 / 左边多远,lower-threshold代表了距离底部 / 右边多远,它俩都是以一抵二的属性,为什么scroll-top、scroll-left,还有scroll-x、scroll-y,要分成两个呢?同一套组件为啥用两套标准?

太随心所欲了。这样的随心所欲,除了增加新手学习的负担,别无用途。怪不得人都说开发难学,一部分门槛是程序员兄弟为自己人量身打造的。

3.4,refresher-enabled、refresher-threshold、refresher-triggered、bindrefresherpulling、bindrefresherrefresh、bindrefresherrestore、bindrefresherabort

看到这些事件名,就想到没有句读的古文。名称长,没有小驼峰,也没有连字符、下划线分隔,非常不一目了然。

前面三个属性,还有后面四个事件,都是与下拉刷新有关的。刚流行iPhone智能手机的时候,下拉刷新是一个体验亮点。后来这种功能设计渐渐成为了App设计规范。

refresher-enabled用于控制是否开启自定义下拉刷新,默认为falserefresher-threshold是触发下拉更新的临界值,向下拉,松手又回去了,列表没有更新,这是没有达到refresher-threshold的值;达到这个值后,松手是「更新中」的提示。

refresher-triggered这个布尔值,默认为false。它是为了在更新后,取消下拉更新状态的。当组件处于「下拉更新」状态后,它的值变为true,此时程序要去做一些耗时的事情,例如网络加载。待处理完成了,将这个值置为false,下拉更新的状态就恢复回去了。

后面四个事件,是实现自定义下拉动画的关键。

bindrefresherpulling这个事件,是手指按住了,往下拉的过程中派发的。自定义的动画要在这个事件里处理。上面的动画就是自实现的下拉更新动画。

WXS代码:

代码语言:javascript
复制
<wxs module="refresh">    ...    onPulling: function(e, instance) {      var p = Math.min(e.detail.dy / 80, 1)      var icon = instance.selectComponent('#refresherIcon')      icon.setStyle({        opacity: p,        transform: "rotate(" + (90 + p * 180) + "deg)"      })      var view = instance.selectComponent('.refresh-container')      view.setStyle({        opacity: p,        transform: "scale(" + p + ")"      })      if (e.detail.dy >= 80) {        if (pullingMessage == "下拉刷新") {          pullingMessage = "释放更新"          instance.callMethod("setData", {            pullingMessage          })        }      }    }  }</wxs>

这段代码稍微有点复杂,主要干了三件事:

1,计算拉到哪了,占总量80的多少,找到icon图标,设置它的旋转角度

2,找到下拉动画的容器,设置它的缩放,看起来越往下拉、容器越大

3,当拉到refresher-threshold临界值时,改变下拉更新的提示文本

这是WXS代码,是在视图层执行的,在这里可以肆意地操作DOM、更新视图,而不用担心因更新渲染开销大。因为它压根儿就不会更新。代码里之所以用callMethod调用页面主体的setData方法,就是为了曲线救国、达到更新视图的目的。

每个WXS代码中的事件句柄函数,执行时都有两个参数传递进来:事件对象与当前页面的实例对象。如果没有这两个参数,这个动画就实现不了啦。默认情况下,WXS视图层执行,与页面JS中的代码不是一路的,后者是在逻辑层执行的。

如微信官方文档所讲,WXS是一套不一样的脚本语言,它是WeXin Script的简写。WXSJS是不同的语言,有自己的语法,并不和JS一致。

举个例子,在JS中我们一般使用let代表var声明变量,这可以避免因变量作用域不合适而产生奇怪的bug。但是在WXS中,如果我们使用let声明变量的话,微信开发者工具立刻就给我们爆出一个奇怪的bug

此时代码错乱,无法执行。编辑器会报一个没有什么任何文本提示的错误。这种错误最让人抓狂,毫无征兆、毫无线索,根本无从查证。这个时候只有运气和耐心,还有上帝能帮助自己。

WXS真的是和JS不一样的语言。

我们再看一下bindrefresherrefresh事件。这个事件应该这样读:bind-refresher-refresh,我第一次看到它,就错看成了是err-refresh,以为是发生某个错误时派发的事件,其实不是。

它是组件进入更新中状态时派发的事件。

WXS代码:

代码语言:javascript
复制
onRefresh: function(e, instance) {  // 此时手拉开了,进入了加载中的状态  pullingMessage = "更新中"  instance.callMethod("setData", {    pullingMessage: pullingMessage,    refresherTriggered: true  })  instance.callMethod("willCompleteRefresh", {})},

在这个地方需要一个定时器模拟网络加载,但是WXS里没有定时器。要么使用页面实例的requestAnimationFrame方法模拟一个定时器,要么在JS中实现。

我选择了后者,这个方案看起来更简单。我在JS中定义了一个willCompleteRefresh方法,然后再在WXS中在合适的时机通过callMethod调用它。

JS代码:

代码语言:javascript
复制
willCompleteRefresh(){  let intervalId = setInterval(()=>{    let pullingMessage = this.data.pullingMessage    console.log(pullingMessage,pullingMessage == '更新中')    if (pullingMessage.length < 7){      pullingMessage += '.'    }else{      pullingMessage = '更新中'    }    this.setData({      pullingMessage    })  },500)  setTimeout(()=>{    clearInterval(intervalId)    this.setData({      pullingMessage:"已刷新",      refresherTriggered:false,    })  },2000)},

bindrefresherrestore事件是状态恢复了,是设置了refresher-triggeredfalse,动画完成之后派发的事件;bindrefresherabort是下拉行为被打断时派发的事件,正常情况下这种事件不会收到。这两个事件属于不可或缺,但不重要的事件。

具体可以看我的阶段性源码,在下方有链接。

关于下拉刷新的组件,有两个开源项目可以参考:

代码语言:javascript
复制
mescroll:github.com/mescroll/mescrollminirefresh:github.com/minirefresh/minirefresh

4、示例代码与最佳实践

4.1、示例代码

代码语言:javascript
复制
<wxs module="refresh">  ...</wxs><scroll-view scroll-y   style="width: 100%; height: 400px;overflow-anchor:auto;"   scroll-with-animation   enable-back-to-top enable-flex   scroll-anchoring refresher-enabled   refresher-default-style="none"   refresher-background="#FFF"   bindrefresherpulling="..."   refresher-triggered="{{refresherTriggered}}">  <view slot="refresher" class="refresh-container"      style="display: block; width: 100%; height: 80px; background: #F8f8f8; display: flex; align-items: center;">    <view class="view1" style="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>  ...</scroll-view>

这里用到的mp-icon,是WeUIicon组件,后面会详细介绍它的使用方法。

4.2、最佳实践

  1. 启用scroll-anchoring,同时添加overflow-anchor:auto样式,应对Android机型
  2. 只开启一个方向的滚动,scroll-yscroll-x只取其一。当开启scroll-y时,必须给组件一个高度,例如400px,或其它值;当启用scroll-x时,必须给组件一个宽度,一般这个值是100%,取屏幕宽度。
  3. 开启enable-flex,这个属性是启用flexbox布局的,相当于添加display:flex样式。但是如果是自己添加,是加在了外围容器上,只有通过这个属性添加,才能加到内围真正的容器上。这是个复杂的容器。
  4. 当需要时,使用refresher-enabled启用下拉动画的自定义。自定义可以很方便地实现这样的小人跑动动画:

自定义的代码最好在WXS中实现,以bindrefresher开头的事件句柄都在WXS中定义。这可以提高渲染效率,减少页面卡顿。

  1. 下拉动画组件的背景色用#F8f8f8,前景色——包括图标与文本,用#888,这更符合微信设计规范。
  2. 在下拉动画组件中,可以启用flexbox布局,参见上面的WXSS代码。这容易使图标、文本上、下、左、右居中。
  3. 在自定义下拉动画时,容器的slot要标记为refresher,虽然官方文档没有这样写,但如果你不这样做,你的自定义下拉动画是拒绝工作的。
  4. 尽量不要在JS代码中,在scroll事件句柄中,直接更新视图,把相关的频繁的更新视图的代码,放在WXS模块中。在大列表视图中尤其要如此。
  5. 在启用scroll-x时,一般设置宽度为100%,横向满屏。如果出现不滚动的现象,可以尝试给外框容器添加样式:white-space:nowrap;display:inline-block,并且保证内容的实际宽度大于屏幕宽度。

5、开发者经常遇到哪些问题?

5.1,使用 scroll-view 时,如何优化使用 setData 向其传递大数据、渲染长列表?

JS代码:

代码语言:javascript
复制
// 更新二维数组const updateList = `tabs[${activeTab}].list[${page}]`const updatePage = `tabs[${activeTab}].page`this.setData({    [updateList]: res.data,    [updatePage]: page + 1})

wxml

代码语言:javascript
复制
<view wx:for="{{gameListWrap}}" wx:for-item="gameList">   ...</view>

这是微信开发者社区上的一个问题,原问题是「scroll-view追加数据会自动回到顶部,请问怎样解决?」。

作者可能是想实现一个多tab页的功能,数据是tabs,这是一个大数组。gameListWrap应该是对这个tabs子数据访问的再封装。

在JS代码中,{activeTab}、{page}都是模板字符串中的变量。updateList、updatePage是setData更新时用的key,因为是变量,所以在使用时要用[]括起来。

作者为什么不直接使用push方法呢?例如:

代码语言:javascript
复制
let tabData = this.data.tabs[activeTab]tabData.list.push(res.data)tabData.page = page+1let key = `tabs[${activeTab}]`this.setData({  [key]: tabData})

当有新数据进来时,直接往某个tab页数据的底部推入新数据。

但这种操作有一个问题。setData受限于视图层逻辑层之间用于传话的evaluateJavascript函数,其每次携带的数据大小,官方评测标准要求在文本序列化以后大小不能超过256KB。如果某个tab页是一个瀑布流,其tabData.list可能是一个越来越大的数据,不超过256KB是很难的。

这就犯了「每次setData都传递大量新数据」的忌讳,是不被微信小程序官方建议的。

虽然传递的不全是新数据,但微信小程序不知道哪些是新的,哪些是旧的,凡是在list中传递过来的,它都认为是新数据。

那么这个问题如何解决呢?如何再优化一下呢?方法是只更新新数据,可以参照作者在实践过程中找到的解决方法。代码:

代码语言:javascript
复制
const updateListStr = `gameListData[${activeTab}][${page}]`const updatePageStr = `pages[${activeTab}]`this.setData({  [updateListStr]: res,  [updatePageStr]: page + 1})

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

在渲染长列表时,微信给出了一个长列表组件recycle-view

代码语言:javascript
复制
developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html

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

我在vue项目中曾实现过一个类似的长列表组件,以前推过文章,可以在这里查看:15 v-if 条件渲染与 v-for 列表渲染

不知道这个问题我讲明白没有,从后端拉取大数据渲染长列表时,现在你明白应该怎么做了吗?

关键是明白卡顿并不定是手机真卡了,并不一定是GPU运转不过来了,而是视图渲染不及时。我们看到页面卡顿时,可能GPU空闲率有90%

影响小程序渲染效率的罪魁祸首是evaluateJavascript这个底层通讯函数,它是逻辑层与视图层之间一个很小的独立桥,无法承接过大、过快的派遣。

5.2,scroll-view 开启自定义下拉刷新,scroll-view 里面内容太少无法触发刷新?

这个问题在旧的基础库版本中存在,经测试在新的2.10.4版本下该问题已经解决了。

之所以出现这个问题,是因为scroll-view组件所有事件,除了scroll本身,都是scroll事件的次生代事件。也就是说,像refresher开头的事件是以scroll事件为基础,在内部做了计算之后派发的。

内容太少,根本无法触发scroll事件,还怎么触发下拉更新呢?

在新的基础库版本中虽然解决了这个问题,但是当内容少的时候,却是连页面内容也滑动了。这是可以理解的,因为除了在父容器上监听scroll事件,可能也没有其它的解决方法了。

问题是解决了,但牺牲了一些性能。如果内容少,建议直接添加一个看不见的容器,使内容高度一定大于滚动框架的高度,就没有这个问题了。

在一些展示列表中,开始的时候可能只有一二个子项,这个时候也想触发下拉更新,合适的做法是在列表里故意放一个无用的空项。看以无用,实则有用。

5.3,scroll-view 在 ios 中下拉刷新,触发两次 bindscrolltoupper 事件?

这个问题前面讲过了,scrolltoupperscroll的次生代事件,是状态事件,不是单点事件,存在多次派发的情况。这种情况只能自己在业务逻辑中做一些特别的防抖动处理。

5.4,scroll-view 组件为什么有时候 scroll-x 不作用?

有时候是鼠标无法滑动,在mac电脑上,用触控板就可以滑动。

如果不是这个问题,可以考虑以下三点:

  1. 内容宽度是否大于外框容器宽度
  2. 可以给外框添加white-space:nowrap;display:inline-block样式,看能否解决
  3. 如果内容使用flexbox布局,要确保scroll-view组件启用了enable-flex属性。启用enable-flex属性,与直接添加display:inline-block并不冲突。

这里有一个延伸问题,white-space设置为nowrap好理解,是不换行;display设置为inline-block是什么意思呢?为什么不设置为blockinline

block是块元素样式,将组件设置为块元素,可以设置它的宽、度、marginpadding等值。block会自动换行。inline是内联元素样式,容器设置为inline后,子元素将在一行内显示、不换行。inline-block兼具两者优势,子元素既在一行内显示、不换行,又能设置其宽、高等块元素属性。

举个例子,ulli默认是以上而下换行显示的,如果给ul添加display:inline-block,所有li会排行成一行。

理解了inline-block样式值的作用,回头再看为什么添加display:inline-block这个样式,就好理解了。

5.5,scroll-view 中两个 scroll-x 和 scroll-y 同时启用有 bug?

据描述现象是这样的:苹果iOS手机正常,在安卓手机上乱跳。

不要同时启用这两个属性。他们虽然形式上不是互斥的,但实际上却是互斥的。这是架构师在框架设计上的疏忽。

5.6,什么情况下需要使用 scroll-view 的下拉刷新,而不使用页面本身的下拉刷新?

除了使用scroll-view的下拉刷新,有一种替代方案,是直接使用Page的下拉刷新。如何使用呢?

很简单,在app.jsonwindow选项中或页面配置中,开启enablePullDownRefresh。通过wx.startPullDownRefresh触发下拉刷新,此时页面将拉于「更新中」的状态。当处理完异步加载后,使用wx.stopPullDownRefresh停止更新状态。

并且,在滚动scroll-view时,小程序会阻止页面回弹;在scroll-view中滚动,无法触发onPullDownRefresh事件。基于此有人建议,尽量不要使用scroll-view的下拉刷新。

但是,有时候必须基于scroll-view实现局部页面的刷新,这种情况是很普遍的。

在顶部自定义一个navigatorBar导航栏,单击一个按钮切换到一个页面,每个页面都是一个独立的scroll-view组件。这时候下拉刷新使用page整体的就不合适了,下拉刷新的动画必须出现在navigatorBar下方才合理。这时候就必然用到scroll-view的自定义下拉刷新功能了。

scroll-view需要一个固定的高度,如果要自定义实现下拉刷新,这个高度需要我们自己计算。

通过wx.getSystemInfo可以获取到两个屏幕高度:screenHeightwindowHeight,前者是屏幕高度,是手机上会亮的那块玻璃板的高度;后者是一个计算值,是screenHeight减去系统状态栏——有电量提示、wifi信号的那一栏(statusBarHeight)、再减去导航栏——有标题和胶囊按钮的那一栏、再减去微信自带的tabBar组件的高度,之后得到的才是windowHeight,是可用的窗口高度。

如果页面配置启用了navigationStyle:"custom",开发者自定义页面导航栏,则导航栏高度不会在windowHeight中减去;还有,如果某个页面没有启用tabBar,高度又会增大一些。

拿到windowHeight之后,它还不是scroll-view应有的高度,因为页面上还可能有自定义的底部导航栏、顶部导航栏,这些高度也要减去。

因为这些原因,给scroll-view设置高度,在不同页面是不一样的,必须区别对待。

5.7,scroll-view 内不支持嵌套原生组件吗?

不支持也情有可原,因为要滚动,普通组件与原生组件都不在一个层,一个要上面,一个要下面,怎么同步?

网上有人说,小程序scroll-view不支持嵌套textarea等组件,那是旧版本。网上有许多教程是旧的。

从基础库2.4开始,已经开始支持嵌套textareamapcanvasvideo这些原生组件了。其它原生组件不支持。支持的越多功能越完备,学习越复杂,性能流失也越大。

5.8、如何实现购物类小程序分类选物品页面?

这里主要需要实现两个功能:

  1. 单击左侧菜单,右侧滚动到相应位置
  2. 在右侧滚动,左侧菜单自动同步高亮

第一个功能点,可以通过scroll-into-view属性实现,将左侧菜单与右侧每块区域的id对应起来,单击时更新scroll-into-view绑定的id

wxml代码:

代码语言:javascript
复制
<!-- 左侧菜单 --><scroll-view scroll-y='true' class='nav'>  <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>

JS代码:

代码语言:javascript
复制
// 单击左侧菜单menuListOnClick:function(e){  let me=this;  me.setData({    activeViewId:e.target.id,    currentIndex:e.target.dataset.index  })}// 滚动时触发,计算当前滚动到的位置对应的菜单是哪个scrollFunc:function(e){  this.setData({    scrollTop:e.detail.scrollTop  })  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  })}

第二个功能点,是通过计算实现的。在列表数据绑定时,把右侧每块物品区域的高度记录下来,就是上面代码中的heightList。右侧列表滚动时,通过绑定scroll事件,拿到scrollTop,循环对比在哪个区域,就把哪个区域对应的菜单高亮。

6、如何在小程序中使用 WeUI 组件库?

首先,从网站上下载组件包:

代码语言:javascript
复制
developers.weixin.qq.com/miniprogram/dev/extended/weui/download.html

微信提供了按需下载的功能,可以只下载自己用到的组件。

接着解压组件包,将解压到的目录weui-miniprogram复制到项目根目录下。如果开启了云开发,一般为miniprogram目录。

再着,在app.wxss里面引入weui.wxss

代码语言:javascript
复制
@import './weui-miniprogram/weui-wxss/dist/style/weui.wxss'

可以将这句代码直接拷贝到app.wxss文件内。这是WeUI组件库的样式表。没有没有这个文件,组件不能正常显示。

再着,在哪个页面引用什么组件,就在它的json配置文件中添加usingComponents组件使用声明。以icon为例:

代码语言:javascript
复制
{  "usingComponents": {    "mp-icon": "./weui-miniprogram/icon/icon"  }}

mp-icon是完全可以自定义的。WeUI组件库默认以mp开头。

最后,在wxml页面中使用组件:

代码语言:javascript
复制
<mp-icon icon="arrow"></mp-icon>

icon属性是图标类型。具体有哪些类型可以使用,可以在这个网址查看:

代码语言:javascript
复制
developers.weixin.qq.com/miniprogram/dev/extended/weui/icon.html

这个页面上有一个icon列表,列表里的图标名称都可以使用。

注意:mp-icon的颜色不能从父组件直接继承,所以即使父组件已经设置了颜色,这个组件也需要额外通过color属性再设置一次。还有,原生icon组件控制类型的属性名称是type,但是这个mp-icon,控制类型的却是icon

阶段源码

源码链接:git.weixin.qq.com/rxyk/weapp-practice/repository/archive.zip?ref=2.3-scroll-view

与本文相关的代码主要位于:miniprogram/pages/2.3/scroll-view

这篇文章是「微信小程序开发实践2020」专栏的一部分,更多内容可以查看原文链接。

好了,我是石桥码农,今天分享就到这里,今天主要讲了scroll-view这个组件,希望对你的学习有帮助。有什么问题欢迎留言,也欢迎进群讨论。

2020年4月10日

参考文献

  • WEIXIN."cover-view".[Online]Available:developers.weixin.qq.com/miniprogram/dev/component/cover-view.html(2020.04).
  • WEIXIN."movable-view".[Online]Available:developers.weixin.qq.com/miniprogram/dev/component/movable-view.html(2020.04).
  • WEIXIN."scroll-view".[Online]Available:developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html(2020.04).
  • "滚动锚定".[Online]Available:cnblogs.com/ziyunfei/p/6668101.html(2017.04).
  • "下载WeUI组件库".[Online]Available:developers.weixin.qq.com/miniprogram/dev/extended/weui/download.html
  • WXS.[Online]Available:developers.weixin.qq.com/miniprogram/dev/framework/view/WXS/
  • Element.scrollIntoView().[Online]Available:developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView
  • 「scroll-view 追加数据会自动回到顶部」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0004c47ef14280f7101ab161151800
  • 「scroll-view 开启自定义下拉刷新」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0000685ea0cd78c16ef9fd3a65b800
  • 「scroll-view ios中下拉刷新触发两次bindscrolltouppe」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0004667f0e0ad8da7de8adc9e56000?_at=1586743575247
  • 「scroll-view 组件为什么 scroll-x 不启用」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0006cae8ce02e0b29548700385b800?_at=1586763031642
  • 「微信小程序的高度和scroll-view」.[Online]Available:jianshu.com/p/198c62482afa
  • 「CSS中display:inline-block的作用」.[Online]Available:jianshu.com/p/22c4bae88566
  • 「微信小程序scroll-view详解及案例」.[Online]Available:segmentfault.com/a/1190000021223987
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 艺述论 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档