侧边栏壁纸
  • 累计撰写 611 篇文章
  • 累计收到 0 条评论

定位与拖动 iframe

加速器之家
2024-08-24 / 0 评论 / 3 阅读 / 正在检测是否收录...

定位 iframe

在写一个划词翻译扩展 Saladict 时,有一个需求:用户选择一段文本之后,会在鼠标附近显示一些元素。

这个初看很简单,监听一个 mouseup 事件,获取 clientXclientY 就行。这也是 Saladict 前几版用的方法。

但这个方法有个缺陷:iframe 里的鼠标事件不会传到父窗口上。

解决方法也很简单,就难在把它们都联系起来。

iframe 里插入脚本

manifest.json 里,content_scripts 有个选项 all_frames,可以让脚本插入到所有的 frame 里。

{
  "content_scripts": [
    {
      "js": ["selection.js"],
      "matches": [""],
      "all_frames": true
    }
  ]
}

检测点击

现在可以检测 iframe 里的点击事件

// selection.js
document.addEventListener('mouseup', handleMouseUp)

上传坐标

当点击发生在 iframe 里时,获取的坐标是相对于 iframe 窗口的,所以把这个坐标交给上层,再加上 iframe 本身的坐标,就可以算出点击相对上层的坐标。

Chrome 里可以放心使用 postMessage

// selection.js
function handleMouseUp (evt) {
  if (window.parent === window) {
    // 到了顶层
    doAwesomeThings(evt.clientX,evt.clientY)
  } else {
    // 把坐标传上去
    window.parent.postMessage({
      msg: 'SALADICT_CLICK',
      mouseX: evt.clientX,
      mouseY: evt.clientY
    }, '*')
  }
}

计算偏移

上层怎么知道是哪个 iframe 传来坐标?很简单,message 事件里携带了 iframe 的 window,对比一下就可以。

// selection.js
window.addEventListener('message', evt => {
  if (evt.data.msg !== 'SALADICT_CLICK') { return }

  let iframe = Array.from(document.querySelectorAll('iframe'))
    .filter(f => f.contentWindow === evt.source)
    [0]
  if (!iframe) { return }

  // 计算偏移
  let pos = iframe.getBoundingClientRect()
  let mouseX = evt.data.mouseX + pos.left
  let mouseY = evt.data.mouseY + pos.top

  if (window.parent === window) {
    // 顶层
    doAwesomeThings(mouseX, mouseY)
  } else {
    // 继续上传
    window.parent.postMessage({
      msg: 'SALADICT_CLICK',
      mouseX,
      mouseY
    }, '*')
  }
})

拖动 iframe

Saladict 另外一个需求就是拖动一个 iframe 查词面板。

实现拖动的常识

实现拖动的一种常用方式就是检测 mousedown, mousemovemouseup。分别对应开始、拖动、结束。然后计算偏移值应用到 lefttop 上。

第一次实现很容易犯的一个错误就是监听元素本身的 mousemove。当然这个也可以正确计算出偏移,问题在于如果鼠标移动稍快超出了元素,拖动就卡掉了。所以应该监听全局的 mousemove 获取偏移值。

iframe 特色的拖动

iframe 的拖动同理,只是因为发生在 iframe 里的事件不能传到上层,需要手动打包一下。

iframe 部分

拖动由 iframe 里的某个元素触发,为了节省资源,在触发的时候才监听拖动和结束,并在结束的时候解绑。

在 iframe 里监听 mousemove 就是为了把偏移值传回上层,因为上层的 mousemove 事件到这里中断了。

// iframe.js
var baseMouseX, baseMouseY

$dragArea.addEventListener('mousedown', handleDragStart)

function handleDragStart (evt) {
  baseMouseX = evt.clientX
  baseMouseY = evt.clientY

  window.parent.postMessage({
    msg: 'SALADICT_DRAG_START',
    mouseX: baseMouseX,
    mouseY: baseMouseY
  }, '*')

  document.addEventListener('mouseup', handleDragEnd)
  document.addEventListener('mousemove', handleMousemove)
}

function handleMousemove (evt) {
  window.parent.postMessage({
    msg: 'SALADICT_DRAG_MOUSEMOVE',
    offsetX: evt.clientX - baseMouseX,
    offsetY: evt.clientY - baseMouseY
  }, '*')
}

function handleDragEnd () {
  window.parent.postMessage({
    msg: 'SALADICT_DRAG_END'
  }, '*')

  document.removeEventListener('mouseup', handleDragEnd)
  document.removeEventListener('mousemove', handleMousemove)
}

上层部分

主要增加了handleFrameMousemove 补上中断的偏移。

// parent.js
var pageMouseX, pageMouseY

var frameTop = 0
var frameLeft = 0
$iframe.style.top = frameTop + 'px'
$iframe.style.left = frameLeft + 'px'

window.addEventListener('message', evt => {
  const data = evt.data

  switch (data.msg) {
    case 'SALADICT_DRAG_START':
      handleDragStart(data.mouseX, data.mouseY)
      break
    case 'SALADICT_DRAG_MOUSEMOVE':
      handleFrameMousemove(data.offsetX, data.offsetY)
      break
    case 'SALADICT_DRAG_END':
      handleDragEnd()
      break
  }
})

function handleDragStart (mouseX, mouseY) {
  // 得出鼠标在上层的位置
  pageMouseX = frameLeft + mouseX
  pageMouseY = frameTop + mouseY

  document.addEventListener('mouseup', handleDragEnd)
  document.addEventListener('mousemove', handlePageMousemove)
}

function handleDragEnd () {
  document.removeEventListener('mouseup', handleDragEnd)
  document.removeEventListener('mousemove', handlePageMousemove)
}

function handleFrameMousemove (offsetX, offsetY) {
  frameTop += offsetY
  frameLeft += offsetX
  $iframe.style.top = frameTop + 'px'
  $iframe.style.left = frameLeft + 'px'

  // 更新鼠标在上层的位置,补上偏移
  pageMouseX += offsetX
  pageMouseY += offsetY
}

function handlePageMousemove (evt) {
  frameTop += evt.clientX - pageMouseX
  frameLeft += evt.clientY - pageMouseY
  $iframe.style.top = frameTop + 'px'
  $iframe.style.left = frameLeft + 'px'

  // 新位置直接可以更新
  pageMouseX = evt.clientX
  pageMouseY = evt.clientY
}

例子

这里实现了一个例子,下面的正方形 iframe 是可以拖动的:

兼容性

可以看到,这里主要就是传鼠标的坐标偏移值。所以需要兼容老浏览器的话,用繁琐的旧方式与 iframe 交流就行。如果是同域的话也可以直接从 iframe 里获取偏移。

0

评论

博主关闭了当前页面的评论