×

在 DOM 中移动元素,同时保留其状态moveBefore

作者:Terry2025.02.07来源:Web前端之家浏览:384评论:0
关键词:Chrome 133

image.png

Chrome 133 (将于 2 月 4 日稳定)中新增了一种在 DOM 中移动元素的新方法:Node.prototype.moveBefore。虽然方法很小,但意义重大,因为它在移动元素时会保留元素的状态!例如:

  • iframe 保持加载状态

  • 活动元素依然是焦点

  • 弹出窗口、全屏、模式对话框保持打开状态

  • CSS 过渡和动画继续

旧与新

移动物体的经典方式是使用。这样做时, “移动”Node.prototype.insertBefore的节点将重新加载其初始状态。

document.querySelector('#classic').addEventListener('click', () => {
    const $newSibling = getRandomElementInBody();
    const $iframe = document.querySelector('iframe');

    document.body.removeChild($iframe);
    document.body.insertBefore($iframe, $newSibling);
});

即使你省略了对 的调用removeChildinsertBefore仍会自行执行该操作 - 即使目标仍与父级相连。我将其保留下来是为了让幕后发生的事情更加清晰。

移动元素的新方法是使用moveBefore。语法方面,该方法是模仿的,insertBefore因此您可以轻松地交换内容。

document.querySelector('#classic').addEventListener('click', () => {
    const $newSibling = getRandomElementInBody();
    const $iframe = document.querySelector('iframe');
   
    document.body.moveBefore($iframe, $newSibling);
});

moveBefore移动元素的状态得以保留。


演示

这是一个包含两种方法的演示。

HTML


<h1>DOM State-Preserving Move <em>(<code>Node.prototype.moveBefore</code>)</em></h1>

<p>With <code>Node.prototype.moveBefore</code> you can move elements around a DOM tree, without resetting the element's state.</p>

<p>When moving – instead of using the classic way of removing+inserting – the state of a node is preserved. For example:</p>

<ul>
  <li>Iframes remain loaded</li>
  <li>Active element remains focus</li>
  <li>Popovers, fullscreen, modal dialogs remain open</li>
  <li>CSS transitions and animations carry on</li>
</ul>

<h2>Demo</h2>

<h3>Instructions</h3>


<ol>
  <li>Start the video in the YouTube Embed below (iframe)</li>
  <li>Hit one of the move buttons</li>
  <li>Note how the iframe keeps playing when using <code>moveBefore</code></li>
</ol>

<iframe width="560" height="315" src="https://www.youtube.com/embed/e7BkmF8CJpQ?si=-O_hK4VvqNfVof1L" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<h3>Code</h3>

<pre><code>document.querySelector('#movebefore').addEventListener('click', () =&gt; {
  const $newSibling = getRandomElementInBody();
  const $iframe = document.querySelector('iframe');
  <b>document.body.moveBefore($iframe, $newSibling);</b>
});</code></pre>



<div class="buttons">
  <button id="classic">Move <code>iframe</code> around the classic way</button>
  <button id="movebefore">Move <code>iframe</code> around using <code>moveBefore</code></button>
</div>

<footer><p>Demo for <a href="http://brm.us/movebefore" target="_top">http://brm.us/movebefore</a></p></footer>

<div class="warning">
  <p>You browser does not support <code>ParentNode.prototype.moveBefore</code>. Try Chrome 133+.</p>
</div>

CSS

@layer reset, layout, demo;

@layer demo {
  iframe {
      aspect-ratio: 16 / 9;
  }
 
  .buttons {
    position: fixed;
    top: 0;
    right: 0;
   
    display: flex;
    flex-direction: column;
    gap: 0.5em;
    padding: 1em;
  }
 
  button {
    font-size: 1.25em;
    padding: 0.6em 0.7em;
    background: aliceblue;
    border-radius: 0.5rem;
    cursor: pointer;
   
    &:hover, &:focus {
      background: lightblue;
    }
  }
 
  .warning {
    position: fixed;
    bottom: 0;
    left: 1em;
    right: 1em;
  }
}

