移動端事件穿透的原理與解決方案

来源:https://www.cnblogs.com/jofun/archive/2020/07/24/13371431.html
-Advertisement-
Play Games

移動設備的流行,帶動了移動互聯網的快速發展,很多開發者開始進入移動開發領域。目前市面上主流的移動設備一般都使用觸摸屏,觸摸屏所使用的觸摸事件模型與傳統網頁的滑鼠事件模型有所區別,這種差異往往使初涉移動端的開發工程師陷入困境,事件穿透問題便是其中一個,本文將帶你瞭解事件穿透及如何在實際項目中選擇合適的... ...


移動設備的流行,帶動了移動互聯網的快速發展,很多開發者開始進入移動開發領域。目前市面上主流的移動設備一般都使用觸摸屏,觸摸屏所使用的觸摸事件模型與傳統網頁的滑鼠事件模型有所區別,這種差異往往使初涉移動端的開發工程師陷入困境,事件穿透問題便是其中一個,本文將帶你瞭解事件穿透及如何在實際項目中選擇合適的方案解決事件穿透問題。

產生的原因

當今,主流的移動設備一般都使用觸摸屏,Web 應用程式可以使用觸摸事件(Touch Events)直接處理基於觸摸的輸入,或者應用程式可以使用可解釋的滑鼠事件以處理應用程式的輸入。使用滑鼠事件的缺點是它們不支持併發用戶輸入,而觸摸事件支持多個同時輸入(可能在觸摸面上的不同位置),從而增強用戶體驗。

觸摸事件有以下事件類型:

  • touchstart:當觸摸點放置在觸摸面上時觸發。
  • touchmove:當觸摸點沿觸摸錶面移動時觸發。
  • touchend:當觸摸點從觸摸錶面移除時觸發。
  • touchcancel:當觸摸點以實現特定的方式中斷(例如,創建的觸摸點太多)時觸發。

在很多情況下,觸摸事件和滑鼠事件會同時被觸發(目的是讓沒有對觸摸設備優化的代碼仍然可以在觸摸設備上正常工作)。如下代碼:

document.addEventListener('touchstart', () => {
  console.log('touchstart')
})

document.addEventListener('touchend', () => {
  console.log('touchend')
})

document.addEventListener('click', () => {
  console.log('click')
})

事件觸發的先後順序是:touchstart -> touchend -> click。正是由於這種 click 事件的滯後性設計為事件穿透(點擊穿透)埋下了伏筆。

什麼是事件穿透

事件穿透是指觸發某個目標元素的觸摸事件時,會同時觸發該目標元素相同位置中其他元素的滑鼠點擊事件。例如:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>事件穿透</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      div {
        width: 100vw;
        height: 100vh;
        line-height: 100vh;
        text-align: center;
      }

      .mask {
        position: fixed;
        top: 0;
        left: 0;
        background: #333;
        opacity: 0.6;
      }
    </style>
  </head>
  <body>
    <div>事件穿透</div>
    <div class="mask"></div>

    <script>
      const $div = document.querySelector("div")
      const $mask = document.querySelector(".mask")

      $mask.addEventListener('touchstart', (e) => {
        console.log('mask touchstart')
        e.target.style.display = 'none'
      })

      $div.addEventListener('click', () => {
          console.log('div click')
        })
    </script>
  </body>
</html>

由於 mask 元素觸發 touchstart 觸摸事件並立即隱藏掉自身,之後應該按先後順序觸發 mask 元素的 touchend 和 click 事件。然而,當要觸發 click 事件的時候由於 mask 元素已經隱藏掉了,於是觸發了 div 的 click 事件。

常見的事件穿透場景:

  • 目標元素觸發觸摸事件時隱藏或移除自身,對應位置元素觸發 click 事件或 a 鏈接跳轉。
  • 目標元素使用觸摸事件跳轉至新頁面,新頁面中對應位置元素觸發 click 事件或 a 鏈接跳轉。

註意:a 標簽的鏈接跳轉事件屬於 click 事件。

解決方法

市面上解決事件穿透的方法有很多,大致可以分為兩類:第一種是禁止混用 click 和 touch 兩種事件;另一種是延遲元素的隱藏或移除。

禁用 click 事件

這種方法是將頁面內所有元素的 click 事件改用 touch 事件。這種方法的好處非常明顯,既解決了 click 事件延遲造成體驗不佳的問題又解決了事件穿透的問題,但是缺點也很明顯,就是 a 標簽的鏈接跳轉的處理問題。

禁用 a 標簽的點擊事件,改用 touch 事件觸發鏈接跳轉。實現如下:

