Drag your thoughts away from your troubles... by the ears, by the heels, or any other way you can manage it.
Drag your thoughts away from your troubles... by the ears, by the heels, or any other way you can manage it.
在写一个划词翻译扩展 Saladict 时,有一个需求:用户选择一段文本之后,会在鼠标附近显示一些元素。
这个初看很简单,监听一个 mouseup
事件,获取 clientX
和 clientY
就行。这也是 Saladict 前几版用的方法。
但这个方法有个缺陷:iframe 里的鼠标事件不会传到父窗口上。
解决方法也很简单,就难在把它们都联系起来。
在 manifest.json
里,content_scripts
有个选项 all_frames
,可以让脚本插入到所有的 frame 里。
{
"content_scripts": [
{
"js": ["selection.js"],
"matches": ["<all_urls>"],
"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
}, '*')
}
})
Saladict 另外一个需求就是拖动一个 iframe 查词面板。
实现拖动的一种常用方式就是检测 mousedown
, mousemove
和 mouseup
。分别对应开始、拖动、结束。然后计算偏移值应用到 left
和 top
上。
第一次实现很容易犯的一个错误就是监听元素本身的 mousemove
。当然这个也可以正确计算出偏移,问题在于如果鼠标移动稍快超出了元素,拖动就卡掉了。所以应该监听全局的 mousemove
获取偏移值。
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 里获取偏移。
评论没有加载,检查你的局域网
Cannot load comments. Check you network.