Facebookの新しいUIデザインで見つけたCSSのテクニックのまとめ
Post on:2020年4月28日
先月からエンドユーザーに段階的に適用され始めたFacebookの新しいUIデザイン、そこで見つけたCSSのテクニックを解説した記事を紹介します。
CSSの実践的なテクニックが数多く使用されており、パフォーマンスや何らかの制約による実装時などの参考になると思います。

CSS Findings From The New Facebook Design
by Ahmad Shadeed
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- はじめに
- アバターにSVGを使用する
- マージンの代わりにスペーサーdivを使用する
- CSSフィルターの使用
- シャドウに画像を使用する
- CSS変数の多用
- テキストの省略表示(複数行テキストの切り捨て)
- ホバーエフェクトにdivを使用する
- insetプロパティの使用
- dir=autoとCSSの論理プロパティ
- 動的なカバー写真の背景
- 複数のbox-shadow
- Flexboxのグリッドと空要素
- 垂直メディアクエリの使用
はじめに
私はWebサイトを見る時、デベロッパーツールでどのように実装されているのかいつも確認してしまう好奇心旺盛な人間です。CSSの興味深い使い方(少なくとも私とって)を見つけたので、共有したいと思いました。
先月から、Facebookは新しいUIデザインをユーザー向けに公開しました。その中で私が興味深かったことについて紹介します。
【訳者注】Facebookの新しいUIデザインは下記のようになっています。ライトモード・ダークモードに対応しており、非常に洗練されたデザインです。
新しいUIが適用されているユーザーは、上部右端の▼アイコンから変更できます。

画像: @facebookapp
アバターにSVGを使用する

Facebookの新しいUIデザイン
Facebookの新しいUIデザインではプロフィール写真、あなたのページやグループなどのアバター画像にSVGを使用しています。
1 2 3 4 5 6 7 8 9 |
<svg role="none" style="height: 36px; width: 36px;"> <mask id="avatar"> <circle cx="18" cy="18" fill="white" r="18"></circle> </mask> <g mask="url(#avatar)"> <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg" style="height: 36px; width: 36px;"></image> <circle cx="18" cy="18" r="18"></circle> </g> </svg> |
なぜSVGを使ったのでしょう?
私の考察は、下記の通りです。
- アバターが真っ白であったとしても、明るいアバターが円形に見えるようにするにはアバターの内側にブラック(透明度10%)のボーダーをつける必要があります。
- HTMLの<img>にインセットのbox-shadowを追加することはできません。それを解決するためにSVGを使用します。
- 画像を円形にするには、SVGの<mask>とSVGの<image>要素を使用します。
前述したように、アバターの内側のボーダーは明るい画像に役立ちます。下記は、それを具体的に示したモックアップです。

ボーダーは、明るい画像に役立つ
内側のボーダーは下記のように追加されています。
1 2 3 4 5 6 |
circle, rect { stroke-width: 2; stroke: rgba(0, 0, 0, 0.1); fill: none; } |
また、画像が矩形の場合、使用される形状はrectになります。
1 2 3 4 5 6 7 8 9 |
<svg role="none" style="height: 36px; width: 36px;"> <mask id="avatar"> <rect cy="18" fill="white" height="36" rx="8" ry="8" width="36" x="0" y="0"></rect> </mask> <g mask="url(#avatar)"> <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg" style="height: 36px; width: 36px;"></image> <rect cy="18" fill="white" height="36" rx="8" ry="8" width="36" x="0" y="0"></rect> </g> </svg> |
興味深いことに、ホームページのフィードのアバターは、内側の透明なボーダーの<div>要素と<img>要素で実装されています。
1 2 3 4 |
<div class="avatar-wrapper> <img class="avatar" width="40" height="40" src="avatar.jpg" width="40" alt=""> <div class="avatar-outline"></div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.avatar-wrapper { position: relative; } .avatar { display: block; border-radius: 50%; } .avatar-outline { position: absolute; left: 0; top: 0; width: 100%; height: 100%; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); border-radius: 50%; } |
このSVGのテクニックは一部の場所でしか使用されていないため、<img>と<div>を使用する理由はページサイズに関係しているのではないかと推測します。フィードのアバターにSVGを使用した場合、ユーザーが下にスクロールするとダウンロードされるKB数が増加します。
マージンの代わりにスペーサーdivを使用する
私はスペーサーgifの時代に生きていたわけではないのですが、それに関連していると思われるものを見つけました。とりあえず、スペーサーdivと呼ぶことにします。

Facebookの新しいUIデザイン
コンテキストを確認してみましょう。上記は、ホームページのフィードにランダムに表示される友達リクエストのセクションです。人物ごとにグリッドがあり、このグリッドには左からのマージンが必要です。通常これはmargin-left: 16px;で実装します。しかし、Facebookではコンテナの端とグリッドの間のスペースとして機能する<div>を使用しています。
その理由について疑問に思いますか?
- おそらく、彼らが構築したデザインシステムでは、コンテナ要素にマージンを追加することが許されていないのではないでしょうか?
- もしくは、幅を指定することで他の場所にも使用できるリアクションコンポーネントでしょうか?
なぜ、マージンを使用しないのでしょう? 私が見た限りではCSS(約10万行)にはユーティリティのクラスがたくさんあり、ラッパーにクラスを追加することで簡単にマージンを与えることができます。
CSSフィルターの使用

Facebookの新しいUIデザイン
4つのアイコンが配置されています。両端のプラスと矢印は画像で、メッセンジャーと通知はSVG要素です。なぜミックスされているのか理由が分かりません。
右端の矢印をクリックすると、アイコンのカラーがブルーに変わります。これは画像ですが、どのようにカラーを変更しているのだろうと思いました。ホバーすると画像が置換される? まさかと思ってみたら、画像のカラーを変更するためにCSSフィルターを使用していることに気がつきました。
1 2 3 |
.icon { filter: invert(39%) sepia(57%) saturate(200%) saturate(200%) saturate(200%) saturate(200%) saturate(200%) saturate(147.75%) hue-rotate(202deg) brightness(97%) contrast(96%) } |
これはFacebook.comの実装コードです。私は非常に奇妙だと思いました。SVGアイコンにしてfillで塗りつぶしのカラーを変更するのは難しいのでしょうか?
これは確認済みのアカウントのアイコンにも使用されています。

確認済みのアカウントのアイコンも画像要素
ユーザープロフィールも同じです。
1 2 3 |
.icon { filter: invert(59%) sepia(11%) saturate(200%) saturate(135%) hue-rotate(176deg) brightness(96%) contrast(94%); } |

ユーザープロフィールのアイコンも画像要素
CSSフィルターでブラックの画像を任意のカラーに変更する方法について詳しく知りたい人は、stackoverflowの記事が役立ちます。変換するコードを生成する便利なツールもあります。
参考: How to transform black into any given color using only CSS filters
The <img> icons with CSS filters are some wizardry dreamed up by @jsu07 to make legacy icons work in all colors and in dark mode until we can convert them to SVGs in React. https://t.co/rD6KknZvKI
— Frank Yan (@frankyan) April 3, 2020
シャドウに画像を使用する

Facebookの新しいUIデザイン
ヘッダにはシャドウがありますが、これはCSSのbox-shadowだと思いますよね?
答えはノーです。これはリピート画像を背景にした<div>で実装されています。
その画像をダウンロードしたので、ご覧ください。

シャドウに使用している画像(拡大)
この2px x 14pxの画像を繰り返して使用しています。さらに、そのシャドウ専用の<div>があります。これを使用する意味は何でしょう?
FacebookのRoyi Hagigiによると、シャドウに画像を使用した理由はパフォーマンスのためであることが分かりました。小さなシャドウがそのようなパフォーマンスの問題を引き起こすというのは興味深いことです。
What!? How strange... using an image for the main header drop shadow!
— Marais (@codervandal) April 2, 2020
それをどのようにテストしたかと尋ねたら、こう答えました。
I don’t know if this is a silly question, but how do you guys test that?
— Matheus Marques (@matiosfm) April 3, 2020
全くもって同意見です。
CSS変数の多用
CSS変数を使用しているのは良いことだと思います。私が見たところ、320以上の変数が:root要素に定義されており、ダークモードにも使用されます。
ダークモードに切り替えられると、__fb-dark-modeクラスがHTML要素に追加され、下記のように:rootで定義されているすべての変数が上書きされます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
:root { /* Light mode variables */ -fds-active-icon: #3578E5; --fds-attachment-footer-background: #F2F3F5; --fds-blue-05: #ECF3FF; --fds-blue-30: #AAC9FF; --fds-blue-40: #77A7FF; } .__fb-dark-mode:root, .__fb-dark-mode { /* Override the light variables */ --fds-active-icon: black; --fds-attachment-footer-background: black; --fds-blue-05: black; --fds-blue-30: black; --fds-blue-40: black; } |
ダークモードに切り替えられた瞬間を動画にしてみました。
ダークモードに切り替えられた瞬間
テキストの省略表示(複数行テキストの切り捨て)

Facebookの新しいUIデザイン
サイドバーには、ユーザープロフィール、最新情報、思い出などのリンクが一覧表示されています。そこで私が気がついたのは、複数行テキストの切り捨てです。
1 2 3 4 5 |
.element { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } |
このスタイルシートはインラインスタイルとして追加されており、ブラウザによって変化します。下記のモックアップを参照してください。

ブラウザによってスタイルシートが変化する
line-clampはブラウザのサポートがかなり良いです。CanIUseによると、すべての主要なブラウザでサポートされており、プレフィックス付きで利用できます。line-clampの使い方は、下記をご覧ください。
参考: line-clampプロパティの効果的な実装方法、CSSで文末を3点リーダーで省略表示する
ホバーエフェクトにdivを使用する
通常わたし達は、CSSでホバーエフェクトを実装します。例えば、ホバー時にグレーにしたい場合は下記のように記述します。
1 2 3 |
.element:hover { background: #ccc; } |
しかし、Facebookのような大規模サイトでは実用的ではないようです。いろいろチェックしているうちに、ホバー時にしか表示されない要素があることに気がつきました。その主な仕事は、ホバー要素をアクティブにすることです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.hover-div { position: absolute; right: 0; left: 0; top: 0; bottom: 0; pointer-events: none; border-radius: 6px; inset: 4px 0px; background-color: var(--hover-overlay); transition-property: opacity; transition-timing-function: var(--fds-animation-fade-out); cursor: pointer; } |
この要素はJavaScriptで不透明度を0から1に変更しているので、少し調べてみたところ、多くのコンポーネントで頻繁に使用されていることが分かりました。この要素が使用されている場所は、下記の通りです。

使用されているコンポーネントの一覧
多くの要素に対して同じホバーエフェクトを持たせる一貫性とシンプルさに私は気に入っています。これが何を意味するのかは、デザイン言語が一貫しており、システムが慎重に設計されたことを意味します。Facebookはよくやったと思います!
insetプロパティの使用
insetプロパティは、top, right, bottom, leftの4つのプロパティの省略形です。下記のように使用します。
1 2 3 4 |
.element { inset: 4px 0; /* これと同等: top: 4px, bottom: 4px, left: 0, right: 0 */ } |
このinsetプロパティは一部の要素のホバーdivで使用されており、HTMLではインラインで追加されています。下記のコンポーネントで気がつきました。

insetプロパティが使用されているコンポーネント
CanIUseによると、insetはFirefox 66以降でのみサポートされています。
dir=autoとCSSの論理プロパティ
Facebookのような多言語サイトの場合、コンテンツがどのようになるのか予測がつかないことがあります。例えば、投稿コンポーネントのユーザー名にdir="auto"があります。これはテキストの方向が言語に応じて変わるということです。日本語や英語の場合は左から右に、アラビア語の場合は右から左になります。
さらにインラインのCSSにもテキストの方向を変更するCSSがあります(dir="auto"だけでは不十分なようです)。
1 |
<div dir="auto" style="text-align: start;">محتوى بالعربية</div> |
text-align: start;があることに注目してください。これはCSSの論理プロパティで、LTRレイアウトにおけるtext-align-right;と同等です。
RTLのスタイルついてさらに詳しく知りたい人は、それについて詳細なガイドを書きました。
動的なカバー写真の背景

Facebookの新しいUIデザイン
カバー写真のメインカラーに近い色のグラデーションがあることに気がつきましたか? これはカバー写真に基づいて動的に生成される背景です。
どのように実装されているのでしょうか?
1. ドミナントカラーの取得
最初に、カバー写真からドミナントカラー(主要な色)を取得する必要があります。彼らはそのカラーだけで小さなカバー写真を作成します。

ドミナントカラーだけで小さなカバー写真を作成
2. ドミナントカラーで背景を生成
次に、ドミナントカラーで背景を生成します。カバー写真を明確にするために、元のカバー写真をホワイトの枠で強調表示します。

ドミナントカラーで背景を生成
3. 背景にグラデーションを追加
背景の上にグラデーションを追加します。
1 2 3 |
.element { background-image: linear-gradient(to top, #FFFFFF, rgb(255, 255, 255), rgba(255,255,255,0.7), rgba(255,255,255,0.4), rgba(255,255,255,0)); } |

背景にグラデーションを追加
デザインが暗い場合、グラデーションはホワイトからブラックに反転します。
1 2 3 |
.element { background-image: linear-gradient(to top, #000, rgb(0, 0, 0), rgba(0,0,0,0.7), rgba(0,0,0,0.4), rgba(0,0,0,0)); } |

デザインが暗い場合は、反転させる
Color Thiefを使用すると、簡単に画像からドミナントカラーを取得できます。
複数のbox-shadow

複数のbox-shadowを使ったボックス
ドロップダウンメニューのような要素に与えたシャドウの実装方法が気に入りました。シャドウは奥行きを感じさせ、通常のシャドウにこだわるよりもはるかにリアルだと思います。

Facebookの新しいUIデザイン
1 2 3 |
.element { box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.5); } |
このCSSを見て不思議に思うかもしれません。なぜ透明度50%のホワイトのinsetシャドウがあるのでしょうか? これはダークモード用でしょう。シャドウを拡大するとそれが分かります。

拡大したシャドウ
賢いですね、私はこれを気に入りました!
Flexboxのグリッドと空要素
すべてのグリッドがFlexboxで実装されていることに気がつきました。中でも私が注目したのは、Photosページのグリッドです。
Flexboxの実装については、下記をご覧ください。

Facebookの新しいUIデザイン
1 2 3 4 5 6 7 8 9 |
.wrapper { display: flex; flex-wrap: wrap; justify-items: space-between; } .item { width: 205px; } |
写真が少ない場合にはレイアウトが崩れる可能性があるので、間隔を空けるためにspace-betweenを使用するのは危険です。どのように失敗するかモックアップを作成しました。

写真が3枚だとレイアウトが崩れる
この問題をどのように解決していると思いますか? 4つの<div>の空要素があり、それぞれ写真と同じ幅を持っています。HTMLは下記の通りです。
1 2 3 4 5 6 7 8 9 10 |
<div class="wrapper"> <div class="item"><a href="#"><img src="photo.jpg"></a></div> <div class="item"><a href="#"><img src="photo.jpg"></a></div> <div class="item"><a href="#"><img src="photo.jpg"></a></div> <div class="item"><a href="#"><img src="photo.jpg"></a></div> <div class="empty"></div> <div class="empty"></div> <div class="empty"></div> <div class="empty"></div> </div> |
こうすることで空のdivが偽のアイテムとして機能し、アイテム間の間隔を等しく保つことができます。
垂直メディアクエリの使用
私にとっては、垂直のメディアクエリが実際に使用されているのはめったに見かけません。このテクニックをニュースフィードの幅を短くするために使用しているが気に入りました。
使用されているCSSは、下記の通りです。
1 2 3 4 5 |
@media (min-height: 700px) { .element { width: 584px; } } |
以上です。私はこの記事を取り組むことを楽しみ、多くを学びました。あなたにとって有用なテクニックがあれば、幸いです。
コメントや提案があれば、@shadeed9までお願いします。
sponsors