likes
comments
collection
share

微信小程序之ScrollView

作者站长头像
站长
· 阅读数 37

最近在做小程序的开发,在使用滚动视图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是非常频繁使用的,而且为了满足需求还要我们来花式使用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
  }
}

页面效果:

微信小程序之ScrollView

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));

}

}

}

\
\

页面效果:

微信小程序之ScrollView

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;

}

页面效果:

微信小程序之ScrollView

3. ScrollView上拉加载

整理完了下拉刷新,那就来整理一下上拉加载,上拉加载还是比较简单的,主要在于加载数据的逻辑处理,主要涉及到的属性:

  • lowerThreshold
  • onScrollToLower

那就浅浅梳理一下加载数据的逻辑吧:

  1. 当触发上拉加载的时候,我们一般会通过lowerThreshold来设置距离底部的一个界限值,当到达界限值的时候会触发onScrollToLower方法去获取数据;
  2. 加载数据的时候,我们要判断一下,当前数据是不是已经加载完毕了,如果加载完毕,就不需要再发送请求了;
  3. 还有我们一般在加载数据的时候也会设置loading效果,而且利用loading的状态值来判断这次数据是否加载完成,如果还在加载过程中,就不要多次发送请求(相当于节流的操作);
  4. 在拿到返回数据时,也要注意一点,是否需要清空原有的数据,也就是区分时刷新的操作还是加载的操作;

这里用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

页面效果:

微信小程序之ScrollView

4. ScrollView数据少时确有滚动条

问题现象

在开发的时候,一直用比较多的测试数据,一直没有发现这个问题,本来在我们用ScrollView的时候,如果数据没有超过ScrollView的高度,这时候是不应该出现滚动条的,但是我在数据只有一两条的时候发现ScrollView也是可以滚动的;

跟你们演示一下:

微信小程序之ScrollView

是不是挺奇怪,明明只有三条数据,但是却有滚动条,刚开始一直以为是我的高度设的有问题,后来在各种百度后发现,原来是问题出在margin这!!

当时看的那篇文章找不到了,造成这个问题的原因就是在ScrollView的第一个和最后一个元素身上如果设置了margin,就是第一个元素设了margin-top,最后一个元素设了margin-bottom,就会出现这个现象;

我的解决方案就是用一个容器包住ScrollView的内容,将margin替换为padding,问题就解决了;

如果后续再遇到问题会继续补充哒!!!

转载自:https://juejin.cn/post/7142460346685456397
评论
请登录