Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

瀑布流列表的下拉刷新和上拉加载实现(下) #20

Open
laizimo opened this issue Sep 1, 2017 · 0 comments
Open

瀑布流列表的下拉刷新和上拉加载实现(下) #20

laizimo opened this issue Sep 1, 2017 · 0 comments

Comments

@laizimo
Copy link
Owner

laizimo commented Sep 1, 2017

前言

上篇我在文章中分析了如何去实现瀑布流的布局,以及怎么使用模版去实现数据的插入到html之中。这一篇,主要讲一下三部分的实现,1.数据mock、2.下拉加载、3.上拉刷新。本次demo的地址在此,喜欢的可以给一个Star

正文

首先,我们来分析一下数据的mock部分。由于想要实现与具备后台一样的效果,我们必须使用node去实现一个mock的后台,将数据放在json中,然后使用fs模块读取里面的内容,然后根据url进行数据的检索。

一般来说,下拉加载的数据都是具备分页特性的。主要可分为data(数据段)、page(当前页码)、next(告知前端接下来是否还有数据内容)。

之后,我们通过express框架对整个后端的数据进行一下模拟。我的demo目录下,有已经构造好的json文件,一共是两份,一份是更新前的数据,一份是更新后的数据。具体实现的代码:

const Express = require('express');
const app = new Express();
const path = require('path');
const fs = require('fs');

//data part

app.get('/:id', function(req, res){
    const page = req.params.id;
    res.setHeader("Access-Control-Allow-Origin", "*");
    // console.log(page);
    fs.readFile(path.resolve(__dirname, 'mock.json'), function(err, data){
        if(err){
            throw err;
        }
        const static = JSON.parse(data).data;
        const arr = Array.from(static);
        const len = arr.length;
        const next = page - 0 + 1 <= len ? true : false;
        for(let value of arr){
            if(value.page == page){
                res.json({data: value.data, next: next});
            }
        }
    });
});

app.get('/update/:id', function(req, res){
    const page = req.params.id;
    res.setHeader('Access-Control-Allow-Origin', '*');
    fs.readFile(path.resolve(__dirname, 'update.json'), function(err, data){
        if(err){
            throw err;
        }
        const static = JSON.parse(data).data;
        const arr = Array.from(static);
        const next = page - 0 + 1 <= arr.length ? true : false;
        let result = [];
        for(let value of arr){
            if(value.page <= page){
                result = result.concat(value.data);
            }
        }
        res.json({data: result, next: next});
    });
});

app.listen(3000);

这部分主要是读取json文件中的数据,然后将它一个固定的json格式返回给前端。源码地址

之后,我么开始来实现h5的下拉加载部分。

如果是我自己写的话,我会将下拉加载分成三个步骤进行实现:

  1. 使用ajax+promise的形式,对数据进行获取,然后去使用插入模版的函数,对数据进行插入
  2. 在window上监听scroll的事件,这里需要做一些性能的优化,比如说函数防抖,或函数截流的工作。因为scroll事件在android是触发的频率比较的高,会导致事件不断的发生,但是,往往scroll事件没有必要执行的这么频繁。频繁的触发事件会导致浏览器的性能下降。所以,截流和防抖都是会减缓触发的频率。
  3. 使用函数去判断页面是否滚动到页面的底部,通过视口的高度+滚动的高度 = 页面高度的方式来进行判断。

但是,这样子的实现方式往往不是最佳的方式。最佳的方式,是类似与iscroll和better-scroll插件的方式。原理:在最外层设置一个wrapper(包),将包的大小绝对定位和内容隐藏,然后给内部的内容添加transform的动画属性,使得它可以进行上下的滚动。这样的好处是,可以实现回弹的效果。

原理图:

原理图

better-scroll在iscroll上面做了一些修缮,尤其是在触摸滚动方面吧。所以可以使用better-scroll来实现下拉加载和上拉刷新。

第一步:固定外层包的大小,使用绝对定位的方式。然后在内容的上下都添加loading图。示例:

<div id="wrapper">
        <div>
            <div class="pull-refresh" id="pull-refresh">
                <div class="arrow" id="arrow">
                        <img src="https://raw.githubusercontent.com/AlloyTeam/AlloyTouch/master/refresh/pull_refresh/asset/arrow.png" alt="arrow"><br>
                </div>
            </div>
            <div id="toploading">
                <img src="./images/200.gif" alt="loading">
            </div>
            <ul class="wrapper">
            </ul>
            <div class="bottom" id="bottom">
                <span class="line"></span>
                <span class="tip">我是底线</span>
                <span class="line"></span>
            </div>
            <div class="loading" id="loading">
                <img src="./images/200.gif" alt="loading">
            </div>
        </div>
    </div>

之后就是一些样式的设置。

第二步:初始化scroll的对象,可以通过BScroll去进行构造。然后在每次载入内容的时候都需要去刷新一下scroll,保证它对内部内容的识别是正确的。否则,你可能只能滚动一部分。(由于是ajax载入数据的,所以在刷新的时候需要一定的延时,才能保证内容的正确)

//init scroll part

_initScroll: function(){
        const _self = this;
        const wrapper = document.getElementById('wrapper')
        const options = {
                probeType: 1,
                click: true,
                scrollbar: false,
                bounduceTime: 2000
        };
       _self.scroll = new BScroll(wrapper, options);
},


//ajax part

        fetch: function(url){
            return new Promise((resolve, reject) => {
                $.ajax({
                    url: url,
                    type: 'GET',
                    dataType: 'json',
                    data: {},
                    success: function(data){
                        resolve(data);
                    },
                    error: function(){
                        reject('request timeout');
                    }
                });
            });
        },
        load: function(fn){
            const _self = this;
            const url = _self.config.base_url + _self.data.page;
            _self.operations.bShow();
            _self.scroll.refresh();
            return _self.fetch(url).then(data => {
                setTimeout(() => {
                    _self.operations.bHide();
                    _self.data.next = data.next;
                    if(!data.next){
                        _self.bottomLine();
                    }
                    _self.data.page++;
                    fn.call(_self, data.data);
                    _self.data.status = 'ready';
                }, 500);
                setTimeout(() => {
                    _self.scroll.refresh();
                }, 600);
            }, err => {
                console.log(err);
            }).catch(e => console.log(e));
        },
        update: function(fn){
            const _self = this;
            const url = _self.config.base_url + 'update/' + (_self.data.page - 1);
            _self.operations.tShow();
            _self.operations.aHide();
            return _self.fetch(url).then(data => {
                setTimeout(() => {
                    _self.operations.tHide();
                    _self.operations.aShow();
                    _self.data.next = data.next;
                    fn.call(_self, data.data);
                }, 500);
                setTimeout(() => {
                    _self.scroll.refresh();
                    _self.data.status = 'ready';
                }, 600);
            }, er => {
                console.log(err);
            }).catch(e => console.log(e));
        },

这部分主要分为三个函数fetch、load、update。它们的作用分别是fetch主要是一个发起ajax请求的函数,以promise的形式,将内容返回回来,便于后面的数据操作。load主要作用就是获取下拉加载的数据,将它加载到html中去,update是上拉刷新后的数据,将它重新替换html中列表部分的内容。这里有一个status去控制加载的状态,防止重复发送ajax请求。

第三步:就是给scroll去添加事件。主要是两个事件touchEnd和scroll事件。这里的scroll事件是当滚动列表时发生整体的滚动时才会触发的。因为我们的html结构中,wrapper内的内容除了ul列表之外头部和尾部都还具备loading的标签。

        bind: function(){
            const _self = this;
            _self.scroll.on('touchEnd', function(position){
                if(position.y < _self.scroll.maxScrollY - 20){
                    _self.more();
                }else if(position.y > 80){
                    _self.update(_self.initHtml);
                }
            });
            _self.scroll.on('scroll', function(position){
                if(position.y > 80){
                    _self.operations.up();
                }else{
                    _self.operations.down();
                }
            });
        },

然后通过后端传过来的next可以帮助我们去判断是否还有后续内容,position.y对应的就是transform动画滚动时的值。然后,去判断是否到达底部是,我们还需要的是scroll中maxScrollY这个值。其中的20只是一个阈值。

总结

这样其实一个下拉刷新和上拉加载的过程就算是完成了。后续还可以改进的地方有是图片的懒加载和js模版引擎的使用。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant