[JavaScript] nodeツリーが見えるか見えないかを判定したい

2022.01.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。きんくまです。

htmlのnodeツリーが不可視状態かどうかを判定したかたったです。

簡単にいうと、ユーザーからブラウザで見えているのか、見えてないのかを判定したいのです。

不可視の例

空のdiv

<div></div>

改行タグや半角・全角の空白文字だけだったり。

<div>
  <br><br>
      <p>      </p>
</div>

scriptタグ

<script>
//何か書かれている
</script>

要素は存在しているけど、スタイルで不可視になっている

<div>
  <p style="display:none;">あいうえお</p>
</div>

上の組み合わせでこんなやつとか

<head>
<style>
.sample {
  display:none;
}
</style>
</head>
<div>
  <p style="display:none;">あいうえお</p>
  <script>
     // 何か
  </script>
  <div class="sample">
    <p><img src="xxx"></p>
  </div>
</div>

判定のソースコード

// 見えるNodeか
function isVisibleNode(node){
    if(node.nodeType === Node.TEXT_NODE){
        let text = node.nodeValue.trim();
        if(text.length === 0){
            return false;
        }
        return true;
    }
    if(node.nodeType !== Node.ELEMENT_NODE){
        return false;
    }
    const styles = window.getComputedStyle(node);
    const displayValue = styles.getPropertyValue('display');
    if(displayValue === 'none'){
        return false;
    }
    const visibility = styles.getPropertyValue('visibility');
    if(visibility === 'hidden'){
        return false;
    }

    const name = node.tagName.toLowerCase();
    if(name === 'br' || name === 'script' || name === 'style'){
        return false;
    // Nodeの末端
    }else if(node.children.length === 0){
        // 画像のみが見えるものとする
        return name === 'img';
    }
    return true;
}

// 自分自身が見えるNodeか。見えていたら子供Nodeもチェック
function checkNodeVisibleWithChildren(node){
    let isVisible = isVisibleNode(node);
    const children = node.children;
    if(isVisible && children.length > 0){
        let invisibleCount = 0;
        for(let i = 0; i < children.length; i++){
            const child = children[i];
            // 再帰的にチェック
            const isChildVisible = checkNodeVisibleWithChildren(child);
            if (!isChildVisible) {
                invisibleCount++;
            }
        }
        // 全ての子供Nodeが見えなかった
        if(invisibleCount === children.length){
            isVisible = false;
        }
    }
    return isVisible;
}

// 空の文字列か?半角、全角スペース、改行文字は空文字と判断
function checkIsEmptyText(node){
    // 見えるtextだけを取り出す
    const text = node.innerText.trim();
    return text.length === 0;
}

// 空のコンテンツか。空ならtrue
function checkEmptyContents(containerId){
    const container = document.getElementById(containerId);
    let isEmpty = checkIsEmptyText(container);
    if(isEmpty){
        let isVisible = checkNodeVisibleWithChildren(container);
        isEmpty = !isVisible;
    }
    return isEmpty;
}

判定基準は、CSSで不可視になっているnodeは見えないと判断。 テキストか画像が見えていたら、見える要素と判断します。

親nodeから再帰的に子供nodeをチェックしていって、全部が見えない要素だったら見えないと判断します。

なので、もしdivに背景色をつけているものがあったとしても、それは空nodeの見えないものとして判断します。

使い方

checkEmptyContents({親のid});

例. htmlでこれを判定したい場合

<div id="wrapper">
  <p style="display:none;">あいうえお</p>
</div>

jsでこう書く

const result = checkEmptyContents('wrapper');
// result -> true

作ってわかったこと

childNodesとchildrenの違い

childNodes
テキストノード以外の全ての子供Nodeを取れる。
コメントノードなども入ってくる

参考: Node.nodeType

children
Elementノードのみ取得できる(いわゆるhtmlタグです。scriptタグなども含まれます)

参考: Element

innerTextとtextContentの違い

innerText
見えている文字列のみ取得する。もしcssで不可視になっていたら取得しない

参考: HTMLElement.innerText

textContent
見えない文字も取得する。scriptの文字さえも取得する

参考: Node.textContent

感想

細かいプロパティの違いがわかって面白かったです。