微信小程序之ScrollView
最近在做小程序的开发,在使用滚动视图ScrollView组件的时候,遇到了很多的小问题,在这里总结一波ScrollView的常见的一些使用方法和小问题;
首先先搭建一个最基础简单的ScrollView页面(我这里使用的是Taro框架);
搭建最基础的ScrollView
在这一步,没有太多要注意的点,按照官网的介绍来就可以;
如果是竖向滚动的话,一定要给ScrollView一个确定的高度,我这里设置的是500rpx;如果不设置高度的话,会出现不滚动或者滚动错误的现象;
ts代码:
import { ScrollView, View } from '@tarojs/components';
import { useEffect, useState } from 'react';
import styles from './index.module.styl';
const ScrollPage = props => {
const [sList, setSList] = useState([]);
useEffect(() => {
const arr: any = [];
for (let i = 0; i <= 10; i++) {
arr.push({ title: `item${i}`, id: i });
}
setSList(arr);
}, []);
return (
<View className={styles.scrollWrapper}>
<View className={styles.title}>滚动视图:</View>
<ScrollView className={styles.scrollView} scrollY>
{sList?.map((item: any) => (
<View key={item.id} className={styles.scrollitem}>
{item.title}
</View>
))}
</ScrollView>
</View>
);
};
export default ScrollPage;
样式代码:
.scrollWrapper {
height: 100vh
padding: 30px
box-sizing: border-box
background-color: #829397
.title {
margin-bottom: 30px
}
.scrollView {
height: 500px
background-color: #bed7dd
.scrollitem {
height: 80px;
border: 1px solid black
}
}
}
效果图:
下面就开始进入打怪升级的道路了,毕竟我们在开发中,ScrollView是非常频繁使用的,而且为了满足需求还要我们来花式使用ScrollView;
1. ScrollView的高度自适应问题
我遇到的第一个问题就是,根据UI的页面布局,我们的滚动视图的高度可能没办法给一个确定的高度,而且考虑到各种机型的响应式布局,高度也是不建议设成一个固定的值的;
假设我们现在要实现的页面布局是页面的上面是一部分文本模块,但是高度不固定,底部是一个吸底按钮,中间是滚动区域;可以实现的方式肯定有很多种;
1. 利用flex布局实现
而且看到底部的固定按钮,我们通常第一反应就是用position: fixed定位来做,但是在小程序中fixed固定定位也是有一定小问题的,这里我们先不提;
我们可以利用ScrollView来做中间固定的滚动区域,整个页面的高度就设为手机的一屏的高度,然后利用flex弹性布局,(flex布局真的是YYDS!!!),给ScrollView的外层包一层,然后ScrollView的高度设为父元素的100%;
直接上代码:
ts代码:(注意看布局)
<View className={styles.scrollWrapper}>
<View className={styles.header}>
<View className={styles.title}>滚动视图:</View>
</View>
<View className={styles.content}>
<ScrollView className={styles.scrollView} scrollY enableFlex>
{sList?.map((item: any) => (
<View key={item.id} className={styles.scrollitem}>
{item.title}
</View>
))}
</ScrollView>
</View>
<View className={styles.footer}>
<Button className={styles.footerBtn}>底部固定按钮</Button>
</View>
</View>
css代码:
.scrollWrapper {
height: 100vh
display: flex
flex-direction: column
overflow: hidden
box-sizing: border-box
background-image: linear-gradient(0deg, rgba(0, 17, 255, 0.18), rgba(47, 255, 0, 0.08))
.header {
font-family: PingFangSC-Medium, PingFang SC
font-size: 36px
line-height: 48px
padding: 30px
}
.content {
flex: 1 // 重点
overflow: scroll // 重点
.scrollView {
// height: 600px
height: 100% // 重点 将具体的高度改成百分比
background: #ffffff
.scrollitem {
height: 80px;
margin: 20px 30px
padding: 20px
box-sizing: border-box
background-color: #bed7dd
border-radius: 20px
border: 1px solid black
}
}
}
.footer {
padding: 16px
border-top-left-radius: 20px
border-top-right-radius: 20px
.footerBtn {
width: 80%
background-image: linear-gradient(90deg, rgba(98, 0, 255, 0.84), rgba(0, 178, 255, 0.95))
color: #ffffff
font-family: PingFangSC-Medium, PingFang SC
}
}
.title {
margin-bottom: 30px
}
}
页面效果:
2. 手动计算当前ScrollView的高度
其实利用flex布局已经可以解决问题了,但是,有时候,我们使用了一些UI框架的时候,ScrollView的父元素就变的没那么好控制,这时候,在通过改UI框架提供的组件的样式,一点点去尝试就会比较痛苦,还不如采取最原始的方式,自己动手计算配合内敛样式来做;
在Taro中,主要是使用到了以下API:
- Taro.getSystemInfoSync(): 主要用来获取屏幕的高度;
- Taro.createSelectorQuery(): 主要用来获取页面的节点信息;
使用Taro.createSelectorQuery()要注意一点,我们想要获取页面的某个节点信息,首先这个页面得渲染完之后我们才能获取到,所以如果是一进入这个页面的时候,我们就需要计算,就需要借助Taro.nextTick;
直接上代码了:
ts代码:
import { Button, ScrollView, View } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useEffect, useState } from 'react';
import styles from './index.module.styl';
const ScrollPage = props => {
const [sList, setSList] = useState([]);
const [scrollHeight, setScrollHeight] = useState(0);
useEffect(() => {
const arr: any = [];
for (let i = 0; i <= 20; i++) {
arr.push({ title: `item${i}`, id: i });
}
setSList(arr);
Taro.nextTick(() => {
// 延迟一个时间片进行
calcScrollHeight();
});
}, []);
const calcScrollHeight = () => {
const { windowHeight } = Taro.getSystemInfoSync();
console.log(windowHeight);
const query = Taro.createSelectorQuery();
let topHeight = 0;
let bottomHeight = 0;
query.select('.header').boundingClientRect(res => {
// 这里的res返回的事当前节点的信息
console.log('header----->', res);
const { height } = res;
topHeight = height;
});
query.select('.footer').boundingClientRect(res => {
console.log('footer---->', res);
const { height } = res;
bottomHeight = height;
});
query.exec(res => {
// 在这里才会执行以上获取节点的信息
// 这里的res是我们所获取的所有节点信息的数组,我一般喜欢在上面获取单个节点的时候取值
// 屏幕的可视高度 - 头部高度 - 底部高度
let scrollH = windowHeight - topHeight - bottomHeight;
setScrollHeight(scrollH);
});
};
return (
<View className={styles.scrollWrapper}>
<View className={['header', styles.header].join(' ')}>
<View className={styles.title}>滚动视图:</View>
</View>
<ScrollView
className={styles.scrollView}
scrollY
enableFlex
style={{ height: `${scrollHeight}px` }}>
{sList?.map((item: any) => (
<View key={item.id} className={styles.scrollitem}>
{item.title}
</View>
))}
</ScrollView>
<View className={['footer', styles.footer].join(' ')}>
<Button className={styles.footerBtn}>底部固定按钮</Button>
</View>
</View>
);
};
export default ScrollPage;
css代码:
.scrollWrapper {
height: 100vh
background-image: linear-gradient(0deg, rgba(0, 17, 255, 0.18), rgba(47, 255, 0, 0.08))
.header {
font-family: PingFangSC-Medium, PingFang SC
font-size: 36px
line-height: 48px
padding: 30px
}
.scrollView {
height: 600px
background: #ffffff
.scrollitem {
height: 80px;
margin: 20px 30px
padding: 20px
box-sizing: border-box
background-color: #bed7dd
border-radius: 20px
border: 1px solid black
}
}
.footer {
padding: 16px
border-top-left-radius: 20px
border-top-right-radius: 20px
.footerBtn {
width: 80%
background-image: linear-gradient(90deg, rgba(98, 0, 255, 0.84), rgba(0, 178, 255, 0.95))
color: #ffffff
font-family: PingFangSC-Medium, PingFang SC
}
}
.title {
margin-bottom: 30px
}
}
重点在于ts的代码,实现效果跟上面那种方式是一样的;
3. flex布局之下拉刷新触发不了的问题
问题背景:这个问题的暴漏是布局结合了以上两种,就是我页面上的scroll View的布局是采用flex布局,但是包含ScrollView的容器的高度不是100vh而是需要计算,这个时候突然发现ScrollView不能下拉刷新了;
在调试过程中,把ScrollView的高度设置为具体值,就没有这个问题了,也就证明我的ScrollView写的是没问题的,问题应该出在容器高度上;
在第二种方案中也提到计算某个元素的高度首先得先有这个元素,问题就出在这,但是具体这个原因不知道怎么去解释;
总之,我在计算ScrollView的外层容器的高度时,先赋一个差不多的默认值(可以通过打印获得一个大概的值),反正之后计算的值会将这个值覆盖掉,我们的页面布局是正确的,这个时候发现ScrollView可以正常使用了;
我觉得跟react的diff页面渲染有关,但是不知道怎么去解释清楚!
2. ScrollView自定义下拉刷新
在使用ScrollView的时候,还有一个频繁使用的场景,就是上拉加载/下拉刷新的需求,而且ScrollView自己本身的自定义下拉刷新的样式大多数情况也是不满足UI的需求的;
1. 默认样式的下拉刷新
先搭建一个默认的下拉刷新的ScrollView,主要涉及到的属性:
- refresherEnabled
- refresherThreshold
- refresherDefaultStyle
- refresherBackground
- refresherTriggered
- onRefresherRefresh
具体含义自行查看官方文档;
ts代码:
import React, { useEffect, useState } from "react"
import { ScrollView, View } from "@tarojs/components"
\
\
import './index.scss'
\
const Scroll = () => {
\
const [sList, setSList] = useState([]);
// 是否下拉刷新
const [isRefresh, setIsRefresh] = useState(false)
\
useEffect(() => {
const arr: any = [];
for (let i = 0; i <= 15; i++) {
arr.push({ title: `item${i}`, id: i });
}
setSList(arr);
}, []);
\
const handleRefresh = () => {
console.log('下拉刷新');
setIsRefresh(true)
setTimeout(() => {
setIsRefresh(false)
}, 500)
}
\
return (
<View className='scroll-wrapper'>
<ScrollView
className='scroll-content'
scrollY
refresherEnabled
refresherThreshold={50}
refresherDefaultStyle='black'
refresherBackground='#c7d1ff'
refresherTriggered={isRefresh}
onRefresherRefresh={handleRefresh}
>
{sList?.map((item: any) => (
<View key={item.id} className='scroll-item'>
{item.title}
</View>
))}
</ScrollView>
</View>
)
}
export default Scroll
css代码:
.scroll-wrapper {
height: 100vh;
box-sizing: border-box;
padding: 24px;
overflow: hidden;
.scroll-content {
height: 100%;
.scroll-item {
height: 100px;
line-height: 100px;
margin-bottom: 24px;
border-radius: 24px;
background-image: linear-gradient(0deg, rgb(199, 209, 255), rgb(255, 199, 205));
}
}
}
\
\
页面效果:
2. 自定义样式的下拉刷新
自定义下拉刷新样式时,需要把refresherDefaultStyle属性修改为不使用默认样式,然后在将符合我们UI需求的loading样式以插槽的形式放进去;
ScrollView下拉刷新的插槽名是[refresher];
直接上代码了
ts代码:
<View className='scroll-wrapper'>
<ScrollView
className='scroll-content'
scrollY
refresherEnabled
refresherThreshold={50}
refresherDefaultStyle='none' // 不使用默认样式
refresherBackground='#fff'
refresherTriggered={isRefresh}
onRefresherRefresh={handleRefresh}
>
{/* 自定义加载动画 start*/}
<Slot name='refresher'>
<View className='refresh-loading'>
<AudioLoading />
</View>
</Slot>
{/* end */}
{sList?.map((item: any) => (
<View key={item.id} className='scroll-item'>
{item.title}
</View>
))}
</ScrollView>
</View>
css:
.refresh-loading {
width: 100vw;
height: 140px;
display: flex;
align-items: center;
justify-content: center;
}
页面效果:
3. ScrollView上拉加载
整理完了下拉刷新,那就来整理一下上拉加载,上拉加载还是比较简单的,主要在于加载数据的逻辑处理,主要涉及到的属性:
- lowerThreshold
- onScrollToLower
那就浅浅梳理一下加载数据的逻辑吧:
- 当触发上拉加载的时候,我们一般会通过lowerThreshold来设置距离底部的一个界限值,当到达界限值的时候会触发onScrollToLower方法去获取数据;
- 加载数据的时候,我们要判断一下,当前数据是不是已经加载完毕了,如果加载完毕,就不需要再发送请求了;
- 还有我们一般在加载数据的时候也会设置loading效果,而且利用loading的状态值来判断这次数据是否加载完成,如果还在加载过程中,就不要多次发送请求(相当于节流的操作);
- 在拿到返回数据时,也要注意一点,是否需要清空原有的数据,也就是区分时刷新的操作还是加载的操作;
这里用setTimeout模拟发送请求;这里只是演示了一个效果,具体逻辑没有写;
ts代码:
import React, { useEffect, useState } from "react"
import { ScrollView, Slot, View } from "@tarojs/components"
import { AudioLoading } from "./audio";
\
import './index.scss'
\
const Scroll = () => {
\
const [sList, setSList] = useState([]);
// 是否下拉刷新
const [isRefresh, setIsRefresh] = useState(false)
// 是否上拉加载
const [isLoading, setIsLoading] = useState(false)
\
useEffect(() => {
const arr: any = [];
for (let i = 0; i <= 15; i++) {
arr.push({ title: `item${i}`, id: i });
}
setSList(arr);
}, []);
\
const handleRefresh = () => {
console.log('下拉刷新');
setIsRefresh(true)
setTimeout(() => {
setIsRefresh(false)
}, 500)
}
\
const handleLoadMore = () => {
console.log('上拉加载');
setIsLoading(true)
setTimeout(() => {
setIsLoading(false)
}, 1000)
}
\
return (
<View className='scroll-wrapper'>
<ScrollView
className='scroll-content'
scrollY
refresherEnabled
refresherThreshold={50}
refresherDefaultStyle='none' // 不使用默认样式
refresherBackground='#fff'
refresherTriggered={isRefresh}
onRefresherRefresh={handleRefresh}
lowerThreshold={50}
onScrollToLower={handleLoadMore}
>
{/* 自定义加载动画 start*/}
<Slot name='refresher'>
<View className='refresh-loading'>
<AudioLoading />
</View>
</Slot>
{/* end */}
{sList?.map((item: any) => (
<View key={item.id} className='scroll-item'>
{item.title}
</View>
))}
{isLoading && <AudioLoading />}
</ScrollView>
</View>
)
}
export default Scroll
页面效果:
4. ScrollView数据少时确有滚动条
问题现象
在开发的时候,一直用比较多的测试数据,一直没有发现这个问题,本来在我们用ScrollView的时候,如果数据没有超过ScrollView的高度,这时候是不应该出现滚动条的,但是我在数据只有一两条的时候发现ScrollView也是可以滚动的;
跟你们演示一下:
是不是挺奇怪,明明只有三条数据,但是却有滚动条,刚开始一直以为是我的高度设的有问题,后来在各种百度后发现,原来是问题出在margin这!!
当时看的那篇文章找不到了,造成这个问题的原因就是在ScrollView的第一个和最后一个元素身上如果设置了margin,就是第一个元素设了margin-top,最后一个元素设了margin-bottom,就会出现这个现象;
我的解决方案就是用一个容器包住ScrollView的内容,将margin替换为padding,问题就解决了;
如果后续再遇到问题会继续补充哒!!!
转载自:https://juejin.cn/post/7142460346685456397