前言
因为微信公众号第三方网页的项目中有下拉刷新的需求,第一反应就是试试使用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>
)
}
}
网友评论