背景
故事是这样的,项目中基于现有的功能,开发了一个小图标,用于放置一些快捷的操作按钮,比如像这样的。
根据产品经理的要求,很快按高保真落地,自测通过后,就开心的交给测试的同学去验证了,嘿嘿,可以开始“摸鱼”了。
很顺利,测试同学没发现什么问题,可以正常交付验收了。故事就这样开始了。
发现问题
产品经理在测试环境随便找几个页面体验的时候,有时会感觉到拖拽图标时,有显示的不流畅现象,但我和测试的同学均没有发现。感觉很奇怪。产品经理随后就发了这样一张动图过来(出于产品保密考虑,这里的动图就“伪造”了一下!)。
可以看出,拖拽图标的时候明显感觉不是很流畅。
最终,改造后效果如下,问题解决。
问题分析
经过反复确认,抓取到发现场景,即:当有元素可拖拽时,在经过iframe所在区域时,问题必现。
问题复现
接下来,我们通过本地构建一个简单的项目,来复现下这个问题。在该页面中,包含有一个大小为600*600px的区域,该区域通过iframe引入一个页面。在顶层页面中有一个可拖拽至任意位置的图标。大体框架如下图所示:
有兴趣的同学,可以将源代码下载下来尝试一下。
经过验证和排查,怀疑可能的原因为: 当鼠标快速经过 iframe页面时,会影响iframe页面监听鼠标事件并做出一些响应(比如js事件监听 和 CSS样式调整,如 dragging, hovering, clicking等等) 从而触发浏览器页面重绘。
解决问题
如果真的是这个原因,那我们可以想办法当我们拖拽图标经过iframe区域时,不触发iframe内的事件。 经过多方资料查询,最终选定2种解决方案:
- 方案一:在开始拖拽图标时,动态给iframe区域添加一个透明的遮罩层,在拖拽结束时,隐藏或移除遮罩层。
- 方案二:在开始拖拽图标时,给iframe动态添加一个样式:
pointer-events: none;
方案一: 【可行】动态添加遮罩层
- 在开始拖拽前,显示遮罩层
- 在拖拽结束时,隐藏或不显示遮罩层
完整的代码,可以参阅: 坑1:拖拽图标经过iframe区域时卡顿
在示例代码中,我们在 draggable中的listeners.start和end中添加相应的执行函数,显示或隐藏遮罩层,代码如下
function showMask(){
var $mask = document.querySelector('#mask')
$mask.style.display = 'block'
}
function hideMask(){
var $mask = document.querySelector('#mask')
$mask.style.display = 'none'
}
interact('.resize-drag').draggable({
listeners: {
start(event){
console.log('start', event)
showMask()
},
move: function(event) {
var x = (parseFloat(event.target.getAttribute('data-x')) || 0)
var y = (parseFloat(event.target.getAttribute('data-y')) || 0)
x += event.dx
y += event.dy
event.target.style['transform'] = 'translate(' + x + 'px, ' + y + 'px)'
event.target.setAttribute('data-x', x)
event.target.setAttribute('data-y', y)
},
end(event){
console.log('end', event)
hideMask()
}
},
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
]
})
方案二:【可行,需考虑兼容性】动态添加样式pointer-events: none
在MDN中有关于CSS pointer-events的相关介绍:
在浏览器端,若不考虑老版本IE浏览器(IE10及以下),可考虑使用该样式来解决问题。
实现方法也比较简单,即:
- 在开始拖拽前,给iframe动态添加一个样式,即:pointer-events: none
- 在拖拽结束时,移除该样式
完整的代码,可以参阅: 坑1:拖拽图标经过iframe区域时卡顿
添加一段样式代码:
.disabled-pointer-events iframe{
pointer-events: none !important;
}
添加js代码如下:
function setBoxIframeCls(cls, isAdd = true){
var $box = document.querySelector('#box')
var classList = $box.className.split(' ')
var clsIndex = classList.findIndex(item => item === cls)
if(isAdd){
if(clsIndex < 0) {
classList.push(cls)
$box.className = classList.join(' ')
}
} else {
if(clsIndex > -1){
classList.splice(clsIndex, 1)
$box.className = classList.join(' ')
}
}
}
interact('.resize-drag').draggable({
listeners: {
start(event){
console.log('start', event)
setBoxIframeCls('disabled-pointer-events')
},
move: function(event) {
var x = (parseFloat(event.target.getAttribute('data-x')) || 0)
var y = (parseFloat(event.target.getAttribute('data-y')) || 0)
x += event.dx
y += event.dy
event.target.style['transform'] = 'translate(' + x + 'px, ' + y + 'px)'
event.target.setAttribute('data-x', x)
event.target.setAttribute('data-y', y)
},
end(event){
console.log('end', event)
setBoxIframeCls('disabled-pointer-events', false)
}
},
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
]
})
总结
原本以为一个简单的拖拽图标小功能,原想着是可以很快处理掉这个任务的,没成想耗费不少时间。为避免后续再踩,故在此记录一笔。有多少坑记录多少,相信以后遇到的坑会越来越少的。
相关链接
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。