子要素を display: none しても :empty は効かない

タイトルの通りです。

要素の表示非表示はJavaScriptで制御することが多いと思いますが、その際CSSで`display: none`を付けたり外したりする方法がよく使われると思います。

Tailwind CSSだと`hidden`というクラス名を付け外しするだけでできるので便利ですね。
例: `element.classList.toggle('hidden');`

```html:HTML
<div class="container">
    <p>この要素のdisplay: noneを切り替える</p>
</div>
```

その際、親要素にスタイルを当てている場合、子要素が空のときはそのスタイルを消したい、もしくは親要素自体も`display: none`で消したい、という場面は結構あるかと思います。

そこで`:empty`の出番です。

```css:CSS
.container {
    border: 3px double red;
}

.container:empty {
    border: none;
}
```

この際、子要素を`display: none`で消したとします。
するとその要素はアクセシビリティツリー上からも消え、ブラウザからも、スクリーンリーダーからも「見えなく」なります。

しかしながら、その要素はDOM上では存在しているため、`:empty`は適用されません。

それはそう、と言われればそれまでなんですが、意外と盲点だったので書き留めておきます。

> `:empty` は CSS の擬似クラスで、子を持たない要素を表します。子とは要素のノードまたは文字列 (ホワイトスペースを含む) です。コメント、処理指示、 CSS の content は要素が空であるかどうかの判断には影響しません。

https://developer.mozilla.org/ja/docs/Web/CSS/:empty

また、`:empty`は改行があっても反映されないので注意が必要です。

```html:これだと :empty は反映されない
<div class="container">
</div>
```

## `:empty`を適用させるには

子要素自体を削除するしかないです。

```js:JavaScript
const container = document.querySelector('.container');

container.innerHTML = '';
```

ただ、要素自体が削除されているので、`display: none`の付け外しによる表示非表示のような動作をさせたい場合、どこかに情報を保持しておく必要があります。

```js:JavaScript
const container = document.querySelector('.container');
const originalContent = container.innerHTML;

// 削除
container.innerHTML = '';

// 復元
container.innerHTML = originalContent;
```

ただ、子要素を削除した後にもう一度上記のJSを実行すると、「そもそも子要素がないよ」と怒られるので、情報の保持、削除、復元は別々の関数で処理する必要があります。

```js:情報の保持、削除、復元は別々の関数で処理
let originalContent = '';

const storeOriginalContent = () => {
    const container = document.querySelector('.container');
    if (container && !originalContent) {
        originalContent = container.innerHTML;
    }
};

const deleteContent = () => {
    const container = document.querySelector('.container');
    if (container) {
        container.innerHTML = '';
    }
};

const restoreContent = () => {
    const container = document.querySelector('.container');
    if (container && originalContent) {
        container.innerHTML = originalContent;
    }
};

// 初回に情報を保持
storeOriginalContent();

// 削除
deleteContent();

// 復元
restoreContent();
```

### `display: none`で消すパターンと要素ごと消すパターンの`:empty`の挙動の違い

以下のサンプルには

```css
.container {
    border: 3px double red;
}

.container:empty {
    border: none;
}
```

的な内容が含まれています。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="LEYqOpz" data-pen-title="Qiita: Differences in `:empty` Behavior: `display: none` vs. Element Removal" data-user="yoruaki" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/yoruaki/pen/LEYqOpz">
  Qiita: Differences in `:empty` Behavior: `display: none` vs. Element Removal</a> by Yoruaki (<a href="https://codepen.io/yoruaki">@yoruaki</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

ユーザーの操作に応じて動的に要素の表示非表示を切り替える場合、`:empty`は相性が悪そうですね。
どちらかというとサーバーサイドでページを生成する際に、不要な要素を除外する場合などで力を発揮しそうです。

## まとめ

ちょっとしたサンプルコードを書いているときに今回の状況に直面したので、別途まとめてみました。
同じような状況に遭遇した方の参考になれば幸いです。

今後も新しい発見を楽しみながら、皆さんと一緒に学びを深めていければと思います。