「前端發動機」Touchmove 禁止默認滾動的幾種方案

前言

分享一些實際開發過程當中遇到的問題和解決方案,文中若有不對之處,歡迎你們指出,共勉。!javascript

我的博客地址 🍹🍰 fe-codecss

背景

源於最近的一個移動端走馬燈需求,使用 touchmove 事件,來觸發走馬燈的動畫。可是在實際運行時發現,滑動走馬燈的時候很容易觸發頁面自身垂直方向的滾動,以下圖html

注:這裏用 overflow: auto 模擬走馬燈,只作 touchmove 的測試。前端

能夠看出,在滑動過程當中,滑動方向一旦偏向垂直方向,就會觸發頁面的垂直滾動。vue

方案

Passive event listeners

由於是 touchmove 事件觸發的垂直滾動,因此很容易就想到了經過 e.preventDefault() 來禁用事件的默認行爲,又很容易就改了代碼。java

function Touch() {
    const startTouchRef = useRef({x: 0, y: 0});
    // 保存初始位置
    function onTouchStart(e) {
        startTouchRef.current = { x: e.touches[0].pageX, y: e.touches[0].pageY };
    }
    // 限制垂直方向上的滾動
    function onTouchMove(e) {
        const y = Math.abs(e.touches[0].pageY - startTouchRef.current.y);
        const x = Math.abs(e.touches[0].pageX - startTouchRef.current.x);
        // 簡單判斷滑動方向是傾向於 y 仍是 x
        // 禁止 x 方向的默認滾動,由於 x 方向的滾動會經過 Touchmove 或者 css 動畫 實現
        if (y < x) {
            e.preventDefault();
        }
    }
    return (
        <div onTouchStart={onTouchStart} onTouchMove={onTouchMove}> // ... </div>
    )
}
複製代碼

最後很容易獲得了一個報錯。node

image.png

一我的性化的報錯,讓咱們去查看 www.chromestatus.com/features/50… 這個 url。git

1www.png

大意是說:addEventListener 有一個參數 passive 默認是 false,可是在 Chrome 56 的時候 把 touchstart 和 touchmove 改爲了默認 passive: true。這樣,touchmove 事件就不會阻塞頁面的滾動。由於在 passive: false 的狀態下,不論是否須要調用 e.preventDefault() 來阻止頁面滾動,都須要等到 touchmove 函數執行完畢,頁面纔會作出反應。github

作一個簡單的測試。mongodb

// 沒有阻止頁面滾動,僅僅是增長了事件處理的時間
function Touch() {
    const ref = useRef(null);
    function onTouchMove(e) {
        console.time();
        let index = 0;
        for (let i = 0; i< 1000000000; i++) {
            index++;
        }
        console.timeEnd();
    }
    useEffect(() => {
        ref.current.addEventListener('touchmove', onTouchMove, { passive: false });
        return () => {
            ref.current.removeEventListener('touchmove', onTouchMove, { passive: false });
        };
    }, []);
    return (
        <div > // ... </div>
    )
}
複製代碼

112.gif

每次滑動後頁面的響應明顯卡頓,由於瀏覽器須要等 touchmove 執行完才知道是否須要禁止默認滾動。而將 passive 設爲 true 後,瀏覽器將不考慮禁用默認行爲的可能性,會當即觸發頁面行爲。

固然,若是確實要阻止默認行爲,就像我以前的那個需求同樣,就須要手動設置 passive 是 false,而後正常使用 preventDefault 就好。不過,不論是哪一種方式,咱們都須要優化本身的執行代碼,儘可能減小時間代碼運行時間。不然,還會看到如下警告:

image.png

關於被動事件監聽,更多的優化是在移動端,pc 端貌似較少處理。我這裏只測試了 mousewheel,在 pc 的 Chrome 74 下,儘管設置成了 passive: true,也沒有優先觸發頁面的滾動行爲。可是,在移動端模式下,是能夠的。你們有興趣的也能夠本身測試一下。

由於 Chrome 56以上才支持 passive,因此在使用時可能須要作一下兼容性測試。代碼來自 MDN

// 若是觸發對 options 取值 passive 的狀況,說明支持 passive 屬性
var passiveSupported = false;

try {
  var options = Object.defineProperty({}, "passive", {
    get: function() {
      passiveSupported = true;
    }
  });

  window.addEventListener("test", null, options);
} catch(err) {}

someElement.addEventListener("mouseup", handleMouseUp, passiveSupported
                               ? { passive: true } : false);
複製代碼

touch-action

用於設置觸摸屏用戶如何操縱元素的區域(例如,瀏覽器內置的縮放功能)。 — MDN

這是一個 css 屬性,簡單來講,就是能夠經過 css 指定容許用戶使用的手勢操做。

  • pan-x 啓用單指水平平移手勢
  • pan-y 啓用單指垂直平移手勢
  • none 禁止操做

其餘屬性,你們能夠去 MDN 自行查閱。結合咱們的需求,使用 pan-y 只開啓垂直方向的操做,也能作到相似的效果。須要注意的是,設置 touch-action,和咱們設置 passive: false 再調用 preventDefault 效果是同樣的,不會再對容許操做方向上的滑動效果進行優化。

11122.gif

另外,這個屬性也有兼容性問題,在 Safari 上的支持效果並很差,具體查看 can i use

overflow

對於元素的禁止滾動,其實咱們給他的父元素添加 overflow: hidden 也能達到想要的效果。對於整個頁面來講,就須要給 html 標籤添加 overflow: hidden。可是,基於當前這個需求場景,由於只是但願在水平滑動時不觸發垂直方向的滾動,因此須要判斷何時設置屬性,何時移除屬性。

這裏我沒有具體去作這個測試,只是提供一種思路。

交流羣

qq前端交流羣:960807765,歡迎各類技術交流,期待你的加入;

微信羣:有須要的同窗能夠加我微信(q1324210213),我拉你入羣。

後記

若是你看到了這裏,且本文對你有一點幫助的話,但願你能夠動動小手支持一下做者,感謝🍻。文中若有不對之處,也歡迎你們指出,共勉。好了,又耽誤你們的時間了,感謝閱讀,下次再見!

感興趣的同窗能夠關注下個人公衆號 前端發動機,好玩又有料。

相關文章
相關標籤/搜索