こんにちは!フロントエンドの現場を渡り歩いてきたチーフアーキテクトの私です。
Webサイトを作っていると、最初はサクサク動いていたのに、JavaScriptでDOMをゴリゴリ書き換えたり、アニメーションを派手に動かしたりした途端、「カクカクしてきた……」なんて経験、ありませんか?
「自分のコードの書き方が悪いのかな……」と落ち込む必要は全くありませんよ。大丈夫、それはあなたが悪いのではなく、Webブラウザの「おせっかいで完璧主義な性格」が原因なんです。
今回は、そんなブラウザの性格をうまく手懐け、レンダリング(画面を描画する仕組み)を劇的に軽くする魔法のCSSプロパティ`contain`(コンテイン)について、身近な例えを交えながら優しく紐解いていきましょう!
—
なぜブラウザはすぐに「全体を作り直す」のか?
まずは、ブラウザが画面を表示するときに裏で何をやっているのかを覗いてみましょう。
Webブラウザは、HTMLやCSSを読み込むと、「DOMツリー」や「CSSOMツリー」という設計図を作ります。そして、それらを組み合わせて「画面のどこに、どんなサイズで配置するか(レイアウト)」を計算し、最後に「色を塗る(ペイント)」という作業を行います。
ここで問題になるのが、「ブラウザの真面目すぎる性格」です。
例えば、Webページの一番端っこにある、小さな「いいねボタン」の数字が「1」から「2」に変わったとします。たったこれだけの変化です。
本来なら「そのボタンのところだけ書き換えればいいじゃん!」と思いますよね?
しかし、ブラウザのデフォルトの挙動はこうです。
> 「もしかしたら、この数字が変わったことで、隣の要素のサイズが変わって、それがさらに上の親要素を押し広げて、ページ全体のレイアウトがガラリと変わるかもしれない……! 安全のために、ページ全体(あるいは大きな親要素)のレイアウトを最初から計算し直さなきゃ!」
これが、ほんの小さな変更なのにブラウザが重くなる原因、つまり「無駄な再計算(リフロー/リペイント)」の嵐です。
—
例え話:巨大なショッピングモールの「防火シャッター」
このブラウザの特性を解決してくれるのが、今回主役の`contain`プロパティです。
`contain`をひとことで言うと、「Webページの中に、見えない防火シャッターを下ろす技術」です。
想像してみてください。
とある巨大なショッピングモール(=Webページ)があります。その中の一つの小さなお店(=DOMのサブツリー)で、店員さんが商品をちょっと模様替えしたとします。
もし、このお店と他のお店を隔てる「防火シャッター」がなかったらどうなるでしょう?
模様替えのたびに、モール全体の管理センターが「おい、あのお店が変わったぞ! 全館の避難経路や通路の広さを再計算しろ!」と大騒ぎになってしまいますよね。これじゃあモールの運営が追いつきません。
そこで、お店ごとに頑丈な防火シャッター(`contain`)を下ろしておくのです。
そうすれば、「このお店の中で何が起きようと、外のモール全体には一切影響しません! だから外の計算はしなくていいです!」と、モールの管理センター(=ブラウザ)に伝えることができます。
これこそが、`contain`がやってくれていることなんです。
—
`contain`プロパティが持つ「4つの力」
CSSの`contain`プロパティは、いくつかある「閉じ込めたい影響の種類」を組み合わせて指定します。よく使われる代表的な値をみていきましょう。
1. `size`(サイズの閉じ込め)
- 「この要素の大きさは、子要素の大きさに依存しない!」とブラウザに教えます。子要素がどれだけ大きくなろうと、親のサイズ計算には影響しません。
2. `layout`(レイアウトの閉じ込め)
- この要素の中で起きたレイアウトの変更は、外側に影響を与えません(逆も同じ)。一番よく使われる頼もしいやつです。
3. `style`(スタイルの閉じ込め)
- CSSのカウンタや見積もりなどのスタイルが、要素の外側に漏れ出さないようにします。
4. `paint`(ペイントの閉じ込め)
- この要素からはみ出た描画(例えば、はみ出たドロップシャドウなど)は、外側に描画されません。クリッピング(切り抜き)されたような状態になり、要素が画面外にあるときはペイント自体がスキップされます。
そして、これらをまとめて指定する便利なショートカットがあります。それが `contain: layout paint;` や、さらに強力な `contain: content`、そして最強の `contain: strict` です。
—
実務でどう使う? コード例を見てみよう
「理屈は分かったけど、実際にどう書くの?」という声が聞こえてきそうですね。
例えば、ニュースサイトやブログにあるような、「大量に並んだカード型のウィジェット(記事一覧など)」を想像してください。ユーザーがスクロールしたり、お気に入りボタンを押したりするエリアです。
ここに `contain` を適用してみましょう。
Webブラウザの仕組みを知ろう
ブラウザのレンダリングエンジンは、私たちが想像するよりもずっと頑張屋です。
containプロパティで爆速化
無駄なレイアウト計算をシャットアウトして、滑らかなスクロールを手に入れましょう。
フロントエンドの沼へようこそ
仕組みがわかると、CSSを書くのがもっと楽しくなりますよ。
このコードでは、`.article-card` に対して `contain: layout paint;` を指定しています。
これにより、例えばJavaScriptで特定のカードの中に新しい要素を挿入したり、スタイルを動的に書き換えたりしても、ブラウザは「あ、あのカードの中だけね、オッケー」と理解し、他のカードのレイアウト再計算を完全にスキップしてくれます。
結果として、ページ全体のパフォーマンス(特に低スペックなスマートフォンや、大量の要素を扱うダッシュボード画面など)が劇的に改善されるのです。
—
使éreばいいってわけじゃない? 注意しておきたいポイント
「じゃあ、すべての要素に `contain: strict` を貼ってしまえば最強じゃん!」と思ったそこのあなた。さすが鋭いですが、ちょっと待ってくださいね。
ブラウザの仕組みを少し深く知る私たちアーキテクトから見ると、`contain` は「諸刃の剣」でもあります。
- 表示が切れてしまうリスク
- `contain: paint` を指定すると、要素からはみ出た部分(例えば、ドロップシャドウや、絶対配置で外にはみ出させたい装飾など)が強制的にスパッと切り取られてしまいます。「あれ? 影が消えた!?」というバグにハマりがちなので注意が必要です。
- サイズが固定されるリスク
- `contain: size` を使う場合、その要素のサイズが子要素のコンテンツ量によって自動で変わらなくなります(高さを明示的に指定しないと潰れてしまうことがあります)。初学者のうちは、まずは安全な `contain: layout paint` あたりから試すのがマイルールです。
—
まとめ:ブラウザと仲良くなろう
Webブラウザは、私たちの書いたコードを忠実に、そして美しく画面に映し出そうと日々フル稼働してくれています。でも、ちょっと真面目すぎて、余計なところまで頑張りすぎてしまうことがあります。
そんなとき、`contain` プロパティを使って、「ここは私が責任を持って独立させておくから、外のことは気にしないで大丈夫だよ!」とブラウザにそっと教えてあげてください。
きっとブラウザも「おっ、今回はあそこの計算だけでいいんだな!ラクちん!」と、軽快な動作で応えてくれるはずです。
フロントエンドの世界は、こうしたブラウザの裏側の仕組みを知れば知るほど、パズルを解くように面白くなっていきます。まずは自分の手元の小さなコンポーネントから、ぜひ防火シャッターを下ろしてみてくださいね。
それでは、また次の現場でお会いしましょう!

コメント