【前端】踩坑日记:拖拽图标经过iframe页面时不流畅

452 阅读4分钟

背景

故事是这样的,项目中基于现有的功能,开发了一个小图标,用于放置一些快捷的操作按钮,比如像这样的。

image.png

根据产品经理的要求,很快按高保真落地,自测通过后,就开心的交给测试的同学去验证了,嘿嘿,可以开始“摸鱼”了。

很顺利,测试同学没发现什么问题,可以正常交付验收了。故事就这样开始了。

发现问题

产品经理在测试环境随便找几个页面体验的时候,有时会感觉到拖拽图标时,有显示的不流畅现象,但我和测试的同学均没有发现。感觉很奇怪。产品经理随后就发了这样一张动图过来(出于产品保密考虑,这里的动图就“伪造”了一下!)。

drag2.gif

可以看出,拖拽图标的时候明显感觉不是很流畅。

最终,改造后效果如下,问题解决。

drag3.gif

问题分析

经过反复确认,抓取到发现场景,即:当有元素可拖拽时,在经过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的相关介绍:

image.png

在浏览器端,若不考虑老版本IE浏览器(IE10及以下),可考虑使用该样式来解决问题。

image.png

实现方法也比较简单,即:

  • 在开始拖拽前,给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
      })
    ]
  })

总结

原本以为一个简单的拖拽图标小功能,原想着是可以很快处理掉这个任务的,没成想耗费不少时间。为避免后续再踩,故在此记录一笔。有多少坑记录多少,相信以后遇到的坑会越来越少的。

相关链接

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