ぽくつなです

記事っぽい読み込み中プレースホルダをかんたんに作る

こういう見た目のやつ

f:id:pokutuna:20200827050801p:plain
loading

僕の Next.js で作ったポートフォリオサイト https://pokutuna.com/ では、blog エントリ情報や github の活動は、Cloud Firestore から読み込んで表示していて、その読み込み中の表示を作る話です。

よくあるのは、くるくる回るローディングインジケーター

わかりやすいけど、ロード完了後に大きくレイアウトが変わってしまう。ガタッと見た目が変わるのは WebVitals の Cumulative Layout Shift 的にも良くない。

タツキを減らすには実際のコンテンツと高さのプレースホルダーを置いておくのが良くて、適当に領域を確保するのが簡単だけど、記事であることは分かりたい。しかし画像でプレースホルダを作ると追加の画像読み込みが発生してしまうし(SVG で埋め込んでおく手もある)、記事の見た目を変えた際に画像もメンテナンスする必要が出てくる。

YouTubeFacebook はロード中にこういうプレースホルダを出している。

f:id:pokutuna:20200827065511p:plain
YouTube

こういう雰囲気の表示を、手を抜きつつ、ちょっと 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% ずつぐらい透明にしてあげる

hoge

水色部分を透明に、黄色い部分だけコンテンツの色で塗る。

@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)で、別に全角スペースとかでもなんでもいい。useQueryloading の時はこういう 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 になったので使ってみてもいいかもしれませんね。