IOS端 vux中scroll滚动自动回弹到顶部或者左侧的解决办法
问题表象
在滑动的时候,偶发性出现自动回弹到顶部的现象,通过onScroll回调发现scrollTop为-1造成回弹,开始寻找为什么scrollTop为-1;
排查问题
1.官网查看例子,发现例子也有,初步断定并不是业务代码的问题
2.查看node_modules文件中的vux的scroll组件
3.发现引用的是node_modules/vux-xscroll组件,找到对应组件
//...
import XScroll from 'vux-xscroll/build/cmd/xscroll.js'
//...
4.找到SimuScroll组件,分析代码,重点查看手势结束事件,因为是松开手指才滚动回去的,最终使用的是SimuScroll库,一个原生滚动,一个是模拟的,
原生滚动应该说的overflow:auto的滚动条,模拟的那个是利用手势加上css3的transform来实现的。
//...
var Util = require('./util'),
Base = require('./base'),
Timer = require('./timer'),
Animate = require('./animate'),
Hammer = require('./hammer'),
SimuScroll = require('./simulate-scroll'),
OriginScroll = require('./origin-scroll');
var XScroll = function(cfg) {
//最终使用的是SimuScroll库,一个原生滚动,一个是模拟的
var _ = cfg && cfg.useOriginScroll ? OriginScroll : SimuScroll;
return new _(cfg);
}
//...
5.关注boundryCheckY函数,因为是滚动完才触发的回弹,估计是边界检测出的问题
function _onpanend(e) {
var self = this;
var userConfig = self.userConfig;
var transX = self.computeScroll("x", e.velocityX);
var transY = self.computeScroll("y", e.velocityY);
var scrollLeft = transX ? transX.pos : 0;
var scrollTop = transY ? transY.pos : 0;
var duration;
if (transX && transY && transX.status == "inside" && transY.status == "inside" && transX.duration && transY.duration) {
//ensure the same duration
duration = Math.max(transX.duration, transY.duration);
}
transX && self.scrollLeft(scrollLeft, duration || transX.duration, transX.easing, function(e) {
self.boundryCheckX();//重点关注对象
});
transY && self.scrollTop(scrollTop, duration || transY.duration, transY.easing, function(e) {
self.boundryCheckY();//重点关注对象
});
//judge the direction
self.directionX = e.velocityX < 0 ? "left" : "right";
self.directionY = e.velocityY < 0 ? "up" : "down";
//clear start
self.__topstart = null;
self.__leftstart = null;
return self;
}
6.关注isBoundryOutTop和isBoundryOutBottom函数,一个是判断顶部是否越界,一个是顶部是否越界
function boundryCheckY(duration, easing, callback) {
var self = this;
if (!self.userConfig.boundryCheck) return;
if (typeof arguments[0] == "function") {
callback = arguments[0];
duration = self.userConfig.BOUNDRY_CHECK_DURATION;
easing = self.userConfig.BOUNDRY_CHECK_EASING;
} else {
duration = duration === 0 ? 0 : self.userConfig.BOUNDRY_CHECK_DURATION,
easing = easing || self.userConfig.BOUNDRY_CHECK_EASING;
}
if (!self.userConfig.boundryCheck || self.userConfig.lockY) return;
var boundry = self.boundry;
if (self.isBoundryOutTop()) {
self.scrollTop(-boundry.top, duration, easing, callback);
} else if (self.isBoundryOutBottom()) {
self.scrollTop(self.containerHeight - boundry.bottom, duration, easing, callback);
}
return self;
}
7.isBoundryOutTop调用的getBoundryOutTop,getBoundryOutTop调用了getScrollTop,所有只需要看getScrollTop函数即可
function isBoundryOutTop() {
return this.getBoundryOutTop() > 0 ? true : false;
}
function getBoundryOutTop() {
return -this.boundry.top - this.getScrollTop();
}
function getScrollTop() {
var transY = window.getComputedStyle(this.container)[transform].match(/[-\d\.*\d*]+/g);
//alert(window.getComputedStyle(this.container)[transform])
//alert(transY[5])
//alert(transY)
return transY ? Math.round(transY[5]) === 0 ? 0 : -Math.round(transY[5]) : 0;
}
8.alert上面三个关键值,发现是正则写错了,作者是想把数字分割,但没有考虑到科学计数法写法
//
console.log("0,1,2".match(/[-\d\.*\d*]+/g));// ["0", "1", "2"]
console.log("1e100,-1.1e-17,0,1,2".match(/[-\d\.*\d*]+/g));//["1", "100", "-1.1", "-17", "0", "1", "2"]
解决方案
/[-\d\.*\d*]+/g
改为 /[-\d.+e]+/g
优化了一下:中括号内点不用转移,*号并不作为一个数字该有的符合,\d出现了两次可以去掉一个,增加了e和正号
并不能直接更改里面的代码,虽然作者已经不维护了,可以在main.js或者其他初始化的地方增加以下代码(这代码不行,生产翻车了)
try {
const scrollPrototype = require('vux-xscroll/build/cmd/simulate-scroll.js').prototype;
const Util = require('vux-xscroll/build/cmd/util.js');
const transform = Util.prefixStyle("transform");
const fnGetScrollTopStr = scrollPrototype.getScrollTop.toString();
const fnGetScrollLeftStr = scrollPrototype.getScrollLeft.toString();
scrollPrototype.getScrollTop=function () {
//此代码有问题,上版本后翻车了,大家引以为鉴
return new Function("transform","scrollPrototype","return ("+fnGetScrollTopStr.replace(/\*\\d\*/,"+e")+").call(this)").call(this,transform)
};
scrollPrototype.getScrollLeft=function () {
return new Function("transform","scrollPrototype","return ("+fnGetScrollLeftStr.replace(/\*\\d\*/,"+e")+").call(this)").call(this,transform)
};
}catch (e) {
}
new Function翻车了,测试环境没一点问题,上了生产就凉了,代码会报错,原因是测试环境并不压缩代码中的变量定义,你定义的transform还是transform
但是生产环境可能就压缩成了单个字母,所以new Function不可取,建议还是常规操作即可
try {
const scrollPrototype = require('vux-xscroll/build/cmd/simulate-scroll.js').prototype;
const Util = require('vux-xscroll/build/cmd/util.js');
const transform = Util.prefixStyle("transform");
scrollPrototype.getScrollTop=function () {
var transY = window.getComputedStyle(this.container)[transform].match(/[-\d.+e]+/g);
return transY ? Math.round(transY[5]) === 0 ? 0 : -Math.round(transY[5]) : 0;
};
scrollPrototype.getScrollLeft=function () {
var transX = window.getComputedStyle(this.content)[transform].match(/[-\d.+e]+/g);
return transX ? Math.round(transX[4]) === 0 ? 0 : -Math.round(transX[4]) : 0;
};
}catch (e) {
}
问题解决