// 禁用 a 標簽的點擊事件
document.addEventListener('click', (e) => {
  const href = e.target.getAttribute('href')
  const nodeName = e.target.nodeName.toLowerCase()

  if (nodeName === 'a' && href) {
    e.preventDefault()
  }
})

// 改用 touch 事件觸發鏈接跳轉
document.addEventListener('touchstart', (e) => {
  const href = e.target.getAttribute('href')
  const nodeName = e.target.nodeName.toLowerCase()

  if (nodeName === 'a' && href) {
    const target = e.target.getAttribute('target')
    window.open(href, target || '_self')
  }
})

看似很完美,然而,當 a 標簽內包含後帶元素的時候,後代元素的 click 事件通過冒泡還是會觸發 a 標簽的跳轉。怎麼解決?使用 pointer-events 禁用 a 標簽所有後代元素的滑鼠事件:

a[href] * {
  pointer-events: none;
}

禁用 touch 事件

這種方法是將頁面內所有元素的 touch 事件改用 click 事件。事件穿透不就是由於 touch 與 click 事件存在觸發時間差造成的嗎,全部都使用 click 事件就不會有問題。然而事實真的如此美好?當然不是的,首先要解決 click 事件延遲 300ms 的問題。解決點擊事件延遲的問題可以使用以下的 CSS 代碼實現:

html {
  touch-action: manipulation;
}

這樣已經很完美了。然而,什麼是工作?工作就是不停的解決問題。當你不得不為項目添加手勢功能,增加用戶體驗的時候(比如:左滑、右滑等等各種滑),你才會意識到完全禁用 touch 事件在實際項目中是不可能的事情。這個時候怎麼辦,推到從來,全部改用 touch 事件?當然不用這麼麻煩,你可以在使用 touch 事件時通過調用 preventDefault() 阻止觸發 click 事件。例如:

const $mask = document.querySelector(".mask")

$mask.addEventListener('touchstart', (e) => {
  ...
  e.preventDefault()
})

總結

解決事件穿透還有通過設置動畫過渡延遲元素消失等方法,由於這類方法影響用戶體驗,不一一介紹。在實際項目開發中,純移動端項目優先推薦禁用 click 事件的方法,多端項目優先推薦禁用 touch 事件的方法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 不管是做自媒體還是做電商或是工作素材採集,都離不開視頻,視頻比文字更好展示出產品、細節內容,就好比我們經常看的抖音、快手、火山、皮皮蝦之類的短視頻裡面就有很多優秀的素材,相信有採集過的朋友都會遇到平臺水印這個問題吧!今天小編就教大家如何快速批量下載無水印短視頻!一起來看看吧! 打開這個哼哼貓批量去水 ...
  • 教程 Flutter瀑布流及通用列表解決方案 Canonical 在 Linux 上提供 Flutter 桌面應用支持 插件 koukicons-flutter 🍪 Colorful Icons for your Flutter App fontify Converts SVG icons to ...
  • (一)三表 用途 list列表 整齊佈局 ul先到先得,ol有序排列,還有個自定義【dl>dt>dd】 table列表 展示數據結構 【caption table>th/tr>td(thead標題 tbody數據 tfoot腳註)】 【border/cellspacing/cellpadding表格 ...
  • 在安裝vue-awesome-swiper時報錯swiper/dist/css/swiper.min.css找不到,如下如: 有的回答安裝6.0版本的話需要引入另外一個css import 'swiper/swiper-bundle.css' 但是,我替換完css 之後又一個問題出現了,vue-aw ...
  • 在選擇的元素內: append() //後 prepend() //前 在選擇的元素外: after() //後 before() //前 舉例: ...
  • 首先需要搭建一個簡單的應用 前端部分不多贅述,如果確實沒接觸過 Vue 項目,可以參考我的《Vue 爬坑之路》系列 後端服務可以參考之前的文章《Node.js 蠶食計劃(六)—— MongoDB + Koa 入門》 完整的項目地址:https://github.com/wisewrong/Test- ...
  • 字體 文本顏色:color:red;字體分類: 襯線字體serif --字體寬度各異,有襯線 --Times、Georgia、宋體 無襯線字體sans-serif --字體寬度各異,無襯線 --Helvetica、Verdana、Arial、微軟雅黑 等寬字體monospace --字體寬度一樣,一 ...
  • 示例 1: 輸入: s = "leetcode"輸出: false 示例 2: 輸入: s = "abc"輸出: true限制: 0 <= len(s) <= 100如果你不使用額外的數據結構,會很加分。 /** * @param {string} astr * @return {boolean} ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...