JavaScriptの新しいElementメソッドの紹介
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: none と content-visibility: hidden は false になります。
重要な制限があります。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(() => {}))
);
finished は cancel() されると 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.fullscreenElement と fullscreenchange イベントで管理してください。
setPointerCapture():ドラッグ実装の定番バグを避ける
setPointerCapture() を使うと、ポインターが要素の外に出ても、その要素がイベントを受け取り続けます。ドラッグ操作で要素を掴み損ねる問題が消えます。
ただし、単純に event.clientX を translate() に入れる実装は間違いです。掴んだ瞬間に要素がカーソル位置へ飛びます。
// つかんだ瞬間に要素が飛ぶ
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: noneとpointercancelを忘れない
DOM操作の記事は「いつの情報か」で価値が大きく変わります。実装前に MDN のブラウザ対応表を見る癖をつけておくと、遠回りが減ります。