「轻阅读」移动端事件穿透的原理与解决方案

双子孤狼 2020-07-24 13:46:54 ⋅ 94 阅读

原文:https://www.cnblogs.com/jofun/archive/2020/07/24/13371431.html

移动设备的流行,带动了移动互联网的快速发展,很多开发者开始进入移动开发领域。目前市面上主流的移动设备一般都使用触摸屏,触摸屏所使用的触摸事件模型与传统网页的鼠标事件模型有所区别,这种差异往往使初涉移动端的开发工程师陷入困境,事件穿透问题便是其中一个,本文将带你了解事件穿透及如何在实际项目中选择合适的方案解决事件穿透问题。

产生的原因

当今,主流的移动设备一般都使用触摸屏,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 事件的方法。




全部评论: 0

    我有话说:

    阅读移动适配必须掌握基本概念和适配方案

    随着技术发展,移动设备越来越流行,并且不同设备间屏幕尺寸和屏幕像素差异,移动开发面临着多分辨率适配问题。

    阅读」如何设计移动屏适配方案

    在众多移动设备中,前端开发人员如何在不同屏幕大小,不同程度高清屏下去百分百还原设计稿,从来都不

    阅读」分布式事务四种解决方案,成长需要尝试

    分布式事务事务操作位于不同节点上,需要保证事务 AICD 特性。

    阅读」亿级用户分布式数据存储解决方案

    分布式数据库和分布式存储是分布式系统中难度最大、挑战最大,也是最容易出问题地方。互联网公司只有解决分布式数据存储问题,才能支撑更多次亿级用户涌入。

    阅读」京东商城交易系统演进之路

    原文:https://www.toutiao.com/i6762874634867048963商城服务如图所

    阅读」推荐系统中信息增强小技巧

    实用推荐系统构建经验,如何进行信息增强。

    阅读」如何构建可伸缩Web应用?

    可伸缩性已经成为Web应用程序DNA!

    阅读」轻松理解 Kubernetes 核心概念

    Kubernetes 迅速成为云环境中软件部署和管理新标准。

    阅读」大众点评是如何分表分库

    原大众点评订单单表早就已经突破两百G,由于查询维度较多,即使加了两个从库,优化索引,仍然存在很多查询不理想

    阅读」“做完”和“做好”区别

    在工作中,“做完”和“做好”虽然仅一字之差,但前者只是完成了某项工作,而后者则不仅是完成了工作还有一个好

    阅读」为什么在做微服务设计时候需要DDD?

    设计蓝图里为什么没有看到DDD影子呢?

    阅读】为什么越来越多系统在做服务化?

    脱离业务实际情况架构都是耍流氓,所以不是所有系统都必须服务化,也不要为了服务化而服务化。

    阅读」Mysql调优你不得不知细节

    多数时候数据库会成为整个系统瓶颈

    阅读」图文并茂带你了解分布式架构演进

    初始阶段架构初始阶段 小型系统 应用程序、数据库、文件等所有资源都在一台服务器上通俗称LAMP

    阅读」Dubbo 如何成为连接异构微服务体系最佳服务开发框架

    要实现异构微服务体系间共存或迁移,关键点在打通异构体系间协议服务发现,得益于 Dubbo 自身对多协议、多注册模型支持

    阅读」美团开源QPS压测结果近5w/s分布式ID生成器leaf调试实战

    大型互联网项目ID要保证全局唯一,一般不在用数据库自带id自增了,一般都会用分布式id生成器。

    滴滴开源基于 React 移动开发组件库-Pile.js

    Pile.js 是滴滴开发基于 React 移动开发组件库。 量,易用,包含 52 个交互功能,支持多语言自定义皮肤。可以非常轻松创建用户交互界面,让前端开发更专注于业务逻辑实现。