Elementまわりで「新しい」と呼べるのは、Element.checkVisibility()(2024年3月にBaseline入り)、Popover API(2025年1月にBaseline入り)、Node.moveBefore()(2025年〜、Safari未対応)の3つです。

先に注意点を書いておきます。ネット上で「新しいElementメソッド」として attachShadow()requestFullscreen() が紹介されていることがありますが、これらはいずれも2010年代から使えている機能です。新しくはありません。

この記事では、本当に最近使えるようになったものと、昔からあるが正しく使えていないことが多いものを分けて、動くコードで解説します。

いつから使えるのかを整理する

まず前提を揃えます。DOM操作の記事は情報の鮮度がばらつきやすいので、「その機能がいつから使えるか」を先に確認する習慣が効きます。

メソッド/API 使えるようになった時期 位置づけ
Element.attachShadow() 2016年前後 枯れた機能
Element.requestFullscreen() 2010年代前半(接頭辞つき) 枯れた機能
Element.setPointerCapture() 2015年前後 枯れた機能
Element.getAnimations() 2019〜2020年 枯れた機能
Element.checkVisibility() 2024年3月 Baseline 新しい
Popover API 2025年1月 Baseline 新しい
Node.moveBefore() Chrome/Edge 133(2025年2月)、Firefox 144。Safari未対応 最新

 

Element.checkVisibility():要素が実際に見えているか調べる

「この要素は今そこに表示されているか」を1行で判定できます。 従来は offsetParent を見たり getComputedStyle() を複数回呼んだりしていた処理が置き換わります。

const el = document.querySelector('#target');

// display:none や content-visibility:hidden なら false
el.checkVisibility();

// opacity:0 や visibility:hidden も「見えていない」として扱う
el.checkVisibility({
  opacityProperty: true,
  visibilityProperty: true,
  contentVisibilityAuto: true,
});

 

オプション true にすると false 判定になる条件
opacityProperty opacity: 0
visibilityProperty visibility: hidden / collapse
contentVisibilityAuto content-visibility: auto で描画がスキップされている

オプションを何も渡さない場合でも、display: nonecontent-visibility: hiddenfalse になります。

重要な制限があります。true が返っても「画面内に見えている」ことは保証されません。 ビューポートの外にある要素や、他の要素に覆われている要素でも true になります。判定しているのは「CSS的に描画対象か」だけです。

画面内に入ったかどうかを知りたい場合は、用途が違います。そちらはIntersection Observer APIの担当です。

// 隠れている要素にフォーカスを当てようとして失敗するのを防ぐ
function focusIfVisible(el) {
  if (!el.checkVisibility({ opacityProperty: true, visibilityProperty: true })) {
    return false;
  }
  el.focus();
  return true;
}

 

Popover API:ツールチップやメニューをJavaScriptなしで出す

2025年1月にBaseline入りした、この数年で最も実用的な追加です。 属性2つで、トップレイヤーに浮くUIが作れます。

<button popovertarget="menu">メニューを開く</button>

<div id="menu" popover>
  <ul>
    <li><a href="#">プロフィール</a></li>
    <li><a href="#">設定</a></li>
  </ul>
</div>

 

これだけで次の挙動が付いてきます。

  • 外側クリックで閉じる(light dismiss)
  • Escキーで閉じる
  • トップレイヤーに載るので z-index の積み上げ問題が起きない
  • 同時に開ける popover はひとつだけ(popover="manual" で解除可)

JavaScriptから制御するメソッドも用意されています。

const menu = document.getElementById('menu');

menu.showPopover();
menu.hidePopover();
menu.togglePopover();

menu.addEventListener('toggle', (e) => {
  console.log(e.oldState, '→', e.newState); // 'closed' → 'open'
});

 

dialog との使い分け

popover <dialog> + showModal()
背景の操作 できる できない
フォーカスの閉じ込め なし あり
外側クリックで閉じる 既定でそうなる closedby="any"が必要
向く用途 ツールチップ、メニュー、通知 確認・入力を止めたいとき

「操作を止めたい」ならダイアログ、「補助的に出したい」ならポップオーバーという切り分けになります。ダイアログ側の実装はdialog要素の改善解説と使い方にまとめています。

 

Node.moveBefore():状態を保ったままDOMを移動する

appendChild()insertBefore() で要素を動かすと、いったんDOMから外れるため状態がリセットされます。フォーカスが外れ、再生中の動画が止まり、CSSアニメーションが最初に戻ります。

moveBefore() はこれを「外さずに移動する」ため、状態が保たれます。

const list = document.getElementById('list');
const item = document.getElementById('item');
const ref  = document.getElementById('ref');

// 従来:フォーカスも動画の再生位置も失われる
list.insertBefore(item, ref);

// 新しい:状態を保ったまま移動する
list.moveBefore(item, ref);

 

保たれるものは次の通りです。

  • フォーカス(入力中のテキストボックスがそのまま)
  • 再生中の <video> / <audio>
  • 開いているポップオーバー・モーダル・全画面表示
  • 実行中のCSSトランジションとアニメーション
  • iframe の中身(再読み込みされない)

2026年9月時点で Safari は未対応なので、機能検出して従来の方法に落とす必要があります。

function moveElement(parent, node, ref) {
  if (typeof parent.moveBefore === 'function') {
    parent.moveBefore(node, ref);
  } else {
    parent.insertBefore(node, ref); // 状態は失われるが動く
  }
}

 

ドラッグ&ドロップの並べ替えで、掴んでいる要素のアニメーションが途切れるという定番の問題は、これで解決します。

 

attachShadow():カプセル化されたコンポーネントを作る

ここからは昔からある機能です。まず attachShadow()。要素の内部に、外のCSSの影響を受けない領域を作ります。

class CustomButton extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });

    // スタイルは adoptedStyleSheets で渡すのが今の書き方
    const sheet = new CSSStyleSheet();
    sheet.replaceSync(`
      button {
        background: #2a7fff;
        color: #fff;
        border: none;
        border-radius: 8px;
        padding: 10px 20px;
        font: inherit;
        cursor: pointer;
      }
      button:focus-visible { outline: 3px solid #ffb300; outline-offset: 2px; }
    `);
    shadow.adoptedStyleSheets = [sheet];

    const button = document.createElement('button');
    button.textContent = this.getAttribute('label') ?? 'Click Me';
    shadow.appendChild(button);
  }
}

customElements.define('custom-button', CustomButton);

 

<custom-button label="送信する"></custom-button>

 

2つポイントがあります。

  • innerHTML に文字列でスタイルを流し込まないadoptedStyleSheets なら複数インスタンスでシートを共有でき、パースも1回で済みます
  • mode: 'open''closed'open なら外から element.shadowRoot で参照できます。closed は参照できませんが、実質的な保護にはなりません。テストのしやすさから open が一般的です

 

getAnimations():CSS由来も含めてアニメーションを掴む

getAnimations() は、JavaScriptで作ったものだけでなく、CSSで定義したアニメーションやトランジションも返します。ここが便利なところです。

const box = document.querySelector('.box');

box.animate(
  [{ transform: 'translateX(0)' }, { transform: 'translateX(100px)' }],
  { duration: 1000, iterations: Infinity }
);

document.querySelector('#stop-animation').addEventListener('click', () => {
  box.getAnimations().forEach((anim) => anim.pause());
});

// ページ全体のアニメーションが終わるのを待つ
await Promise.all(
  document.getAnimations().map((a) => a.finished.catch(() => {}))
);

 

finishedcancel() されると reject されるので、Promise.all でまとめて待つときは catch を挟んでおきます。アニメーション制御の詳細はWeb Animations APIのanimateメソッドにまとめました。

 

requestFullscreen():Promiseを返すので必ずcatchする

requestFullscreen()Promiseを返し、失敗することがあります。ユーザー操作を起点にしていない場合や、iframe に allow="fullscreen" が無い場合に拒否されます。

const content = document.querySelector('#content');

