解決ios下滑動引起webview整體滾動

在作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 };

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章