【分享】拖拽相关的一些踩坑记录

前言

  • 细阅此文章大概需要 <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 分钟 \color{red}{7分钟} </math>7分钟左右

  • 本篇中讲述了:

    1. 拖拽操作在iframe场景下的踩坑方案
    2. 拖拽后位置持久化引发问题的一些思考
  • 欢迎在评论区探讨、留言,如果认为有任何错误都还请您不吝赐教,万分感谢。希望今后能和大家共同学习、进步。

  • 下一篇会尽快更新,已经写好的文章也会在今后进行不定期的修订、更新。

  • 如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!

  • 欢迎转载,注明出处即可。


分享两个最近修复的和拖拽有关的线上问题,虽然拖拽在实现上已经属于有手就行的那一part了,但是很多边界场景还是需要我们在具体实现中多去考虑,避免遗漏一些细节上的处理。

拖拽操作在iframe场景下的踩坑方案

背景

首先页面上有一个小助手,众所周知,小助手基本上都是可以到处拖动的。

通常来说实现拖动需要:mousedown绑定给拖拽元素,mousemove和mouseup绑定给document,处理边界场景。

如果当前页面是一个正常的页面,那就能正常实现拖拽。不会有鼠标焦点丢失和边界问题。

但是如果在这个页面里展示了一个内嵌iframe页面,那么我如果在iframe区域拖动图标过快,让鼠标脱离了图标,那么图标就会停住,鼠标焦点丢失,在图标外移动鼠标,无法触发mousemove,图标不会更新位置。

而且在图标外松开鼠标,也不会触发mouseup导致mousemove没有移除,此时鼠标重新进入图标,不管鼠标是否按下图标都会跟随移动。

原因

问题说清楚了,那就来分析一下出现的原因。首先确定这个问题是由于浏览器的事件冒泡机制导致的。

正常我们在在拖动图标时,如果鼠标快速移动并超出了图标,那么鼠标的mousemove事件就会被document捕获,然后会执行我们设定好的失焦逻辑,避免交互出现异常。

但是如果当前页面展示的是一个iframe,那此时鼠标快速移动并超出了图标,鼠标的mousemove事件就会被iframe捕获,而不是document。因此,图标会出现失焦的现象。

同时,由于mouseup事件也被iframe捕获,所以当你松开鼠标按键时,图标并不知道鼠标已经松开,所以当鼠标再次回到图标上时,它仍然会保持拖动状态。

解决方案

1. 给document添加遮罩层

在mousedown事件触发时,给document添加遮罩层。这样就可以阻止iframe捕获鼠标事件。然后在mouseup事件触发时,再移除这个遮罩层。这样就可以确保在拖动过程中,所有的鼠标事件都会被正确地捕获。

javascript 复制代码
element.addEventListener('mousedown', event => {
 //.......
 // 创建遮罩层
 const mask = document.createElement('div');
 mask.style.position = 'fixed';
 mask.style.top = '0';
 mask.style.right = '0';
 mask.style.bottom = '0';
 mask.style.left = '0';
 mask.style.zIndex = '9999';
 document.body.appendChild(mask);
});

document.addEventListener('mouseup', () => {
 //.......
 // 移除遮罩层
 const mask = document.querySelector('div');
 document.body.removeChild(mask);
});

2. 使用CSS的pointer-events属性

在mousedown时,将所有iframe的pointer-events设置为none,这样iframe就不会接收到鼠标事件了。然后在mouseup时,再将pointer-events设置回auto。

javascript 复制代码
element.addEventListener('mousedown', event => { 
//....... 
// 设置所有iframe的pointer-events为none
const iframes = document.querySelectorAll('iframe')
iframes.forEach(iframe => { iframe.style.pointerEvents = 'none' }) 
})

document.addEventListener('mouseup', () => { 
//......... 
// 设置所有iframe的pointer-events为auto
const iframes = document.querySelectorAll('iframe')
iframes.forEach(iframe => { iframe.style.pointerEvents = 'auto' })
})

优缺点

  1. 遮罩层方法:这种方法的优点是不会影响到iframe内部的鼠标事件,只是在拖动过程中阻止了iframe捕获鼠标事件。缺点是需要在DOM中添加和删除元素,如果频繁拖动,可能会对性能产生影响。

  2. pointer-events方法:这种方法的优点是实现简单,不需要操作DOM。缺点是在拖动过程中,iframe内部的所有鼠标事件都会被阻止,如果iframe内部需要响应鼠标事件,这种方法就不适用。

总的来说,如果iframe不需要响应鼠标事件,或者在拖动过程中暂时阻止鼠标事件不会产生影响,那么可以选择pointer-events方法。如果iframe需要响应鼠标事件,那么应该选择遮罩层方法。


拖拽后位置持久化引发问题的一些思考

背景

同样还是拖拽,我们再来看另一个问题。助手的图标具有位置持久化的能力,依赖的是localStorage来记忆位置。

当同一个用户在同一台设备上打开页面,助手图标就会出现在上次拖动的位置。

但是最近频繁收到客诉,说有时候页面上找不到助手图标,以为是有什么权限问题。经过测试,发现只要清掉localStorage,或者使用无痕浏览器,助手图标就会重新出现在页面上。

原因

出现问题的地方似乎已经很清楚了,就是位置的缓存出现了问题,因为通过清除缓存就可以解决。但是当时仍然选择了降级方案,暂时去掉位置持久化能力,来临时解决这个问题。

既然问题已经很清晰了,为什么不修复而是选择了降级呢?那就是因为无法稳定复现,当时看了一遍相应的持久化逻辑,感觉没什么问题,流程也很清晰。而且当时十万火急,在复现的时候也并没有考虑的特别周全,无法复现,那就决定先不要浪费时间了,而是优先解决问题。

等到暂时消停了之后,终于能安静下来思考一下了,盯着大屏,我突然有了灵感。于是同时在大屏和笔记本小屏上打开了相同的页面,然后在大屏上将图标拖拽到了边界的位置,接着在小屏上刷新了一下页面。果然,图标消失了。

至此,才终于发觉,原来图标不是消失了,而是显示在了视口之外。

解决方案

知道了问题,解决起来就很简单了,我们可以在页面初始化加载图标位置的时候,动态的获取当前视口大小,从而判断缓存的位置是不是已经超出了视口。继而决定是继续使用缓存的位置还是重新计算一个位置来展示。

当然了,重新计算位置,也有很多种场景,想要优雅的解决,那就需要都考虑到:

  1. 初始化时,缓存的位置超出了视口。此时我们可以,将位置设置在靠近当前视口最近的边上。也可以直接生硬的设置一个初始位置回去。相比之下第一种体感还是比较好的,用户不会感觉很突兀。

  2. 在小屏幕上拖到边缘后,在大屏幕打开页面,初始化时,图标会展示在屏幕上一个当不当正不正的位置上,此时用户大概率会把图标拖到一边去,避免遮挡内容。对于这种情况,我们也可以通过记忆上次视口大小,从而来优化初始化时图标显示位置的逻辑。避免出现这种生硬的交互。

  3. 厌烦了一直算算算位置?这里给出一种更优雅的思路,那就是无论图标被拖到哪里,一段时间不操作后(或者失焦后)自动收起到最近的边框侧。这样不仅避免了人工穷举般处理,而且用户的体验也会更好,同时避免了烦人的内容遮挡。

相关推荐
恋猫de小郭6 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端