こんにちは、22新卒のGMOアドマーケティングの天河です。
みなさん、普段スケルトンスクリーンローディングはどうやって実装していますか?
こういうやつ
大体の方は、ライブラリでクラスぽいっと入れ込んで、秒で実装完了!という感じですよね。仮にそのライブラリを使用せず、生のJavaScriptとCSSのみで書けって言われたらどうでしょう。
ということでさっそく書いてみました。
完成
HTML
1 2 3 4 5 6 7 |
<div class="_article _article_skelton"> <div class="_article_art_img"></div> <div class="_article_content"> <div class="_article_art_title"></div> <div class="_article_art_optional"></div> </div> </div> |
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
._article { display: flex; margin: 0 auto; margin-top: 50px; width: 80%; height: 130px; border: 1px solid; } ._article_art_img { margin: 5px; height: 120px; width: 120px; } ._article_content { width: calc(100% - 162px); margin: 16px; } ._article_art_title { height: 24px; margin-bottom: 8px; } ._article_art_optional { height: calc(100% - 32px); } /* スケルトンローディングCSS */ /* アニメーション定義 */ @keyframes skeleton-animation { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } ._article_skelton ._article_art_img, ._article_skelton ._article_art_title, ._article_skelton ._article_art_optional { position: relative; overflow: hidden; background-color: #eee; } ._article_skelton ._article_art_img::before, ._article_skelton ._article_art_title::before, ._article_skelton ._article_art_optional::before { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 100; content: ""; display: block; background: linear-gradient( 90deg, rgba(255,255,255,0), rgba(255, 255, 255, 0.4), rgba(255,255,255,0) ); animation: skeleton-animation 1.2s linear infinite; } |
See the Pen skelton loading(ベース) by 10K (@10JII_K) on CodePen.
コード量は多く見えますが、スケルトンスクリーンの実現をするコード自体は少ないです。
やった〜〜〜
以上です。
えっ、解説??
はい、解説しますね。
実装
読み込み完了後にこういう画面を表示したいとします。このベースとなるデザインのHTMLとCSSの該当部分は以下の通りです。
1 2 3 4 5 6 7 |
<div class="_article _article_skelton"> <div class="_article_art_img"></div> <div class="_article_content"> <div class="_article_art_title"></div> <div class="_article_art_optional"></div> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
._article { display: flex; margin: 0 auto; margin-top: 50px; width: 80%; height: 130px; border: 1px solid; } ._article_art_img { margin: 5px; height: 120px; width: 120px; } ._article_content { margin: 16px; width: calc(100% - 162px); } ._article_art_title { margin-bottom: 8px; height: 24px; } ._article_art_optional { height: calc(100% - 32px); } |
このベースのデザインの読み込み中の画面をこうしたいと私たちは思っています。
処理の手順としては、
- データ読み込み(ローディング)
- データをDOMに挿入
- ローディングを除去
スケルトンスクリーン
上記のHTMLにて、最上階層にあるクラスのうち
1 |
_article_skelton |
がローディングを表示するためのクラスになります。
このクラスに関して以下のCSSを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* スケルトンローディングCSS */ ._article_skelton ._article_art_img, ._article_skelton ._article_art_title, ._article_skelton ._article_art_optional { position: relative; background-color: #eee; } ._article_skelton ._article_art_img::before, ._article_skelton ._article_art_title::before, ._article_skelton ._article_art_optional::before { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 100; content: ""; display: block; background: linear-gradient( 90deg, rgba(255,255,255,0), rgba(255, 255, 255, 0.4), rgba(255,255,255,0) ); animation: skeleton-animation 1.2s linear infinite; } |
まず ::before について説明します。
::before
疑似要素の一つです。似たものに疑似クラスというものがありますが、疑似クラス | HTML内の特定の状態(hover, 最初の行など)の要素にクラスを適用したかのように振る舞う |
疑似要素 | 既存の要素にクラスを適用するのではなく、まったく新しい HTML 要素をDOMに追加したり、要素の特定の部分にスタイル付けをするかのように振る舞う |
といった違いがあります。
ほかにも疑似クラスはコロン一つなのに対して疑似要素はコロンが二つといった違いがあります。
こいつらを使うと何が嬉しいかというと、別途JavaScriptを書いたりしなくても要素指定してスタイルを適応したりDOMを追加したっぽくできます。
この::beforeは何ができるかというと、指定した要素の直前に、疑似要素を子要素として挿入します。
また、
- content がないと::beforeは表示されない
- ::beforeはインライン要素である
1 2 3 4 5 |
content: ""; display: block; width: 100%; // 親と同じデカさにする height: 100%; // 親と同じデカさにする |
linear-gradient
背景色としてこちらの見慣れないプロパティを指定しています。これはCSSの関数で、二つ以上の色を指定して線形グラデーション画像を生成します。
以下は例です。
See the Pen Untitled by 10K (@10JII_K) on CodePen.
この例のCSSでは, linear-gradient を
1 2 3 4 5 6 |
background: linear-gradient( 90deg, rgba(255,0,0,0), red, rgba(255,0,0,0) ); |
「左から右へ、赤(透明) → 赤 → 赤(透明) と変化している画像つくって〜〜〜」
巷に溢れている例では、rgb(255, 0, 0, 0) の部分を transparent と書いているケースが多いですが、FireFoxとかでは transparentは rgb(0, 0, 0, 0) と認識されるため、白ではなく黒色っぽい色が混ざってしまいます。
なので丁寧に、ベースとなる色のtransparent値を0に設定したrgbで書いてあげましょう。
つまりの最初の設定は
「左から右へ、白(透明)→ 白(ちょっと透明)→ 白(透明)と変化している画像つくって〜〜〜」
@keyframes
1 2 3 4 5 6 7 8 |
@keyframes skeleton-animation { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } |
かなり大雑把な説明なので、もっと知りたい方はとても詳しく解説しているこちらを参考にしてください。
【CSS3】@keyframes と animation 関連のまとめ
@keyframesの直後でアニメーション名を定義できます。
0%はアニメーションの開始、100%はアニメーションの終了を意味しており、それぞれにCSSの設定ができます。
ここで設定しているtransformプロパティは、要素を移動させることができます。translateXは水平方向に移動する距離を示します。
このアニメーションを 1周期 1.2秒で一定の速さでループさせます。
1 |
animation: skeleton-animation 1.2s linear infinite; |
しかしこれではまだ不完全です。
@keyframesで開始位置を -100% 終了位置を100%にしているため、他の要素と干渉するおそれがあります。
わかりやすく上記の例に色をつけて表示してみます。変えたのは ::before のbackgroundの色です。
See the Pen skelton ダブり by 10K (@10JII_K) on CodePen.
左のスケルトンの部分が右の要素にかぶさってしまっているのが確認できますよね。
なのでこのはみ出た部分は表示させないように、疑似要素(::before)の親要素に
1 |
overflow: hidden |
を付与してあげます。
See the Pen skelton ダブらない by 10K (@10JII_K) on CodePen.
これで理想のスケルトンができあがりました。
一応これでスケルトンスクリーンの実装は完了ですが、実際のサービスっぽく読み込み終了後にスケルトンスクリーンを除去してデータを挿入するJavaScirptも実装してみましょう。
JavaScriptの実装
このスケルトンスクリーンをOFFにするには
1 |
_article_skelton |
クラスをJavaScriptで取り除いてあげるだけです。
取り除くのと同時に取得したデータも挿入してあげましょう。例では便宜的に3秒経ったらスケルトンスクリーンを取り除くようにしています。
querySelectorAll は forEach は使えても map 関数などは使えない謎仕様なので、fromで配列に変えて処理してあげましょう(前回の記事のはてブコメントで教えてくださった方、ありがとうございます😊)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
window.setTimeout(() => { Array.from(document.querySelectorAll("._article_art_img")).map((imageDom) => { setArticleImage(imageDom); }); Array.from(document.querySelectorAll("._article_art_title")).map( (titleDom) => { setArticleTitle(titleDom); } ); Array.from(document.querySelectorAll("._article_art_optional")).map( (optionalDom) => { setArticleOptional(optionalDom); } ); Array.from(document.querySelectorAll("._article")).map((articleDom) => { articleDom.classList.remove("_article_skelton"); }); }, 3000); function setArticleImage(imageDom) { imageDom.style.backgroundImage = 'url("https://placehold.jp/e60613/ffffff/150x150.png?text=画像")'; imageDom.style.backgroundColor = "#eee"; imageDom.style.backgroundRepeat = "no-repeat"; imageDom.style.backgroundPosition = "center"; imageDom.style.backgroundSize = "90%"; } function setArticleTitle(titleDom) { titleDom.textContent = "タイトル"; } function setArticleOptional(optionalDom) { optionalDom.textContent = "ここに説明が入ります"; } |
これで完成です。
See the Pen skelton loading (完成) by 10K (@10JII_K) on CodePen.
ねっ、そんなに難しくないでしょ?
(地味にベースのデザインを整えるのが一番苦労した)
また、縦バージョンはこちらになります。
変更点としては、ベースのデザインの div構造と display を変更し、width の設定を無くしました。
_article_skeltonに関するCSSで変更しているのは、::beforeの width: 100% を除去したことだけです。
See the Pen skelton column(完成) by 10K (@10JII_K) on CodePen.
おまけ(背景)
GMOアドマーケティングではTAXELというプロダクトでレコメンドウィジェットを扱っています。こういうやつ
レコメンドウィジェットの仕組みを簡単に説明すると、
- メディア様が所有しているウェブサイトにこちらの用意した divタグと scriptタグを設置していただく
- そのdivタグが設置されたページにアクセスすると、divタグと一緒に設置したscriptタグが読み込まれ、レコメンド記事を格納しているサーバにリクエストが送られる
- サーバからのレスポンスの中にあるレコメンド記事のDOMが、設置した divタグ の中に配置される
メディア様の所有するウェブサイトには、私たちのレコメンドウィジェット以外にも、たくさんの他社のレコメンドウィジェットが設置されているケースが多いです。
そのためライブラリなどをこちらが気軽に設置してしまうと他社のウィジェットに影響を与えかねないので、生のJavaScriptでゴリゴリ書いている、という背景があります。
なのでスケルトンスクリーン一つ実装するのにも、ライブラリのクラス適応させてポンッというわけにもいかず、ゴリゴリJavaScriptを書いていくしかないのです。
ものすごくめんどくさくはありますが、定番UIの仕組みは学べていいかもしれませんね。
(ワンチャン、自分でOSS作る時の土台になるかも!?)
つくったもの
おわりに
明日はU.Yさんによる「GMO SSPのCloud(Google Cloud Platform)移行の失敗と成功のまとめ」です。引き続き、GMOアドマーケティング Advent Calendar 2022 をお楽しみください!
■学生インターン募集中!
https://note.gmo-ap.jp/n/nc42c8a60afaf
■エンジニア採用ページはこちら!
https://note.gmo-ap.jp/n/n02cbeb6edb0d
■GMOアドパートナーズ 公式noteはこちら!
https://note.gmo-ap.jp/