最近在写一个H5的页面,其中有一个组件使用了antd-mobile中的pullToRefresh,好家伙,车翻了!
实现功能
利用pullToRefresh组件进行上拉加载下一页数据,实现数据的不断加载。
翻车现象
在Web端Chrome中使用pullToRefresh组件时可以正常上拉加载下一页数据,使用模拟器访问APP中嵌入的H5上拉加载依旧正常,没有任何问题。但是到了真机上或者将H5直接在真机上打开连接,pullToRefresh翻车了,无法进行上拉操作(无法触发其上拉操作的函数),就离谱!
排查原因
通过各种地方的资料,最终在antd-mobile的issues中找到了大佬的解释。
问题如下:
antd-mobile的pullToRefresh组件中触发上拉的源码为isEdge 函数中的以下代码
if (direction === UP) {
return ele.scrollHeight - ele.scrollTop === ele.clientHeight;
}
咋一看没啥问题啊,但是在移动端的ele的高度是通过rem动态计算出来的,算法是:
let remHeight = 2.48 // 元素用rem表示的高度
let __fontUnit = 43.3333 // 不同分辨率下的手机1rem代表的像素
someElementHeight = remHeight * __fontUnit
最终呈现到页面上的高度因为不同手机的分辨率someElementHeight是可能出现小数的小数A,而用js代码获取到的ele.clientHeight却始终是整数B,从而导致真实someElementHeight的px值和通过js代码获取到的真实值在不同机型上存在误差。
如果A<B, 那么元素的真实设置的高度就是A(设置在style上),那么ele.scrollTop就可能比理论(元素的高度为B)值偏大,ele.scrollHeight-ele.scrollTop != ele.clientHeight,经过长时间测试发现等式左边会始终比等式右边小1!从而不会触发滑动的临界值。
结论
由于使用rem的原因,导致动态设置到元素上的高度(px)有可能出现小数,从而导致ele.scrollHeight-ele.scrollTop != ele.clientHeight,无法触发滑动临界值。
解决方法
本文仅给出自己使用的解决方法可能并非最优解,请酌情使用!
修改源码
从源码中可以看出是由于ele.scrollHeight - ele.scrollTop === ele.clientHeight这条语句判断异常导致的无法触发上拉刷新操作
故将源码改成
_this2.isEdge = function (ele, direction) {
var container = _this2.props.getScrollContainer();
if (container && container === document.body) {
// In chrome61 `document.body.scrollTop` is invalid
var scrollNode = document.scrollingElement ? document.scrollingElement : document.body;
if (direction === UP) {
return scrollNode.scrollHeight - scrollNode.scrollTop <= window.innerHeight;
}
if (direction === DOWN) {
return scrollNode.scrollTop <= 0;
}
}
if (direction === UP) {
var __height = Math.abs(ele.scrollHeight - ele.scrollTop - ele.clientHeight)// 更改后代码
return __height>=0 && __height<=1 // 更改后代码
return ele.scrollHeight - ele.scrollTop === ele.clientHeight; // 源码代码
}
if (direction === DOWN) {
return ele.scrollTop <= 0;
}
};
这里又翻车了啊,因为是第一次修改node_modules包中依赖的源码,上来就往antd-mobile源码里找,嘿,你猜咋地,还真有,咔咔咔一顿操作,重启项目一看,嗯,无事发生,妙啊!
后来问了人才知道这里只是源码,但是项目中实际引用进来的代码路径是在antd-mobile中package.json中
"main": "lib/index.js",
"module": "es/index.js",
至于是main还是module甚至是browser这个我们下次再讲或者自行研究,这里我们用的是module也就是es/index文件,找到pullToRefresh组件的来源。
export { default as PullToRefresh } from './pull-to-refresh/index';
前往es/pull-to-refresh/index.js文件
...
import RPullToRefresh from 'rmc-pull-to-refresh';
...
var PullToRefresh = function (_React$Component) {
_inherits(PullToRefresh, _React$Component);
function PullToRefresh() {
...
}
_createClass(PullToRefresh, [{
key: 'render',
value: function render() {
...
//我在这里
return React.createElement(RPullToRefresh, _extends({}, this.props, { indicator: ind }));
}
}]);
return PullToRefresh;
}(React.Component);
export default PullToRefresh;
上述源码省略了部分代码以方便阅读,从源码中可以看到创建的组件来源于RPullToRefresh即rmc-pull-to-refresh,前往rmc-pull-to-refresh,重复之前步骤查入口地址es/index,然后进入真正被引用的PullToRefresh.js文件,修改其中isEdge,重启项目,终于成功修复改问题。
解决后续
但是仅我本地修改依赖会出现项目由别人拉取时安装依赖未修改的情况出现,所以需要解决所有人拉取代码时都可以自动修改该文件的问题。
这个问题的处理方法有多个方式:
- 将组件自己封装放在项目内直接引用
- colone项目重新发布npm包,但是都比较麻烦。
- ...
这里引入了patch-package插件来对依赖进行打补丁
package.json
在package.json文件中添加以下代码
"scripts": {
...
"postinstall": "patch-package"
},
执行补丁
npx patch-package package-name(需要修改的包名)
它会自动将你本地文件与干净的npm包进行比较判断出两份代码的差异并记录下来这个补丁。会在项目中创建patches 的目录其下会有这个补丁,只需要将这个补丁上传至代码仓库,那么之后所有人拉取项目安装依赖时会自动为该依赖包打补丁,将你的修改写入依赖包中。
综上是整个翻车及所有的修复过程!