document.querySelector('#fullscreen-button').addEventListener('click', async () => {
  try {
    if (document.fullscreenElement) {
      await document.exitFullscreen();
    } else {
      await content.requestFullscreen({ navigationUI: 'hide' });
    }
  } catch (err) {
    console.warn('全画面表示に失敗しました:', err);
  }
});

// Escキーなどで解除された場合もここで拾える
document.addEventListener('fullscreenchange', () => {
  const isFull = Boolean(document.fullscreenElement);
  document.body.classList.toggle('is-fullscreen', isFull);
});

 

解除ボタンを自分で用意しても、ユーザーはEscキーで抜けます。 状態は自前のフラグではなく document.fullscreenElementfullscreenchange イベントで管理してください。

 

setPointerCapture():ドラッグ実装の定番バグを避ける

setPointerCapture() を使うと、ポインターが要素の外に出ても、その要素がイベントを受け取り続けます。ドラッグ操作で要素を掴み損ねる問題が消えます。

ただし、単純に event.clientXtranslate() に入れる実装は間違いです。掴んだ瞬間に要素がカーソル位置へ飛びます。

// つかんだ瞬間に要素が飛ぶ
draggable.addEventListener('pointermove', (event) => {
  draggable.style.transform = `translate(${event.clientX}px, ${event.clientY}px)`;
});

 

正しくは「押した位置と要素位置の差分」を保持し、移動量だけを反映します。あわせて pointerup でキャプチャを解放します。

const draggable = document.querySelector('#draggable');

let dragging = false;
let startX = 0, startY = 0;   // 押した瞬間のポインター座標
let baseX = 0, baseY = 0;     // そのときの要素の移動量

draggable.addEventListener('pointerdown', (event) => {
  dragging = true;
  startX = event.clientX;
  startY = event.clientY;

  draggable.setPointerCapture(event.pointerId);
  draggable.style.touchAction = 'none'; // スクロールと競合させない
});

draggable.addEventListener('pointermove', (event) => {
  if (!dragging) return;

  const dx = event.clientX - startX;
  const dy = event.clientY - startY;
  draggable.style.transform = `translate(${baseX + dx}px, ${baseY + dy}px)`;
});

function endDrag(event) {
  if (!dragging) return;
  dragging = false;

  baseX += event.clientX - startX;
  baseY += event.clientY - startY;

  draggable.releasePointerCapture(event.pointerId);
}

draggable.addEventListener('pointerup', endDrag);
draggable.addEventListener('pointercancel', endDrag);

 

押さえるべき点は3つです。

  • 差分で動かす:絶対座標を入れると飛ぶ
  • touch-action: none:付けないとスマートフォンでページのスクロールに持っていかれます
  • pointercancel も拾う:着信などで中断されたとき、掴んだままの状態が残るのを防ぎます

 

まとめ

  • attachShadow() / requestFullscreen() / setPointerCapture() / getAnimations()2024年の新機能ではありません
  • 本当に新しいのは checkVisibility()(2024年3月)、Popover API(2025年1月)、moveBefore()(Safari未対応)
  • checkVisibility() が見るのは「CSS的に描画対象か」。画面内に見えているかは分かりません
  • ツールチップやメニューは Popover API、操作を止めたいときは <dialog>
  • moveBefore() は状態を保ったまま移動できる。機能検出して insertBefore() に落とす
  • Shadow DOM のスタイルは adoptedStyleSheets で渡す
  • requestFullscreen() は Promise。状態は document.fullscreenElement で管理
  • ドラッグは差分で動かし、touch-action: nonepointercancel を忘れない

DOM操作の記事は「いつの情報か」で価値が大きく変わります。実装前に MDN のブラウザ対応表を見る癖をつけておくと、遠回りが減ります。

ABOUT ME
りん
このブログでは、Web開発やプログラミングに関する情報を中心に、私が日々感じたことや学んだことをシェアしています。技術と生活の両方を楽しめるブログを目指して、日常で触れた出来事や本、グルメの話題も取り入れています。気軽に覗いて、少しでも役立つ情報や楽しいひとときを見つけてもらえたら嬉しいです。