H5下拉刷新(解决与android微信端下拉冲突、与页面滚动冲突)

前言

因为微信公众号第三方网页的项目中有下拉刷新的需求,第一反应就是试试使用H5自己写一个,网上搜了一下确实很多前辈都写过,所以直接参考;结果使用到项目中,问题频出:

  • 问题1:android端微信网页有个下拉查看浏览器信息和网页信息的功能,我们加入的下拉刷新与之冲突,导致功能无法实现。
  • 问题2:下拉刷新与页面滚动冲突。
  • 问题3:无论在android还是ios,手势的向下滑动和左右滑动交替,导致左右滑动时触发了下拉刷新。

以下是使用的前辈代码:

实现原理

实现下拉刷新主要分为三步:

  • 监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;
  • 监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
  • 监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置。
<main>
   <p class="refreshText"></p>
    <ul id="refreshContainer">
        <li>111</li>
        <li>222</li>
       <li>333</li>
        <li>444</li>
        <li>555</li>
        ...
    </ul>
</main>
(function(window) {
    var _element = document.getElementById('refreshContainer'),
      _refreshText = document.querySelector('.refreshText'),
      _startPos = 0,
      _transitionHeight = 0;

    _element.addEventListener('touchstart', function(e) {
        console.log('初始位置:', e.touches[0].pageY);
        _startPos = e.touches[0].pageY;
        _element.style.position = 'relative';
        _element.style.transition = 'transform 0s';
    }, false);

    _element.addEventListener('touchmove', function(e) {
        console.log('当前位置:', e.touches[0].pageY);
        _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);

    _element.addEventListener('touchend', function(e) {
        _element.style.transition = 'transform 0.5s ease 1s';
        _element.style.transform = 'translateY(0px)';
        _refreshText.innerText = '更新中...';

        // todo...

    }, false);
})(window);

我开始解决以上三个问题,

问题1:android端微信网页有个下拉查看浏览器信息和网页信息的功能,我们加入的下拉刷新与之冲突,导致功能无法实现
  • 既然与微信自带的下拉刷新冲突,我的思路就是取消下拉事件的默认动作,阻止事件向外传播,自然想到了preventDefault();我高兴地在 id="refreshContainer"元素的touchstart事件、touchmove事件、touchend事件的执行函数中都加入了e.preventDefault();这样的确生效了,解决了问题1;
  • 不幸的是,又引入了新的问题,在下拉刷新内容块内的click事件失效了;把刚刚加入的e.preventDefault()注释掉,click事件是正常的;基本能确定两者冲突;思来想去,touchstart、touchend不就是一个完整的click嘛?那就把他们两个的e.preventDefault()注释掉;果然我的猜想是对的,click生效了,至此,问题1圆满解决;
问题2:下拉刷新与页面滚动冲突
  • 首先由于解决问题1,touchmove事件引入e.preventDefault(),导致touchmove取消默认动作,也就是滚动区域无法滚动,我的思路是将e.preventDefault()提升到其父元素 id="myPage"处理。监听滚动条的位置,如果位于滚动元素顶部,并且向下滑动时,触发下拉刷新,并且取消事件的默认动作;如果不是位于顶部,自然不触发下拉刷新。注:RefreshContainer-scroll是我为页面滚动元素命名的id。
 <div id="myPage">
      <p className="refreshText" hidden={hide}>{text}</p>
      <div id="refreshContainer">
        {this.props.children}
      </div>
</div>
    wrapBoxHandle = () => { // 兼容Android,解决下拉触发微信网页的下拉。
        let that = this;
        let _element = document.getElementById('myPage'),
            _startPos = 0,
            _transitionHeight = 0;
        _element.addEventListener('touchstart', function (e) {
            _startPos = e.touches[0].pageY;
        }, false);
        _element.addEventListener('touchmove', function (e) {
            _transitionHeight = e.touches[0].pageY - _startPos;
            if (that.getScrollTop() === 0 && _transitionHeight > 0) { // 位于滚动元素顶部并且向下滑动时
                e.preventDefault(); // 取消事件的默认动作。可屏蔽android端微信网页自带的拉下动作。
            }
        }, false);
    }
    getScrollTop = () => { // 解决下拉刷新与页面滚动的冲突;获取当前页滚动元素的滚动条位置,保证在滚动条位于顶部时才会
                                        //执行下拉刷新
        return document.getElementById('RefreshContainer-scroll').scrollTop;
    }
      _element.addEventListener('touchmove', function (e) {
            if (that.state.transitionWidth === 0) { // 阻止其频繁变动,保证能进入【下拉刷新】就能继续【释放更新】
                _transitionWidth = Math.abs(e.touches[0].pageX - _startX); // 防止横向滑动
            }
            _transitionHeight = e.touches[0].pageY - _startPos;

            if (that.getScrollTop() === 0 && _transitionWidth < 10
                && _transitionHeight > 0 && _transitionHeight < 60) {
                that.setState({
                    hide: false,
                    text: '下拉刷新',
                    transitionWidth: _transitionWidth // 保证能进入【下拉刷新】就能继续【释放更新】
                });
                if (_transitionHeight > 30) {
                    _element.style.transform = `translateY(30px)`;
                    that.setState({
                        text: '释放更新',
                        flage: true,
                        transitionWidth: 0 // 一个流程走完,重置
                    });
                }
            }
        }, false);
问题3:无论在android还是ios,手势的向下滑动和左右滑动交替,导致左右滑动时触发了下拉刷新
  • 我的思路是监听左右滑动的幅度,判断是左右滑动还是上下滑动,在touchmove中给变量赋值
    _transitionWidth = Math.abs(e.touches[0].pageX - _startX);
    如果 _transitionWidth < 10则我认为是在下滑(这个值自己定的,你想定20也没问题,就是判定一个幅度),否则就是左右滑动,左右滑动的话我就不触发下拉刷新的动作。

完整代码如下:

import * as React from "react";
import './index.less';

interface Props {
    reload: Function
}

export default class RefreshContainer extends React.PureComponent<Props, any> {
    constructor(props) {
        super(props);
        this.state = {
            hide: true, // 提示元素隐藏
            text: '', // 提示信息
            flage: false, // 执行刷新函数标志位
            transitionWidth: 0 // 保证能进入【下拉刷新】就能继续【释放更新】的变量
        }
    }
    componentDidMount = () => {
        this.wrapBoxHandle();
        this.refresh();
    }

    refresh = () => { // 下拉刷新功能函数
        let that = this;
        let _element = document.getElementById('refreshContainer'),
            _startPos = 0,
            _startX = 0,
            _transitionWidth = 0,
            _transitionHeight = 0;

        _element.addEventListener('touchstart', function (e) {
            _startPos = e.touches[0].pageY;
            _startX = e.touches[0].pageX;
            _element.style.position = 'relative';
            _element.style.transition = 'transform 0s';
            that.setState({
                flage: false
            });
        }, false);

        _element.addEventListener('touchmove', function (e) {
            if (that.state.transitionWidth === 0) { // 阻止其频繁变动,保证能进入【下拉刷新】就能继续【释放更新】
                _transitionWidth = Math.abs(e.touches[0].pageX - _startX); // 防止横向滑动
            }
            _transitionHeight = e.touches[0].pageY - _startPos;

            if (that.getScrollTop() === 0 && _transitionWidth < 10
                && _transitionHeight > 0 && _transitionHeight < 60) {
                that.setState({
                    hide: false,
                    text: '下拉刷新',
                    transitionWidth: _transitionWidth // 保证能进入【下拉刷新】就能继续【释放更新】
                });
                if (_transitionHeight > 30) {
                    _element.style.transform = `translateY(30px)`;
                    that.setState({
                        text: '释放更新',
                        flage: true,
                        transitionWidth: 0 // 一个流程走完,重置
                    });
                }
            }
        }, false);

        _element.addEventListener('touchend', function (e) {
            _element.style.transition = 'transform 0.5s';
            _element.style.transform = 'translateY(0px)';
            if (that.state.flage) { // 标志下滑的值达到刷新,就执行刷新函数
                that.setState({
                    text: '正在刷新...'
                });
                that.props.reload().then(() => {
                    that.setState({
                        hide: true
                    });
                });
            } else {
                that.setState({
                    hide: true
                });
            }
        }, false);
    }

    wrapBoxHandle = () => { // 兼容Android,解决下拉触发微信网页的下拉。
        let that = this;
        let _element = document.getElementById('myPage'),
            _startPos = 0,
            _transitionHeight = 0;
        _element.addEventListener('touchstart', function (e) {
            _startPos = e.touches[0].pageY;
        }, false);
        _element.addEventListener('touchmove', function (e) {
            _transitionHeight = e.touches[0].pageY - _startPos;
            if (that.getScrollTop() === 0 && _transitionHeight > 0) { // 位于滚动元素顶部并且向下滑动时
                e.preventDefault(); // 取消事件的默认动作。可屏蔽android端微信网页自带的拉下动作。
            }
        }, false);
    }

    getScrollTop = () => { // 解决下拉刷新与页面滚动的冲突;获取当前页滚动元素的滚动条位置,保证在滚动条位于顶部时才会执行下拉刷新
        return document.getElementById('RefreshContainer-scroll').scrollTop;
    }

    render() {
        const { text, hide } = this.state;

        return (
            <div id="myPage">
                <p className="refreshText" hidden={hide}>{text}</p>
                <div id="refreshContainer">
                    {this.props.children}
                </div>
            </div>
        )
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,124评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,453评论 3 404
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,386评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,136评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,142评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,593评论 1 315
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,958评论 3 429
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,944评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,477评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,512评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,639评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,227评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,971评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,397评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,550评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,203评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,713评论 2 366

推荐阅读更多精彩内容