实现上拉加载和下拉刷新

通过原生 JavaScript 实现移动端上拉加载和下拉刷新功能

问题

如何在移动端实现上拉加载更多和下拉刷新功能?

解答

上拉加载和下拉刷新本质上是移动端的分页交互方式,通过监听用户的滚动和触摸事件来触发数据加载。

上拉加载

上拉加载的核心是判断页面是否触底。需要理解三个关键属性:

  • scrollTop:滚动条距离顶部的距离,随滚动变化
  • clientHeight:屏幕可视区域高度,固定值
  • scrollHeight:页面内容总高度

触底判断公式:

scrollTop + clientHeight >= scrollHeight

实现代码:

let clientHeight = document.documentElement.clientHeight;
let scrollHeight = document.body.scrollHeight;
let scrollTop = document.documentElement.scrollTop;

let distance = 50; // 距离底部 50px 时触发

if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
    console.log("开始加载数据");
}

下拉刷新

下拉刷新通过监听触摸事件实现,分为三个步骤:

1. 记录初始位置

var _element = document.getElementById('refreshContainer'),
    _refreshText = document.querySelector('.refreshText'),
    _startPos = 0,
    _transitionHeight = 0;

_element.addEventListener('touchstart', function(e) {
    _startPos = e.touches[0].pageY;
    _element.style.position = 'relative';
    _element.style.transition = 'hd18w 0s';
}, false);

2. 计算滑动距离

_element.addEventListener('touchmove', function(e) {
    _transitionHeight = e.touches[0].pageY - _startPos;

    if (_transitionHeight > 0 && _transitionHeight < 60) { 
        _refreshText.innerText = '下拉刷新'; 
        _element.style.transform = 'translateY(' + _transitionHeight + 'px)';

        if (_transitionHeight > 55) {
            _refreshText.innerText = '释放更新';
        }
    }
}, false);

3. 触发刷新回调

_element.addEventListener('touchend', function(e) {
    _element.style.transition = 'hd18w 0.5s ease 1s';
    _element.style.transform = 'translateY(0px)';
    _refreshText.innerText = '更新中...';
    // 执行刷新逻辑
}, false);

使用 better-scroll 库

实际开发中推荐使用成熟的第三方库。以 better-scroll 为例:

HTML 结构

<div id="position-wrapper">
    <div>
        <p class="refresh">下拉刷新</p>
        <div class="position-list">
            <!-- 列表内容 -->
        </div>
        <p class="more">查看更多</p>
    </div>
</div>

初始化插件

import BScroll from "@better-scroll/core";
import PullDown from "@better-scroll/pull-down";
import PullUp from '@better-scroll/pull-up';

BScroll.use(PullDown);
BScroll.use(PullUp);

let pageNo = 1, pageSize = 10, dataList = [], isMore = true;

var scroll = new BScroll("#position-wrapper", {
    scrollY: true,
    click: true,
    pullUpLoad: true,
    pullDownRefresh: {
        threshold: 50,
        stop: 0
    }
});

// 监听下拉刷新
scroll.on("pullingDown", async function() {
    dataList = [];
    pageNo = 1;
    isMore = true;
    await getlist();
    scroll.finishPullDown();
    scroll.refresh();
});

// 监听上拉加载
scroll.on("pullingUp", async function() {
    if (isMore) {
        pageNo++;
        await getlist();
        scroll.finishPullUp();
        scroll.refresh();
    }
});

关键点

  • 上拉加载通过 scrollTop + clientHeight >= scrollHeight 判断触底
  • 下拉刷新需要监听 touchstarttouchmovetouchend 三个事件
  • 使用 transform: translateY() 实现元素跟随手势滑动
  • better-scroll 的 wrapper 内只能有一个子元素,且子元素高度必须大于 wrapper
  • 每次操作结束后必须调用 finishPullDown()finishPullUp(),并执行 refresh() 更新滚动区域