前言
-
细阅此文章大概需要左右
-
本篇中讲述了:
- 拖拽操作在iframe场景下的踩坑方案
- 拖拽后位置持久化引发问题的一些思考
-
欢迎在评论区探讨、留言,如果认为有任何错误都还请您不吝赐教,万分感谢。希望今后能和大家共同学习、进步。
-
下一篇会尽快更新,已经写好的文章也会在今后进行不定期的修订、更新。
-
如果觉得这篇文章对您有帮助,还请点个赞支持一下,谢谢大家!
-
欢迎转载,注明出处即可。
分享两个最近修复的和拖拽有关的线上问题,虽然拖拽在实现上已经属于有手就行的那一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' })
})
优缺点
-
遮罩层方法:这种方法的优点是不会影响到iframe内部的鼠标事件,只是在拖动过程中阻止了iframe捕获鼠标事件。缺点是需要在DOM中添加和删除元素,如果频繁拖动,可能会对性能产生影响。
-
pointer-events方法:这种方法的优点是实现简单,不需要操作DOM。缺点是在拖动过程中,iframe内部的所有鼠标事件都会被阻止,如果iframe内部需要响应鼠标事件,这种方法就不适用。
总的来说,如果iframe不需要响应鼠标事件,或者在拖动过程中暂时阻止鼠标事件不会产生影响,那么可以选择pointer-events方法。如果iframe需要响应鼠标事件,那么应该选择遮罩层方法。
拖拽后位置持久化引发问题的一些思考
背景
同样还是拖拽,我们再来看另一个问题。助手的图标具有位置持久化的能力,依赖的是localStorage来记忆位置。
当同一个用户在同一台设备上打开页面,助手图标就会出现在上次拖动的位置。
但是最近频繁收到客诉,说有时候页面上找不到助手图标,以为是有什么权限问题。经过测试,发现只要清掉localStorage,或者使用无痕浏览器,助手图标就会重新出现在页面上。
原因
出现问题的地方似乎已经很清楚了,就是位置的缓存出现了问题,因为通过清除缓存就可以解决。但是当时仍然选择了降级方案,暂时去掉位置持久化能力,来临时解决这个问题。
既然问题已经很清晰了,为什么不修复而是选择了降级呢?那就是因为无法稳定复现,当时看了一遍相应的持久化逻辑,感觉没什么问题,流程也很清晰。而且当时十万火急,在复现的时候也并没有考虑的特别周全,无法复现,那就决定先不要浪费时间了,而是优先解决问题。
等到暂时消停了之后,终于能安静下来思考一下了,盯着大屏,我突然有了灵感。于是同时在大屏和笔记本小屏上打开了相同的页面,然后在大屏上将图标拖拽到了边界的位置,接着在小屏上刷新了一下页面。果然,图标消失了。
至此,才终于发觉,原来图标不是消失了,而是显示在了视口之外。
解决方案
知道了问题,解决起来就很简单了,我们可以在页面初始化加载图标位置的时候,动态的获取当前视口大小,从而判断缓存的位置是不是已经超出了视口。继而决定是继续使用缓存的位置还是重新计算一个位置来展示。
当然了,重新计算位置,也有很多种场景,想要优雅的解决,那就需要都考虑到:
-
初始化时,缓存的位置超出了视口。此时我们可以,将位置设置在靠近当前视口最近的边上。也可以直接生硬的设置一个初始位置回去。相比之下第一种体感还是比较好的,用户不会感觉很突兀。
-
在小屏幕上拖到边缘后,在大屏幕打开页面,初始化时,图标会展示在屏幕上一个当不当正不正的位置上,此时用户大概率会把图标拖到一边去,避免遮挡内容。对于这种情况,我们也可以通过记忆上次视口大小,从而来优化初始化时图标显示位置的逻辑。避免出现这种生硬的交互。
-
厌烦了一直算算算位置?这里给出一种更优雅的思路,那就是无论图标被拖到哪里,一段时间不操作后(或者失焦后)自动收起到最近的边框侧。这样不仅避免了人工穷举般处理,而且用户的体验也会更好,同时避免了烦人的内容遮挡。
原文链接:https://juejin.cn/post/7354045058264006683 作者:Hypnotize