如何解决在Shadow DOM中的setTimeout中更改了event.target?
https://jsbin.com/qogewewomi/1/edit?html,output
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<test-test></test-test>
<button id='out'>Outside Shadow DOM</button>
</body>
</html>
customElements.define('test-test',class extends HTMLElement {
constructor() {
super();
const node = document.createElement('template');
node.innerHTML = '<button id="in">Inside Shadow DOM</button>';
this.attachShadow({
mode: 'open'
}).appendChild(node.content);
this.shadowRoot.querySelector('#in').addEventListener('click',e => {
console.log(e.target);
setTimeout(() => {
console.log(e.target);
});
});
}
});
document.querySelector('#out').addEventListener('click',e => {
console.log(e.target);
setTimeout(() => {
console.log(e.target);
});
});
我在影子DOM内外的事件侦听器中发现了这些不一致的行为。单击Inside Shadow DOM
按钮后,控制台将输出:
<button id="in">Inside Shadow DOM</button>
<test-test>...</test-test>
单击Outside Shadow DOM
按钮时,控制台将输出:
<button id="out">Outside Shadow DOM</button>
<button id="out">Outside Shadow DOM</button>
在Chrome,FireFox和Safari中进行了测试。他们都有这些不一致的行为。我不知道这是预期的行为还是错误?
更新: 这个问题不应该被解决。另一个没有回答这个问题。
解决方法
这是预期的行为,不是错误。
解释将需要太多字符。
请参阅:
- event currentTarget changes after setTimeout
- https://javascript.info/shadow-dom-events
- https://dom.spec.whatwg.org/#dispatching-events
- https://developer.mozilla.org/en-US/docs/Web/API/Event/Comparison_of_Event_Targets
简而言之:
JavaScript是单线程的。
(e)Event
是在所有事件处理程序周围传递的全局对象
使用SetTimeout时,Event
的内容可以/将有所不同
我重写了您的测试代码:https://jsfiddle.net/CustomElementsExamples/wj1234km/
<shadow-element id="lightcoral" title=One></shadow-element>
<script>
function log(label,color,scope,evt) {
let composedTarget = (evt.composed && evt.composedPath());
console.log(`%c ${label} \t%c ${evt.target.id} `,`background:${color}`,`background:${evt.target.id};color:white`,'\n\ttarget:',evt.target,"\n\tthis:",scope.nodeName || "window","\n\tcurrentTarget",evt.currentTarget,'\n\tclickedTarget:',evt.clickedTarget,"\n\tcomposed:",evt.composed ? "True" : "False","composedPath[0]:",composedTarget[0]);
}
customElements.define('shadow-element',class extends HTMLElement {
constructor() {
super().attachShadow({mode:'open'})
.innerHTML = `<button id=green>click ${this.title}</button>`;
let button = this.shadowRoot.querySelector('button');
button.onclick = e => {
let savedTarget = e.target;
e.clickedTarget = e.target;
button.onclick = false; //prevent double capture
log(`clicked element:${this.id}`,'lightgreen',this,e);
setTimeout(() => {
log('timeout element','red;color:yellow',e)
},500);
};
//this.onclick = button.onclick;
}
});
</script>
要输出:
target
现在是<shadow-element>
,因为一旦setTimeout
运行 global 事件,该事件就一直沿DOM传递。
currentTarget
告诉您所有事件处理均已完成
https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget
clickedTarget
演示您可以在该全局事件对象(正在传递)上设置自定义属性。从而“保存”您单击的目标。但是其他事件(或下面的element.onclick函数调用)可能会覆盖它,因此最好在正确的范围内设置自定义变量savedTarget
,并在您的setTimeout
您可以通过在元素本身上设置点击处理程序来查看target
的变化。
target
成为事件<shadow-element>
冒出DOM并“逃离” shadowDOM的那一刻
,
不应关闭此问题。另一个没有回答这个问题。
是的,如果您仔细阅读的话。但由于社区不同意,我觉得我必须回答...
there的主要要点是,将对Shadow DOM中的元素触发的事件重定向了,以便从Light元素中隐藏Shadow DOM。这意味着Event对象在其生存期内将更改其target
。
- 首先,在捕获阶段,尽管Event对象仍未到达Shadow DOM,但Light容器(此处为
<test-test>
)将成为该Event对象的目标。 - 然后在Shadow DOM内部,它将更改为实际的Shadow目标(
<button>
)。 - 最后,在从Shadow DOM退出的冒泡阶段,它将重新设置为Light容器。
因此,当您从超时记录日志时,所有事件阶段都会发生,并且事件对象将处于最终状态,并且Light容器为.target
。
customElements.define('test-test',class extends HTMLElement {
constructor() {
super();
const node = document.createElement('template');
node.innerHTML = '<button id="in">Inside Shadow DOM</button>';
this.attachShadow({
mode: 'open'
}).appendChild(node.content);
this.shadowRoot.addEventListener('click',e => {
console.log("[capturing phase] in Shadow DOM",e.target);
},{ capture: true });
this.shadowRoot.addEventListener('click',e => {
console.log("[bubbling phase] in Shadow DOM",{ capture: false });
}
});
document.addEventListener('click',e => {
console.log( "[capturing phase] in Light DOM",e.target);
},{ capture: true });
document.addEventListener('click',e => {
console.log( "[bubbling phase] in Light DOM",{ capture: false });
<test-test></test-test>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。