当元素被删除时的DOM事件

12

有没有办法在一个元素从 DOM 中直接移除或作为子树的一部分被移除时得到通知?似乎现有的方法只能用于直接移除的节点,但我希望在包含我的节点的整个子树被移除时得到通知。

编辑

问题似乎并不是很清晰,因此我提出了一个挑战:https://jsbin.com/winukaf

DOM 看起来像这样:

<body>
  <div class="root">
    <div class="great-grand-parent">
      <div class="grand-parent">
        <div class="parent">
          <div class="leaf">
            Am I still here?
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

而挑战在于当这些元素中的任意一个被移除时,通知该节点已从DOM树中移除。


你能否请展示你的代码? - Ionut Necula
@Ionut - 我们不需要看到它,问题已经足够清晰了。 - user10058046
@JᴀʏMᴇᴇ,如果有相关的代码,问题会更清晰明了。在此之前,我们只能猜测。我们还需要看到提问者的努力,他到目前为止尝试了什么。也许你应该阅读《如何创建一个最小、完整和可验证的示例》(https://stackoverflow.com/help/mcve)。 - Ionut Necula
@Ionut - 你需要什么的澄清吗?不!这就是我纠正你的原因--我们不需要看到为了做事而做事的努力。这不是一个学院,太多时候人们要求展示努力,以便证明已经表现出了一些“意愿”,才配得上答案。这个问题很清楚,如果有人有答案,那么他应该得到回答。如果需要代码来澄清,则可以提出请求,但我认为在这种情况下并非如此。 - user10058046
OP - 针对这个问题,变异观察器能帮助你吗?https://dev59.com/1mIj5IYBdhLWcg3wilgI#20156615 显而易见的替代方案是某种形式的轮询,但出于明显的原因,我不喜欢它。 - user10058046
2
请注意,尽管当前答案 指向了正确的API,但如果您需要它,这几乎总是一个设计缺陷。您应该掌控自己的代码,并能够知道何时会发生这种情况。 - Kaiido
3个回答

14

有一个名为MutationObserver的HTML5 API,它有相当不错的支持

这里是一个例子:

// Element is the whatever subtree/element you need to watch over
var in_dom = document.body.contains(element);
var observer = new MutationObserver(function(mutations) {
    if (document.body.contains(element)) {
        if (!in_dom) {
            console.log("element inserted");
        }
        in_dom = true;
    } else if (in_dom) {
        in_dom = false;
        console.log("element removed");
    }

});
observer.observe(document.body, {childList: true, subtree: true});

1
我已经纠正了我的答案,这并不是最好的方法,我认为最简洁的方式就是意识到我的代码以及可能删除 DOM 的地方,并将逻辑粘贴在那里。 - Tareq El-Masri
1
是的,你说得很到位;-)(附注:我看到了你的编辑并删除了我的先前评论) - Kaiido
嗯,看起来document.body.contains是一个很好的潜在解决方案。我没有意识到它会遍历整个树。MDN文档 - Marius
是的,这个答案中的观察器需要使用 {childList: true, subtree: true} 进行调用。 - Marius
document.body.contains(element) 可以被替换为 element.isConnected,并且可能更快,但是它的浏览器支持较少。 - Yay295
显示剩余2条评论

0
你应该使用 MutationObserver API 来完成这个任务。以下是MDN的示例,已经适应了一个简单的场景:

// Select the node that will be observed for mutations
var targetNode = document.getElementsByClassName('root')[0];

// Options for the observer (which mutations to observe)
var config = {
  childList: true,
  subtree: true
};

// Callback function to execute when mutations are observed
var callback = function(mutationsList, observer) {
  console.log('A child node has been added or removed.');
  console.log(mutationsList[0]);
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

var subElement = document.getElementsByClassName('parent')[0];
var elementToRemove = document.getElementsByClassName('leaf')[0];
var anotherElement = document.getElementsByClassName('great-grand-parent')[0];
setTimeout(function() {
  subElement.removeChild(elementToRemove);
}, 500);
setTimeout(function() {
  targetNode.removeChild(anotherElement);
}, 500);

// Later, you can stop observing
// observer.disconnect();
<div class="root">
  <div class="great-grand-parent">
    <div class="grand-parent">
      <div class="parent">
        <div class="leaf">
          Am I still here?
        </div>
      </div>
    </div>
  </div>
</div>


这并没有回答问题,因为它只能确定直接子节点是否已被删除。 - Marius
@Marius,请检查我的更新示例。在直接后代中删除一个元素会正确地记录到控制台! - Angelos Chalaris
但我不想删除直接后代中的一个元素,我想要删除子树。 - Marius
@Marius 就像我之前所说的,如果你看一下这些例子,移除 #a#app 的直接后代)会正确地记录日志。同样的情况也适用于 #c(它是 #a 的后代,而 #a 又是 #app 的后代,因此是 #app 子树的一部分)。 - Angelos Chalaris
1
@Marius它观察子树(请注意选项subtree:true),您只需要观察document.body - barbsan
@Marius 我已经更新了我的示例以匹配你的HTML结构。 - Angelos Chalaris

0

这里有一个现成的代码片段可供使用(我修改了this answer以使其更简单,去掉了一些不必要的东西,并添加了更多的删除情况),使用MutationObserver HTML5 API。

var obs = new MutationObserver(mut => console.log(mut[0].removedNodes[0].id));
obs.observe(document.getElementById('root'), { childList: true, subtree: true });

setTimeout(() => { document.getElementById('leaf').remove(); }, 2000);
setTimeout(() => { document.getElementById('grand-parent').remove(); }, 4000);
setTimeout(() => { document.getElementById('great-grand-parent').outerHTML = '<div id="new-born">Hello!</div>'; }, 6000);
setTimeout(() => { document.getElementById('new-born').remove(); }, 8000);
<div id="root">
  <div id="great-grand-parent">
    <div id="grand-parent">
      <div id="parent">
        Parent
        <div id="leaf">
          Am I still here?
        </div>
      </div>
    </div>
  </div>
</div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接