如何解决移动端击穿(穿透)问题

本文深入探讨了移动端开发中常见的点击穿透bug,即关闭遮罩层时下方元素被误触。解析了问题成因,涉及touch与click事件冲突,并提供了五种解决方案,包括阻止默认事件、统一使用click事件、延迟执行、利用pointer-events属性及使用fastClick库。

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

在移动端开发的时候,我们有时候会遇到这样一个bug:点击关闭遮罩层的时候,遮罩层下面的带有点击的元素也会被触发,给人一种击穿了页面的感觉,这是为什么呢?

为了让大家更直观的看到效果,我复现了bug,并录制了一个gif。供大家参考:
在这里插入图片描述

  • 点击“打开弹框”按钮,显示遮罩层
  • 点击“关闭弹框”按钮,遮罩层消失,底下的连接被触发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mQhiZ4W0-1571897111992)(./111.gif)]

上图事例js部分代码

var show = document.getElementById('show') // 打开按钮
var mask = document.getElementById('mask') // 遮罩层
var btn = document.getElementById('btn')   // 关闭按钮

show.onclick = function () {
    mask.style.display = 'block'
}

btn.addEventListener('touchstart', function () {
    mask.style.display = 'none'
}, false)

</body>
</html>

这样问题的形成原因是什么呢?

我们先来看一段代码:(以下代码需在移动端上运行)

<div id="btn">我是一个按钮</div>
var btn = document.getElementById('btn')
btn.addEventListener('touchstart', function () {
	console.log('start')	
}, false)

btn.addEventListener('touchmove', function () {
    console.log('move')
}, false)

btn.addEventListener('touchend', function () {
    console.log('touchend')
}, false)

btn.addEventListener('click', function () {
    console.log('click')
}, false)

以上代码会出现2种运行情况

  • start ===> move ===> end
  • start ===> end ===> click

看到这里相信大家都明白了,由于「关闭弹框」按钮绑定的事件是touch,a标签是click事件,在touch事件触发后,我们弹出框的遮罩层就消失了,这时候的click事件就被a标签给捕获到了,形成了击穿的效果。

方法一、阻止默认事件

btn.addEventListener('touchend', function (e) {
    mask.style.display = 'none'
    e.preventDefault()
}, false)

在执行 touchstart 和 touchend 事件时,隐藏执行完隐藏命令后,立即阻止后续事件(推荐在touchend时,阻止后续的默认事件)

方法二、统一使用click事件

btn.addEventListener('click', function () {
    mask.style.display = 'none'
}, false)

这个方法简单,就是交互的效率没有click事件高,另外,用户在touch的时候,有可能微微滑动了一下,就会无法触发点击事件。影响用户体验。

方法三、延迟执行

btn.addEventListener('touchend', function () {
	setTimeout(function () {
        mask.style.display = 'none'  // 可以使用fadeOut动画
    }, 300)
}, false)

点击之后,我们不立即隐藏。让遮罩在350ms毫秒内淡出消失。(我为了演示方便就没有添加动画了,采用了定时器方法。)

方法四、 css属性pointer-events

click.setAttribute('style', 'pointer-events:none')
mask.style.display = 'none'
setTimeout(function () {
    click.setAttribute('style', 'pointer-events:auto')
}, 350)

这样做法是,在遮罩消失之前,先让a标签忽略点击事件,这样遮罩层的点击事件,就不会被a标签捕获到。还是等350毫秒之后,再次赋予a标签的点击能力。这个方法跟方法三原理相似,只是利用了不同的css属性而已。个人觉得方法三比较好一点。方法四有明显的2个缺点:

1、遮罩层下面可能有多个带有事件的元素,那么你需要给所有可点击元素添加pointer-events属性 然后删除。不仅容易出错,还影响性能

2、如果用户在350毫秒内点击了元素,会造成页面失效的错觉,影响体验。

方法五、fastClick库

