在作flutter内webview内嵌h5的时候,遇到了在ios下滑动页面会出现橡皮筋效果,而在android下不会出现该问题。这样的话在ios下滑动会意外的触发会感觉页面整体在上下弹动。
后面继续开发中发现次方案只能解决首次手势问题,比如手指一直没离开屏幕而先向上又向下依然会出现弹性滚动,在移动过程中不能中断,目前没找到更好的办法解决
方案1:在移动端h5中我们可以监听touchstart(触摸开始),touchmove(触摸移动),touchend(触摸结束)等触摸事件,来判断当前是否该滑动。
方案2: 若是引入的第三方滚动组件,大多数第三方组件都是使用transform来实现滚动的,所以只需要设置css: touch-action:none;则不会触发触摸。
我这里使用的webview自带的浏览器滚动条来实现的原生滚动,目前使用起还是比较顺手。
// 局部使用浏览器自带滚动条
@mixin useScrollByBrowser($h: 1px) {
overflow-x: hidden;
overflow-y: auto;
height: calc(100% - #{$h});
&::-webkit-scrollbar {
display: none;
}
}
<template functional>
<!-- 创建滚动区域 滚动一级-->
<div class="area-scroll" data-scroll-root="true">
<!-- 滚动二级 主要获取其高度与滚动一级比较 -->
<div class="area-scroll-content">
<slot></slot>
</div>
</div>
</template>
Touch._startX = e.touches[0].pageX;
Touch._startY = e.touches[0].pageY;
let endX = e.touches[0].pageX;
let endY = e.touches[0].pageY;
let distanceX = endX - Touch._startX;
let distanceY = endY - Touch._startY;
let finalDirction = 'none';
if (Math.abs(distanceX) < Math.abs(distanceY) && distanceY > 0) {
finalDirction = 'down';
} else if (Math.abs(distanceX) < Math.abs(distanceY) && distanceY < 0) {
finalDirction = 'up';
}
if (curNodeParent.scrollTop === 0 && finalDirction === 'down') {
// 在最顶层且往下滑动 禁止
return false;
}
当滚动到底且手势往上 需禁止
if (
Math.ceil(curNodeParent.clientHeight + curNodeParent.scrollTop) ==
curNodeParent.scrollHeight &&
finalDirction === 'up'
) {
// 在最下面且往往上滑动 禁止
return false;
}
_findParent: (pNode, finalDirction) => {
/**
* @func 递归找上级函数
* @param 父级元素node
* @param 当前用户手势操作
*/
const curNodeParent = pNode.parentNode;
if (curNodeParent && curNodeParent.nodeName === 'BODY') {
// 证明当前点击的元素不是滚动区域内的元素
return false;
} else {
// 判断当前父级元素是否为一级滚动区域
if (
curNodeParent &&
curNodeParent.getAttribute('data-scroll-root') === 'true'
) {
// 若找到当前父级元素为一级 则需要判断其二级滚动区域高度是否大于一级父级元素
const curNodeParentHeight = curNodeParent.offsetHeight; // 当前父元素高度
const curNodeParentChild = curNodeParent.firstElementChild || null; // 当前一级滚动下属二级滚动区域
const curNodeParentChildHeight =
curNodeParentChild && curNodeParentChild.offsetHeight; // 二级滚动区域高度
if (curNodeParentChildHeight > curNodeParentHeight) {
if (curNodeParent.scrollTop === 0 && finalDirction === 'down') {
// 在最顶层且往下滑动 禁止
return false;
} else if (
Math.ceil(curNodeParent.clientHeight + curNodeParent.scrollTop) ==
curNodeParent.scrollHeight &&
finalDirction === 'up'
) {
// 在最下面且往往上滑动 禁止
return false;
} else {
// 其余情况 可滚动
return true;
}
}
} else {
// 未到body则继续往上遍历
return Touch._findParent(curNodeParent, finalDirction);
}
}
// 在顶层vue模板中引入触摸事件
import { onInitTouchEvent, onRemoveTouchEvent } from 'Utils/touch'
// script中
created() {
onInitTouchEvent()
},
destroyed() {
onRemoveTouchEvent()
}
页面内直接使用全局滚动组件
<!-- 局部滚动组件 -->
<area-scroll>
<!-- 您的业务dom -->
<ul>
<li v-for="index in 124" :key="index">{{index}}</li>
</ul>
</area-scroll>
// css
/deep/ .area-scroll {
@include useScrollByBrowser(60px);
}
其实还可以优化一点就是针对ios才走逻辑若是安卓机则直接跳过,这样更合理些。
/**
* @module 针对ios滑动带动webview触摸事件封装
* @author ljxin
* @tips Touch对象
* 私有变量 startX,startY
* 私有方法:_onMove,_onStart,_onEnd,_findParent
* 暴露方法: onInitTouchEvent onRemoveTouchEvent
*/
const Touch = {
_startX: 0, // 手指起始触摸位置x
_startY: 0, // 手指起始触摸位置y
_onMove: (e) => {
/**
* @func touchmove事件响应函数
* @param e 系统事件对象
*/
let endX = e.touches[0].pageX;
let endY = e.touches[0].pageY;
let distanceX = endX - Touch._startX;
let distanceY = endY - Touch._startY;
let finalDirction = 'none';
if (Math.abs(distanceX) < Math.abs(distanceY) && distanceY > 0) {
finalDirction = 'down';
} else if (Math.abs(distanceX) < Math.abs(distanceY) && distanceY < 0) {
finalDirction = 'up';
}
// 1. 先判断当前vue页面下是否存在可滚动区域
if (!!document.querySelector('.area-scroll')) {
// 2. 再判断点击的该节点元素是否隶属于滚动区域
const curNode = e.target;
if (Touch._findParent(curNode, finalDirction)) {
// console.log('可滚动');
return null;
}
}
// 在cancalable为true得时候 来阻止冒泡及默认事件 及一定要return 不然会报个错
if (e.cancelable) {
e.stopPropagation();
e.preventDefault();
// console.log('不能滚动');
}
return null;
},
_onStart: (e) => {
/**
* @func touchstart事件响应函数
* @param e
*/
Touch._startX = e.touches[0].pageX;
Touch._startY = e.touches[0].pageY;
},
_onEnd: (e) => {
/**
* @func touchend事件响应函数 (暂未使用)
* @param e
*/
},
_findParent: (pNode, finalDirction) => {
/**
* @func 递归找上级函数
* @param 父级元素node
* @param 当前用户手势操作
*/
const curNodeParent = pNode.parentNode;
if (curNodeParent && curNodeParent.nodeName === 'BODY') {
// 证明当前点击的元素不是滚动区域内的元素
return false;
} else {
// 判断当前父级元素是否为一级滚动区域
if (
curNodeParent &&
curNodeParent.getAttribute('data-scroll-root') === 'true'
) {
// 若找到当前父级元素为一级 则需要判断其二级滚动区域高度是否大于一级父级元素
const curNodeParentHeight = curNodeParent.offsetHeight; // 当前父元素高度
const curNodeParentChild = curNodeParent.firstElementChild || null; // 当前一级滚动下属二级滚动区域
const curNodeParentChildHeight =
curNodeParentChild && curNodeParentChild.offsetHeight; // 二级滚动区域高度
if (curNodeParentChildHeight > curNodeParentHeight) {
if (curNodeParent.scrollTop === 0 && finalDirction === 'down') {
// 在最顶层且往下滑动 禁止
return false;
} else if (
Math.ceil(curNodeParent.clientHeight + curNodeParent.scrollTop) ==
curNodeParent.scrollHeight &&
finalDirction === 'up'
) {
// 在最下面且往往上滑动 禁止
return false;
} else {
// 其余情况 可滚动
return true;
}
}
} else {
// 未到body则继续往上遍历
return Touch._findParent(curNodeParent, finalDirction);
}
}
},
};
/**
* @func 暴露给vue使用的初始化接口
*/
const onInitTouchEvent = () => {
document.body.addEventListener('touchstart', Touch._onStart, {
passive: false,
});
document.body.addEventListener('touchmove', Touch._onMove, {
passive: false,
});
document.body.addEventListener('touchend', Touch._onEnd, { passive: false });
};
/**
* @func 暴露给vue使用的卸载监听接口
*/
const onRemoveTouchEvent = () => {
document.body.removeEventListener('touchstart', () => {}, false);
document.body.removeEventListener('touchmove', () => {}, false);
document.body.removeEventListener('touchend', () => {}, false);
};
export { onInitTouchEvent, onRemoveTouchEvent };