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

前言

  • 细阅此文章大概需要7分钟\color{red}{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事件触发时,再移除这个遮罩层。这样就可以确保在拖动过程中,所有的鼠标事件都会被正确地捕获。

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。

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

原文链接:https://juejin.cn/post/7354045058264006683 作者:Hypnotize

(0)
上一篇 2024年4月6日 下午4:31
下一篇 2024年4月6日 下午4:42

相关推荐

发表回复

登录后才能评论