这个库的引用方法,在我上一篇文章中已经讲到。fastClick的原理就是使用了方法一的做法。fastClick 在 touchend 阶段 调用 event.preventDefault,然后通过 document.createEvent 创建一个 MouseEvents,然后 通过 event​Target​.dispatch​Event 触发对应目标元素上绑定的 click 事件。

后来有读者提了一个fastClick的bug。在这里我贴一下解决方案的地址

在这里插入图片描述

### 如何使用 Redis 解决缓存击穿穿透、雪崩和热键问题的最佳实践 #### 处理缓存击穿 缓存击穿是指特定的热点 key 在某一时刻突然有大量的并发请求,由于这个 key 正好失效,所有的请求都会落在数据库上,给数据库带来巨大压力。为了应对这种情况: - **设置热点 Key 不过期**:对于一些非常热门的数据项,可以考虑将其 TTL 设置为永久有效,即不设定过期时间[^5]。 ```python import redis r = redis.Redis(host='localhost', port=6379, db=0) # 对于特别重要的key,设置其永不过期 r.set('hot_key', 'value', nx=True, ex=None) ``` #### 应对缓存穿透 缓存穿透指的是查询一个既不在缓存也不在存储系统中存在的数据。这通常是由恶意攻击者故意制造不存在的数据请求造成的。有效的防范措施包括但不限于: - **布隆过滤器**:利用布隆过滤器可以在一定程度上减少无效查询带来的性能损耗。虽然这种方法可能会引入少量误报率,但在实际应用中已经证明是非常高效的[^4]。 布隆过滤器本身不是一种直接解决问题的方法,而是作为前置条件判断机制来辅助处理。 - **返回空对象**:当发现某条记录确实不存在时,在缓存里写入一条特殊的标记(比如 `null` 或其他约定好的值),并给予较短的有效期限,从而阻止后续相同请求直达数据库。 ```python def get_data_from_cache_or_db(key): value = r.get(key) if not value: result = fetch_from_database(key) if result is None or result == "": # 数据库中无此数据 r.setex(key, timedelta(seconds=60), "") # 存储空字符串一段时间 else: r.setex(key, timedelta(hours=1), result) # 正常情况下的缓存策略 return result return value.decode() ``` #### 抵御缓存雪崩 为了避免因大量缓存同时到期而导致的服务不可用现象发生,可采取如下手段: - **随机化过期时间**:通过为不同类型的缓存添加不同的过期间隔或者在同一类别的基础上增加一定的随机偏移量,使得各个缓存不会集中在同一时间段内全部失效。 ```python for item in items_to_cache: ttl_with_jitter = base_ttl + random.randint(-jitter_range, jitter_range) r.setex(item['id'], timedelta(seconds=ttl_with_jitter), json.dumps(item)) ``` - **提前加载/预热缓存**:定期执行批量读取操作,预先填充即将过期的重要资源至缓存层,减轻高峰期到来时的压力。 #### 控制热键访问 面对高频率访问某些固定几个 keys 的场景,除了上述提到的一些通用方法外,还可以采用限流算法控制单个 API 接口每秒允许的最大请求数目,以及分布式锁技术确保多实例环境下的一致性和安全性。 ```python from functools import wraps from time import sleep class RateLimiter(object): def __init__(self, rate_limit_per_second): self.rate_limit_per_second = rate_limit_per_second self._last_call_time = {} def limit(self, func): @wraps(func) def wrapper(*args, **kwargs): now = int(time.time()) caller_id = threading.current_thread().ident last_called_at = self._last_call_time.get(caller_id) if (not last_called_at) or ((now - last_called_at) >= 1/self.rate_limit_per_second): self._last_call_time[caller_id] = now try: response = func(*args, **kwargs) return response finally: pass else: raise TooManyRequestsError("Too many requests") return wrapper @RateLimiter(rate_limit_per_second=1).limit def handle_hotspot_request(): ... ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值