在作flutter內webview內嵌h5的時候,遇到了在ios下滑動頁面會出現橡皮筋效果,而在android下不會出現該問題。這樣的話在ios下滑動會意外的觸發會感覺頁面整體在上下彈動。
方案1:在移動端h5中我們可以監聽touchstart(觸摸開始),touchmove(觸摸移動),touchend(觸摸結束)等觸摸事件,來判斷當前是否該滑動。
方案2: 若是引入的第三方滾動組件,大多數第三方組件都是使用transform來實現滾動的,所以只需要設置css: touch-action:none;則不會觸發觸摸。
我這裏使用的webview自帶的瀏覽器滾動條來實現的原生滾動,目前使用起還是比較順手。
- 思路
最主要的就是我會遞歸去找我的滾動容器,看二級滾動容器是否大於一級滾動容器。若大於則肯定存在會出現滾動,若小於則在頁面內並不需要滾動。 - mixin一個原生滾動條(並隱藏滾動條)
// 局部使用瀏覽器自帶滾動條
@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>
- touchstart記錄起始點
Touch._startX = e.touches[0].pageX;
Touch._startY = e.touches[0].pageY;
- touchmove記錄移動點位
在touchmove中判斷不在touchend中判斷是爲了短時間可以一直移動屏幕,而不擡起手指。
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';
}
- 臨界點
當滾動距離爲0且手勢往下 需禁止
if (curNodeParent.scrollTop === 0 && finalDirction === 'down') {
// 在最頂層且往下滑動 禁止
return false;
}
當滾動到底且手勢往上 需禁止
if (
Math.ceil(curNodeParent.clientHeight + curNodeParent.scrollTop) ==
curNodeParent.scrollHeight &&
finalDirction === 'up'
) {
// 在最下面且往往上滑動 禁止
return false;
}
- 遞歸邏輯
我會以當前點擊位置起開始找尋它的parentNode並且判斷當前parentNode是否是我的滾動組件的一級滾動容器,若不是則繼續找尋,若是,則通過firstElementChild獲取當前二級滾動容器的高度,並判斷是否大於一級容器;
還有一種情況就是當我頁面內不存在滾動容器則此時若parentNode找到body標籤就退出了。
_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);
}
}
- 使用
在app.vue中引入事件監聽
// 在頂層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才走邏輯若是安卓機則直接跳過,這樣更合理些。
- touch.js
/**
* @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 };