@layer layout {
  @layer general {
    html {
      font-family: sans-serif;
    }
    body {
      padding: 3rem 0 10rem;
      width: 90%;
      max-width: 50em;
      margin: 0 auto;
    }
    h2 {
      margin-top: 2em;
    }
   
    p, ul {
      margin-block: 1em;
    }
    a {
      color: #0000aa;
      text-decoration: none;
      &:hover {
        color: blue;
      }
    }
    footer {
      text-align: center;
      font-style: italic;
      margin: 4em 0;
    }
   
    iframe {
      display: block;
      width: 100%;
      height: auto;
      margin: 1em auto;
    }
   
    button, input, select {
      font-family: inherit;
    }
  }

  @layer code {
    script[visible],
    style[visible],
    pre {
      display: block;
      white-space: pre;
      border: 1px solid #dedede;
      padding: 1em;
      background: #fafafa;
      font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
        Liberation Mono, monospace;
      overflow-x: auto;
      border-left: 0.4em solid cornflowerblue;
      tab-size: 2;
      color: #1a1a1a;
      line-height: 1.6;
    }

    code:not(pre code) /*, output:not(code:has(output) output) */ {
      background: #f7f7f7;
      border: 1px solid rgb(0 0 0 / 0.2);
      padding: 0.1rem 0.3rem;
      margin: 0.1rem 0;
      border-radius: 0.2rem;
      display: inline-block;
    }
  }

  @layer warning {
    .warning {
      display: none;
      box-sizing: border-box;
      padding: 1em;
      margin: 1em 0;
      border: 1px solid #ccc;
      background: rgba(255 255 205 / 0.8);
    }

    .warning > :first-child {
      margin-top: 0;
    }

    .warning > :last-child {
      margin-bottom: 0;
    }

    .warning a {
      color: blue;
    }
    .warning--info {
      border: 1px solid #123456;
      background: rgb(205 230 255 / 0.8);
    }
    .warning--alarm {
      border: 1px solid red;
      background: #ff000010;
    }
  }
}

@layer reset {
  * {
    box-sizing: border-box;
  }
  html,
  body {
    margin: 0;
    padding: 0;
  }
}

JS


const randomBetween = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }
 
  const getRandomElementInBody = () => {
    const $candidates = document.querySelectorAll('body > *:not(.buttons, iframe, .warning)');
    return $candidates[randomBetween(0, $candidates.length)];
  }
 
  document.querySelector('#movebefore').addEventListener('click', () => {
    const $newSibling = getRandomElementInBody();
    const $iframe = document.querySelector('iframe');
    document.body.moveBefore($iframe, $newSibling);
  });
 
  document.querySelector('#classic').addEventListener('click', () => {
    const $newSibling = getRandomElementInBody();
    const $iframe = document.querySelector('iframe');
   
    document.body.removeChild($iframe);
    document.body.insertBefore($iframe, $newSibling);
  });
 
  if (!("moveBefore" in Element.prototype)) {
    document.querySelector('.warning').style.display = 'block';
  }

如果您的浏览器不支持moveBefore,请观看此视频以了解其实际效果。YouTube 嵌入(即 iframe)会随着 iframe 的移动而持续播放。

image.png

MutationObserver对Web 组件的影响

如果您有MutationObserver,则使用moveBefore将(就像insertBefore)生成两个突变:一个用于删除,一个用于将元素添加到其新父级。 出于兼容性原因,做出了这样的选择。

使用 Web 组件时,connectedMoveCallback如果您指定了该方法,则会触发该方法。如果您未指定方法 connectedMoveCallback,则常规的disconnectedCallbackconnectedCallback将触发(同样是出于向后兼容的原因),并且isConnectedtrue

浏览器支持

截至撰写本文时,浏览器支持仅限于 Chrome 133+。Safari 和 Firefox 均已表示支持此新 API。

moveBefore您可以按如下方式检测功能的可用性:

if (!("moveBefore" in Element.prototype)) {
    // Not supported
}

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/Chrome133version.html

网友评论文明上网理性发言 已有0人参与

发表评论: