检测 DOM 元素的父元素何时更改

Detecting when a DOM element's parent changes

本文关键字:元素 何时更 DOM 检测      更新时间:2023-09-26

是否有当元素的parentElement发生变化时触发的 DOM 事件?如果没有,还有什么比轮询超时更好的方法吗?

我特别感兴趣的是知道parentElement何时从null更改为某个定义的元素。也就是说,当 DOM 元素附加到文档树的某个位置时。

编辑

鉴于注释中的问题,下面是一个示例,展示了如何使用null parentElement创建元素:

var element = document.createElement('div');
console.assert(element.parentElement == null);

父级仅在添加到 DOM 后进行设置:

document.body.appendChild(element);
console.assert(element.parentElement != null);

另请注意,使用 jQuery 创建的元素在创建时也将具有null父元素:

console.assert($('<div></div>').get(0).parentElement == null);

Afaik 没有这样的"父侦听器"。

然而,我发现了一个可能有帮助的黑客。至少值得一读,因为这个想法很聪明。

http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

他在插入过程中使用 CSS @keyframes,并侦听生成的动画事件,该事件告诉他元素已插入。

1(这样的parentElementHasChanged event不存在。

2(PISquared指出的解决方法可以工作,但对我来说看起来很奇怪。

3(在实践中,没有必要发生这样的事件。只有当元素在 DOM 中的位置发生变化时,parentChange 才会出现在元素上。为此,您必须在执行此操作的元素上运行一些代码,并且所有这些代码都必须使用本机parent.removeChild()parent.appendChildparent.insertBefore()parent.replaceChild()某个地方。相同的代码可以在之后运行回调,因此回调将是事件。

4( 您正在构建库代码。该库可以为所有 DOM 插入/删除提供单个函数,该函数包装四个本机函数并"触发事件"。这是我想到的最后一个也是唯一一个避免频繁查找 parentElement 的方法。

5(如果需要包含本机Event API,您可以使用CustomEvent创建一个parentChanged事件

element.addEventListener('parentChanged', handler); // only when  Event API needed
function manipulateElementsDOMPosition(element, target, childIndex, callback, detail) {
    if (!target.nodeType) {
        if (arguments.length > 4) return element;
        detail = callback; callback = childIndex; childIndex = target; target = null;
    }
    if (typeof childIndex === 'function') detail = callback, callback = childIndex;
    var oldParent = element.parentElement,
        newParent = target,
        sameParent = oldParent === newParent,
        children = newParent.children,
        cl = children.length,
        ix = sameParent && cl && [].indexOf.call(children, element),
        validPos = typeof childIndex === 'number' && cl <= childIndex;
    if (childIndex === 'replace') {
        (newParent = target.parentElement).replaceChild(element, target);
        if (sameParent) return element;
    } else {
        if (samePar) {
            if (!oldParent || ix == childIndex ||
                childIndex === 'first' && ix === 0 ||
                childIndex === 'last' && ix === (cl - 1)) return element;
            oldParent.removeChild(element);
        } else if (oldParent) oldParent.removeChild(element);
        if (!cl || childIndex === 'last') {
            newParent.appendChild(element);
        } else if (childIndex === 'first') {
            newParent.insertBefore(element, children[0])
        } else if (validPos) {
            newParent.insertBefore(element, children[childIndex]);
        } else return element;      
    }
    console.log(element, 'parentElement has changed from: ', oldParent, 'to: ', newParent);
    element.dispatchEvent(new CustomEvent('parentChanged', detail)); // only when Event API needed
    if (typeof callback === 'function') callback.call(element, oldParent, newParent, detail);
    return element;
}

一些示例用法(详细信息可能是要传递给事件/回调的任何内容(。函数始终返回元素。

// remove element
manipulateElementsDOMPosition(element /*optional:*/, callback, detail);
// prepend element in target
manipulateElementsDOMPosition(element, target, 'first' /*optional:*/, callback, detail);
// append element in target
manipulateElementsDOMPosition(element, target, 'last' /*optional:*/, callback, detail);
// add element as third child of target, do nothing when less than two children there
manipulateElementsDOMPosition(element, target, 3 /*optional:*/, callback, detail);
// replace a target-element with element
manipulateElementsDOMPosition(element, target, 'replace' /*optional:*/, callback, detail);

你可以像这样使用MutationObserver:

const trackedElement = document.getElementById('tracked-element');
const parent1 = document.getElementById('parent1');
const parent2 = document.getElementById('parent2');
startObserver();
function changeParent() {
  if (trackedElement.parentElement == parent1) {
    parent1.removeChild(trackedElement);
    parent2.appendChild(trackedElement);
  } else {
    parent2.removeChild(trackedElement);
    parent1.appendChild(trackedElement);
  }
}
function startObserver() {
  let parentElement = trackedElement.parentElement
  new MutationObserver(function(mutations) {
    if (!parentElement.contains(trackedElement)) {
      trackedElement.textContent = "Parent changed";
      setTimeout(() => {
        trackedElement.textContent = "Parent not changed";
      }, 500);
      startObserver();
      this.disconnect();
    }
  }).observe(parentElement, {
    childList: true
  });
}
<!doctype html>
<html lang="en">
<body>
  <div id="parent1">
    <p id="tracked-element">Parent not changed</p>
  </div>
  <div id="parent2"></div>
  <button onclick="changeParent()">ChangeParent</button>
</body>
</html>

您可以右键单击Parent not changed文本,然后单击检查以确保它确实正在更改父项

如果你想知道.observer函数是如何工作的,这里有非常好的文档:https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe