こういう見た目のやつ
僕の Next.js で作ったポートフォリオサイト https://pokutuna.com/ では、blog エントリ情報や github の活動は、Cloud Firestore から読み込んで表示していて、その読み込み中の表示を作る話です。
よくあるのは、くるくる回るローディングインジケーター
わかりやすいけど、ロード完了後に大きくレイアウトが変わってしまう。ガタッと見た目が変わるのは WebVitals の Cumulative Layout Shift 的にも良くない。
ガタツキを減らすには実際のコンテンツと高さのプレースホルダーを置いておくのが良くて、適当に領域を確保するのが簡単だけど、記事であることは分かりたい。しかし画像でプレースホルダを作ると追加の画像読み込みが発生してしまうし(SVG で埋め込んでおく手もある)、記事の見た目を変えた際に画像もメンテナンスする必要が出てくる。
YouTube や Facebook はロード中にこういうプレースホルダを出している。
こういう雰囲気の表示を、手を抜きつつ、ちょっと CSS 書けばできないかなと思って作ってみたのがこちら
ロード後にこういう表示をするとして
See the Pen entry-content by pokutuna (@pokutuna) on CodePen.
ロード中はこういう感じにする
See the Pen entry-placeholder by pokutuna (@pokutuna) on CodePen.
ちょっと濃すぎるとかうるさすぎる感じはあるけど、手抜きで作れる割にはいい感じなんじゃないでしょうか。
テキストコンテンツのプレースホルダ
インライン要素、フレージングコンテンツは、文字色を透明にして、背景を塗る。
line-height
分塗るとかなり太く見えるので、linear-gradient
で上下 20% ずつぐらい透明にしてあげる
水色部分を透明に、黄色い部分だけコンテンツの色で塗る。
@mixin placeholder-text($color) { color: transparent; background: linear-gradient(transparent 20%, $color 20%, $color 80%, transparent 20%); } .placeholder { user-select: none; a { @include placeholder-text(#008080); text-decoration: none; pointer-events: none; } p, time, span.content { @include placeholder-text(gray); } p { display: inline; } i, svg { color: gray; } * { text-overflow: unset; } }
要素に直接スタイルを当てる是非はおいておいて、sass/scss で書くとこのぐらい、.placeholder
を当ててつかう。生の css で書いてもほぼ変わらない記述量でしょう。
user-select: none
でテキストを選択できないようにするtext-decolation: none
で下線とか消すpointer-events: none
でリンククリックできないようにtext-overflow: unset
で省略時の...
表示を消す
などのちまっとした作業をしたり、
マークアップにもよるけど、
p
要素を inline にする- アイコン(
i, svg
)は代替のアイコンを出すので色だけ変えておく span.content
は塗りたい span と塗りたくないのを雑に.content
で区別- 画像出す領域の背景はそれっぽい色で塗っておく
みたいな感じです。
@keyframes placeholder-blink { 0% { opacity: 0.8; } 50% { opacity: 0.6; } 100% { opacity: 0.8; } }
あとはこういうアニメーションを当ててフワフワさせている。左から右にテカッとしたハイライトが動くようにできるともっとかっこいいかも。
ロード中はこういうスタイルを当てた要素を置いておくと見た目がいい。
React コンポーネントに空のデータを埋める
React で作っていたので、プレースホルダ用にダミーデータを埋めた props を作って渡すようにする。
\u2003
は m 幅スペース(EMSPACE)で、別に全角スペースとかでもなんでもいい。useQuery
が loading
の時はこういう props を渡したコンポーネントをプレースホルダとして出しておくと簡単ですね。不揃いな感じにしたいなら乱数で幅を持たせてもいい。
export function blank(len: number): string { return '\u2003'.repeat(len); } export const entryPlaceholder: Entry = { id: '', date: new Date(0), blog: '', item: { title: blank(14), link: '#' categories: [blank(5), blank(5), blank(5)], contentSnippet: blank(100), enclosure: {}, }, };
おわりに
ということでロード中の表示を作ってみました。
とはいえポートフォリオサイトなんて全員に同じデータを返すわけだし、定期的に Static Generation しておいたほうがパフォーマンスは稼げると思う。するとデータの更新と、コンテンツの再生成をどうやるかという話になるわけだけど、先月リリースされた Next.js 9.5 から Incremental Static Generation が stable になったので使ってみてもいいかもしれませんね。