【JavaScript】document.bodyはbody要素以外を参照することがあるという小ネタとその状況への対応方法

 document.body は描画される範囲のDOMの根本を参照したい時によく使います。現代で言えば body 要素を参照したい時に使うということです。このdocument.bodyですが、実はbody要素以外が参照される場合があります。実際に MDN のドキュメントには次のようにあります。

Document.body プロパティは、その文書の <body> または <frameset> ノードを表し、そのような要素がなければ null になります。

Document.body – Web API | MDN

 framesetという見慣れない要素も参照する場合がある、とあります。HTML構造が次の場合は必ずdocument.bodyがbody要素になりますが

<html>
  <head></head>
  <body></body>
</html>

次の場合はframeset要素になります。

<html>
  <head></head>
  <frameset></frameset>
</html>

 framesetは複数のHTMLを一画面上に表示するために用いられていた非推奨要素です。最近ではまず見ないのですが、多くのブラウザはframesetに対応しており、framesetを使っている古いwebページは稀にあります。

– HTML: ハイパーテキストマークアップ言語 | MDN

 document.bodyを使う時、自作したHTMLの上で用いるのであれば参照される要素が何なのかは分かり切っていますが、ブラウザ拡張機能などそうは行かない時もあります。framesetは非推奨要素ということで対応をしないことも考えられますが、対応する方法を紹介します。

 実際に各画面の body 要素を取得する関数が次です。

function getBodyElList() {
    // bodyEl は HTMLFrameSetElement か HTMLBodyElement のどちらか
    const bodyEl = document.body;
    return bodyEl instanceof HTMLBodyElement
        // HTMLBodyElement ならば、そのまま配列に入れて返す
        ? [bodyEl]
        // HTMLFrameSetElement ならば、その子である各 frame から HTMLBodyElement を取得する
        : Array.from(window.frames).map((f) => f.document.body);
}

 frame配下のbodyの参照の仕方はいささか通常のbodyより回り道ですが、ちゃんと参照できます。これで取得した各 body 要素を元に何かをすることでとりあえず動きはします(画面が分割されているためデザインが崩れやすいです)。

 またframe要素はiframe的な動きをする要素であり、URLに対応したページが呼ばれた後にframe内のページが呼ばれるイメージで動作します。こレに対処する方法の一つとして次のように各frameのwindowのloadイベントを使う方法があります。

const initialize = (e) => {
    // e.targetはloadイベントでもDOMContentLoadedイベントでもHTMLDocumentである
    const rootEl = e.target.body;
    // rootElの最下部に赤背景の文字列を表示
    const divEl = document.createElement('div');
    divEl.textContent = 'ここが最下部';
    divEl.style.position = 'fixed';
    divEl.style.bottom = '0';
    divEl.style.left = '0';
    divEl.style.backgroundColor = 'red';
    rootEl.appendChild(divEl);
};
// 親ページの読み込み完了イベントが発生したら、以下の処理を行って各frameを処理します。
// これは document.body が frameset 要素の時のための処理です。
document.addEventListener('DOMContentLoaded', () => {
    // window.framesでフレームの配列を取得し、それぞれに対してイベントリスナーを設定します。
    Array.from(window.frames).forEach(f => f.addEventListener('load', initialize));
})

// DOMContentLoadedイベントが発生した際に、initialize関数を呼び出します。
// これは document.body が body 要素の時のための処理です。
document.addEventListener('DOMContentLoaded', initialize)

 これを実際に動かしたデモが次です。

 手間がかかるしframesetは非推奨要素なので対応を打ち切るのもありですが、一応こんな感じで document.body が body でも frameset でも対応できるようにできます。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG