前端穿透问题

本文探讨了移动端点击穿透问题,分析了touch事件与click事件的区别,介绍了tap事件的模拟原理及几种穿透解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文:https://segmentfault.com/a/1190000003848737 

同时自己也学习下,做一个备忘

前言

做过移动端H5页面的同学肯定知道,移动端web的事件模型不同于PC页面的事件。看了一些关于touch事件的文章,我想再来回顾下touch事件的原理,为什么通过touch可以触发click事件,touch事件是不是万能的以及它可能存在的问题。

touch事件的来源

PC网页上的大部分操作都是用鼠标的,即响应的是鼠标事件,包括mousedownmouseupmousemoveclick事件。一次点击行为,事件的触发过程为:mousedown -> mouseup -> click 三步。

手机上没有鼠标,所以就用触摸事件去实现类似的功能。touch事件包含touchstarttouchmovetouchend,注意手机上并没有tap事件。手指触发触摸事件的过程为:touchstart -> touchmove -> touchend

手机上没有鼠标,但不代表手机不能响应mouse事件(其实是借助touch去触发mouse事件)。有人在PC和手机上对事件做了对比实验,以说明手机对touch事件相应速度快于mouse事件。

可以看到在手机上,当我们手触碰屏幕时,要过300ms左右才会触发mousedown事件,所以click事件在手机上看起来就像慢半拍一样。

touch事件中可以获取以下参数

参数含义
touches屏幕中每根手指信息列表
targetTouches和touches类似,把同一节点的手指信息过滤掉
changedTouches响应当前事件的每根手指的信息列表

tap是怎么来的

用过Zepto或KISSY等移动端js库的人肯定对tap事件不陌生,我们做PC页面时绑定click,相应地手机页面就绑定tap。但原生的touch事件本身是没有tap的,js库里提供的tap事件都是模拟出来的。

我们在上面看到,手机上响应 click 事件会有300ms的延迟,那么这300ms到底是干嘛了?浏览器在 touchend 后会等待约300ms,原因是判断用户是否有双击(double tap)行为。如果没有 tap 行为,则触发 click 事件,而双击过程中就不适合触发 click 事件了。由此可以看出 click 事件触发代表一轮触摸事件的结束。

既然说tap事件是模拟出来的,我们可以看下Zepto对 singleTap 事件的处理。见源码 136-143 行,可以看出在 touchend 响应 250ms 无操作后,则触发singleTap。

点击穿透的场景

整个容器里有一个底层元素的div,和一个弹出层div,为了让弹出层有模态框的效果,我又加了一个遮罩层。

<div class="container">
    <div id="underLayer">底层元素</div> <div id="popupLayer"> <div class="layer-title">弹出层</div> <div class="layer-action"> <button class="btn" id="closePopup">关闭</button> </div> </div> </div> <div id="bgMask"></div> 

然后为底层元素绑定 click 事件,而弹出层的关闭按钮绑定 tap 事件。

$('#closePopup').on('tap', function(e){ $('#popupLayer').hide(); $('#bgMask').hide(); }); $('#underLayer').on('click', function(){ alert('underLayer clicked'); }); 

点击关闭按钮,touchend首先触发tap,弹出层和遮罩就被隐藏了。touchend后继续等待300ms发现没有其他行为了,则继续触发click,由于这时弹出层已经消失,所以当前click事件的target就在底层元素上,于是就alert内容。整个事件触发过程为 touchend -> tap -> click。

而由于click事件的滞后性(300ms),在这300ms内上层元素隐藏或消失了,下层同样位置的DOM元素触发了click事件(如果是input框则会触发focus事件),看起来就像点击的target“穿透”到下层去了。

结合Zepto源码的解释

zepto中的 tap 通过兼听绑定在 document 上的 touch 事件来完成 tap 事件的模拟的,是通过事件冒泡实现的。在点击完成时(touchstart / touchend)的 tap 事件需要冒泡到 document 上才会触发。而在冒泡到 document 之前,手指接触和离开屏幕(touchstart / touchend)是会触发 click 事件的。

因为 click 事件有延迟(大概是300ms,为了实现safari的双击事件的设计),所以在执行完 tap 事件之后,弹出层立马就隐藏了,此时 click 事件还在延迟的 300ms 之中。当 300ms 到来的时候,click 到的其实是隐藏元素下方的元素。

如果正下方的元素有绑定 click 事件,此时便会触发,如果没有绑定 click 事件的话就当没发生。如果正下方的是 input 输入框(或是 select / radio / checkbox),点击默认 focus 而弹出输入键盘,也就出现了上面的“点透”现象。

穿透的解决办法

1. 遮挡

由于 click 事件的滞后性,在这段时间内原来点击的元素消失了,于是便“穿透”了。因此我们顺着这个思路就想到,可以给元素的消失做一个fade效果,类似jQuery里的fadeOut,并设置动画duration大于300ms,这样当延迟的 click 触发时,就不会“穿透”到下方的元素了。

同样的道理,不用延时动画,我们还可以动态地在触摸位置生成一个透明的元素,这样当上层元素消失而延迟的click来到时,它点击到的是那个透明的元素,也不会“穿透”到底下。在一定的timeout后再将生成的透明元素移除。

// 遮挡物
        var $tap = $('<div></div>');
        $tap.css({
            width: '40px',
            height: '40px',
            'border-radius': '20px',
            position: 'absolute',
            opacity: '1'
        });

        $close.on('touchend', function(e){
            $popup.hide();
            $mask.hide();

            var touch = e.changedTouches[0];
            $tap.css({
                top: (touch.pageY-20) + 'px',
                left: (touch.pageX-20) + 'px'
            });

            $('body').append($tap);

            setTimeout(function(){
                $tap.remove();
            }, 400);
        });

        $under.on('click', function(){
            alert('underLayer clicked');
        });

 

2. pointer-events

pointer-events是CSS3中的属性,它有很多取值,有用的主要是autonone,其他属性值为SVG服务。

取值含义
auto效果和没有定义 pointer-events 属性相同,鼠标不会穿透当前层。
none元素不再是鼠标事件的目标,鼠标不再监听当前层而去监听下面的层中的元素。但是如果它的子元素设置了pointer-events为其它值,比如auto,鼠标还是会监听这个子元素的。

 

$('#closePopup').on('tap', function(e){ $('#popupLayer').hide(); $('#bgMask').hide(); $('#underLayer').css('pointer-events', 'none'); setTimeout(function(){ $('#underLayer').css('pointer-events', 'auto'); }, 400); }); 

3. fastclick

使用fastclick库,其实现思路是,取消 click 事件(参看源码 164-173 行),用 touchend 模拟快速点击行为(参看源码 521-610 行)。

FastClick.attach(document.body);

从此所有点击事件都使用click,不会出现“穿透”的问题,并且没有300ms的延迟。

了解更多事件问题:http://www.cnblogs.com/yexiaochai/p/3462657.html

转载于:https://www.cnblogs.com/zhaixr/p/8269204.html

<think>好的,我需要解决前端画布蒙版点击穿透问题。首先,用户提到他们想防止点击穿透,也就是当画布上有一个蒙版层时,点击事件不应该传递到底层的元素。这个问题通常发生在使用HTML5 Canvas或者WebGL渲染的图形上,因为Canvas本身的事件处理机制和DOM元素不同。 首先,我需要了解点击穿透的原因。当用户点击Canvas上的某个区域时,如果该区域在Canvas上没有对应的事件监听器,事件就会穿透到下方的DOM元素。例如,如果Canvas覆盖了一个按钮,但Canvas没有处理点击事件,点击Canvas区域就会触发按钮的点击事件,这就是点击穿透。 接下来,我需要考虑解决方案。常见的做法是在Canvas上拦截事件,阻止它们继续传播。可以通过在Canvas元素上添加事件监听器,比如`pointer-events`属性设置为`none`,但这可能不够,因为需要更精细的控制。或者,可以在Canvas上捕获事件,并根据需要决定是否阻止默认行为。 另外,用户提到的参考引用中提到了模糊混合模式,这可能与处理蒙版的视觉效果有关,但点击穿透更多是事件处理的问题,而不是视觉效果。因此,可能需要结合视觉上的蒙版和事件处理的拦截。 然后,我需要验证这些方法的正确性。例如,在Canvas上添加一个覆盖整个画布的透明层,并为其添加点击事件监听器,调用`preventDefault()`和`stopPropagation()`来阻止事件传播。同时,确保这个层的`pointer-events`属性设置为`auto`,以便能够捕获事件。 还有可能需要使用CSS的`pointer-events`属性来控制哪些元素可以接收事件。例如,将蒙版层的`pointer-events`设置为`auto`,而底层内容设置为`none`,但这可能需要调整层叠顺序和定位。 最后,总结几种可行的方案,并给出具体的代码示例,比如使用事件监听器拦截,或者结合CSS属性控制事件的接收。同时,提醒用户注意层叠上下文和事件冒泡机制的影响,确保解决方案在不同浏览器中的兼容性。</think>### 前端画布蒙版防止点击穿透解决方案 #### 1. 事件拦截法 在画布蒙版层添加透明覆盖层并拦截事件: ```html <div style="position: relative;"> <canvas id="mainCanvas"></canvas> <!-- 透明拦截层 --> <div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: auto;" onclick="event.stopPropagation()"> </div> </div> ``` **原理**:通过`pointer-events: auto`强制该层接收事件,`stopPropagation()`阻止事件向父级传递[^1]。 #### 2. CSS指针事件控制 直接通过CSS控制画布事件响应: ```css .canvas-mask { pointer-events: none; /* 禁止画布响应事件 */ } .mask-layer { pointer-events: auto; /* 允许蒙版层响应事件 */ } ``` #### 3. 动态事件阻断 在Canvas渲染循环中添加事件监听: ```javascript canvas.addEventListener('click', (e) => { if (isInMaskArea(e.clientX, e.clientY)) { e.preventDefault(); e.stopImmediatePropagation(); } }); ``` #### 4. 分层渲染方案 使用WebGL/Canvas多层渲染时: ```javascript // 创建交互层canvas const interactionCanvas = document.createElement('canvas'); interactionCanvas.style.pointerEvents = 'auto'; interactionCanvas.addEventListener('click', handleMaskClick); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值