ウマ娘プロジェクトでは毎年4月1日に特設Webサイトが期間限定で公開されている
その特徴のひとつに比較的モダンな技術スタックで制作されていることが挙げられる
2024年にはNuxt.jsが採用されていたが今回はAstroが採用されている
リリース4周年時にはDMMGAMESの特設サイトもAstroでリプレイスされている
Webフロントエンド部門のtechブログや講演が無いか探したが2025年4月時点では発見できなかった
そこで筆者の理解できる範囲で本サイトの構造を調査した
Astro活用例
Astro自体の説明は割愛する
スコープ付きスタイル
Astroコンポーネントでstyleタグを記載するとそのコンポーネント専用のスタイルとなる
スコープされたコンポーネントにはdata-astro-cid-xxx
属性が付与される
Astro ImageTools
画像アセットを様々なサイズ・拡張子で出力して表示の出し分けが可能
該当要素にはクラス名にastro-imagetools-picture
が付与される
マークアップ大観
ここから主にHTML/CSSの構造に注目する
本サイトはおよそ5つのブロックから構成されていた
(本記事では上から2つまでに絞って記載する)
下記classがsection要素に追加される
(参考:hoisted.*.js)
- ページローディング完了 →
scene-sound
- 音声ON/OFF選択完了 →
scene-kv
- kv(キービジュアル)アニメ終了 →
scene-end
attentionコンポーネント
横幅900px以下の横向き時のみ表示される(CSSで制御)
また端末の向きを変えたり一定の画面サイズに変化することで再読み込みが走る
introコンポーネント
ローディング→音声選択→アニメーション実行 までの表示を担う
loadingコンポーネント
ページ読み込み中に表示されるCSSアニメーション
animation-delay
で周期ズレの実現
sound-selectコンポーネント
transform
でコンポーネント自身を上下左右中央揃え
{
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
videoコンポーネント
シンプルなvideo要素
黒背景の上からopacity
20%の被せで控えめな表示
kvコンポーネント
音声ON/OFFが選択されるまではopacity
とvisibility
で非表示
section要素にscene-kv
クラスが付与されてからアニメーションタイムラインが開始される
最初に[ぼかし背景]と[中央ロゴ]を表示
そのあと[通常背景]と[斜めロゴ]、[キャラ画像]を表示という流れ
横幅がおよそ768px以上の場合フェードインとスケールダウンアニメが入るが、
768px未満の場合は左から右へスライド移動するアニメが入る
また数秒経過するかSKIPボタンを押下するとkvコンポーネントがフェードアウトする
その後introコンポーネント自体が消滅する
アニメーション
画像のアニメーションは基本transition
で制御されている
<div>
<figure>
<!-- ここからAstro ImageTools -->
<picture>
<source/>
<source/>
...
<img/>
</picture>
<!-- ここまでAstro ImageTools -->
</figure>
</div>
共通セットアップ処理
本サイトの画像要素のほとんどに仕込まれているアニメ
画像読み込み中
figure picture {
opacity: calc(1 - var(--opacity, 1));
transition: opacity .3s cubic-bezier(.165,.84,.44,1);
}
var(--opacity, 1)
とあるが画像読み込み完了前は変数--opacity
が与えられていないため、第二引数の1
が選ばれる
よってこの時点ではopacity
は0
画像読み込み完了後
<img
src="(略)"
onload="parentElement.style.setProperty('--z-index', 1);
parentElement.style.setProperty('--opacity', 0);"
/>
画像読み込みが完了するとonload
が発火して親picture要素にスタイル情報が付与される
※ onload
の値もAstro ImageToolsによって自動生成されている
figure picture {
opacity: calc(1 - var(--opacity, 1));
transition: opacity .3s cubic-bezier(.165,.84,.44,1);
}
初期状態とスタイルは同一だが、
CSS変数が与えられた瞬間から0.3秒かけてopacity
が1
になる
これにて画像の準備が完了する
中央ロゴのアニメーション
フェードイン開始前
img {
opacity: 0;
}
scene-kv
クラスが与えられる前の状態
フェードイン+スケールアップ
.scene-kv img {
animation: anime-logo 4s .3s both;
}
@keyframes anime-logo {
0% {
opacity: 0;
transform: translateZ(0) scale(.5);
animation-timing-function: cubic-bezier(.895,.03,.685,.22)
}
12% {
opacity: 1;
transform: translateZ(0) scale(.9);
animation-timing-function: cubic-bezier(.25,.25,.75,.75)
}
to {
opacity: 1;
transform: translateZ(0) scale(1.2)
}
}
scene-kv
クラスが付与されてから0.3秒待機してフェードイン+スケールアップアニメーションを4秒間実行
0.3秒待つことで必ず親picture要素のセットアップを終えてからアニメを実行する
フェードアウト
.scene-kv div {
opacity: 0;
transition: opacity .5s 2.2s cubic-bezier(.19,1,.22,1);
}
scene-kv
クラスが付与されて2.2秒経過後に親div要素にてフェードアウトアニメを0.5秒間実行
フェードインアニメを仕掛ける先は 子要素(img/picture要素)
フェードアウトアニメを仕掛ける先は 親要素(ラッパー/コンテナ要素)
キャラ画像のアニメーション(PC版)
div {
opacity: 0;
transform: translateZ(0) scale(1.15);
}
若干拡大させた状態で待機
.scene-kv div {
opacity: 1;
transform: translateZ(0) scale(1);
transition: opacity 1.8s 2.2s cubic-bezier(.165,.84,.44,1), transform 2s 2.2s cubic-bezier(.77,0,.175,1);
}
scene-kv
クラスが付与されてから2.2秒待機してフェードイン+スケールダウンアニメーションを1.8秒間実行
このように待機秒数を設定することにより[キャラ画像]が[中央ロゴ]と入れ替わる形で出現する
キャラ画像にフェードアウトアニメが存在しないためか
このフェードインアニメは 親要素(ラッパー/コンテナ要素) に仕掛けられている
まとめ
導入部分を軽く見ただけでも新しい技術スタックや個人的に参考になる点が多く見受けられ、最高のコンテンツを作る会社というビジョンに嘘偽りは無いと実感した
本記事で記載した内容は一部にすぎないがアニメーションの実装内容については多少追跡しやすくなったのではないだろうか
今後の特設サイトにもぜひ注目したい