GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

【コピペOK】CSSヒーローセクション アニメーション9選

ヒーローセクションとは、サイトを開いた瞬間に画面いっぱいに映る最初の一枚、大きな見出しとボタンが乗ったあの一画面のことです。この記事では、そこに動きを添える「ヒーローセクション アニメーション」を集めました。CSSだけ・JS不要で、そのまま貼り付けて動く9つです。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つは人気順ではなく、訪問者が画面を開いた瞬間から出会う順番に並べています。開いてすぐ目が行く見出し(01)から始まり、背景の静かな動き(02)・視線が止まるCTAボタン(03)を経て、プロダクトを見せるモックアップ(04)と目を引くバッジ(05)で信頼を積み重ねます。その下へスクロールすると流れていく連携先ストリップ(06)までが読み込み直後の順番で、残り3つ(07〜09)はユーザーが実際にスクロールしたり何かを保存したりしたときに反応します。最初の画面を何枚も回すつもりならCSSカルーセルがその枠を作り、下へ続く物語の順番を見せたいならタイムラインのアニメーションが次の枠を埋めます。

01見出しが順番に登場

ヒーローセクション デザイン のどこで使うか

ヒーローの見出しが3行同時にではなく、1行ずつ少し遅れて登場します。

見出しが順番に登場
新しいタブで開く

01 · 見出しが順番に登場 — 計測 累積面積 13.299% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

@keyframesopacity · translateYstagger delay

動く仕組み

行ごとに別々の@keyframesを用意し、10%・20%・30%の地点から動き出すタイミングをずらすことで、opacityとtranslateYの2つだけで順番の登場を作っています。

.hero__line {
  opacity: 0;
  transform: translateY(24px);
}
.hero__line--1 {
  animation: line-in-1 2s $ease-spring infinite;
}
@keyframes line-in-1 {
  0%   { opacity: 0; transform: translateY(24px); }
  10%  { opacity: 1; transform: translateY(0); }
  85%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(24px); }
}

02背景グラデーションの漂い

何が動くか

カードよりもずっと大きなconic-gradient層を::beforeに敷き、overflow: hiddenで切り取った状態でtranslateとrotateだけをゆっくり動かします。

背景グラデーションの漂い
新しいタブで開く

02 · 背景グラデーションの漂い — 計測 累積面積 23.185% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

::beforeconic-gradienttranslate · rotate

組み立て方

background-positionを動かす方法と違って、カードのサイズが変わってもグラデーションの計算が崩れません。

.hero::before {
  content: '';
  position: absolute;
  inset: -60% -60%;
  background: conic-gradient(from 0deg, #ff477e, #7b2ff7, #4cc9f0, #ff477e);
  animation: drift 2s $ease-out infinite;
}
@keyframes drift {
  0%   { transform: translate(-6%, -4%) rotate(0deg); }
  50%  { transform: translate(6%, 5%) rotate(180deg); }
  100% { transform: translate(-6%, -4%) rotate(360deg); }
}

03CTAパルスグロー

どこで使うか

ボタンがscaleとbox-shadowを一緒に行き来させながら、呼吸するようにパルスします。

CTAパルスグロー
新しいタブで開く

03 · CTAパルスグロー — 計測 累積面積 8.864% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

box-shadowscale:hover · :focus-visible

なぜこう書くか

ループ用のアニメーションとは別に:hoverと:focus-visibleにも実際に反応が入っているので、ループを止めてもボタン自体はちゃんと生きています。

.cta {
  animation: pulse 2s $ease-pop infinite;
}
.cta:hover {
  transform: translateY(-2px);
}
@keyframes pulse {
  0%   { transform: scale(1);     box-shadow: 0 0 0 0 rgba(255, 224, 102, .55); }
  50%  { transform: scale(1.045); box-shadow: 0 0 32px 8px rgba(255, 224, 102, .55); }
  100% { transform: scale(1);     box-shadow: 0 0 0 0 rgba(255, 224, 102, 0); }
}

04モックアップフロート

見えるもの

ブラウザの上部バーが付いたモックアップカード全体が、translateYとごくわずかなrotateを行き来しながらふわふわ浮いているように見えます。

モックアップフロート
新しいタブで開く

04 · モックアップフロート — 計測 累積面積 16.142% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transform: translateYrotate@keyframes

実装の核

どちらもtransformだけを動かしているので、レイアウトの再計算は発生しません。

.mockup {
  animation: float 2s $ease-spring infinite;
}
@keyframes float {
  0%   { transform: translateY(0) rotate(0deg); }
  50%  { transform: translateY(-16px) rotate(-1.2deg); }
  100% { transform: translateY(0) rotate(0deg); }
}

05バッジスライドイン

どんな場面か

NEWバッジが横から滑り込んできて、しばらく留まってからまた滑り出ていく動きを繰り返します。

バッジスライドイン
新しいタブで開く

05 · バッジスライドイン — 計測 累積面積 1.14% · 動いたフレーム 9/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transform: translateXopacitykeyframe hold

何が肝心か

30%〜65%の区間をあえて停止させているので、ノンストップで往復するのではなく、読める時間を確保してから消えます。

.badge {
  animation: slide-in 2s $ease-pop infinite;
}
@keyframes slide-in {
  0%   { opacity: 0; transform: translateX(40px); }
  30%  { opacity: 1; transform: translateX(0); }
  65%  { opacity: 1; transform: translateX(0); }
  100% { opacity: 0; transform: translateX(40px); }
}

06マーキーストリップ

何が起きるか

ヒーロー下の連携先ピルを2セットつなげたトラックが、translateX(-50%)で途切れなく流れます。

マーキーストリップ
新しいタブで開く

06 · マーキーストリップ — 計測 累積面積 7.556% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translateX(-50%)margin-right gapdoubled track

押さえておく点

間隔はflexのgapではなくピルごとのmargin-rightで取っていて、gapを使うとセットの継ぎ目だけ間隔が半分になり、一周するたびに位置がずれてしまいます。

.marquee__track {
  display: flex;
  width: max-content;
  animation: pill-scroll 2s linear infinite;
}
.marquee__pill {
  margin-right: $sp-4; // gapではなくmargin-right
  flex-shrink: 0;
}
@keyframes pill-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

07スクロールで文字が背後にフェード

画面で起きること

本番のサイトならscroll-timelineがヒーロー見出しの大きさ・不透明度・位置をスクロールの進み具合に直接結びつけます。

スクロールで文字が背後にフェード
新しいタブで開く

07 · スクロールで文字が背後にフェード — 計測 累積面積 13.056% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scale · opacitytranslateYtransform-origin: center top

どう作るか

ただブラウザごとの対応差がまだ大きいため、このデモでは同じ見た目を@keyframesの繰り返しループで代わりに再現しています。

.hero {
  transform-origin: center top;
  animation: recede 2s $ease-out infinite;
}
@keyframes recede {
  0%   { opacity: 1;   transform: scale(1)   translateY(0); }
  55%  { opacity: .15; transform: scale(.85) translateY(-40px); }
  100% { opacity: 1;   transform: scale(1)   translateY(0); }
}

08分割テキストマスクリビール

見え方

見出しがclip-path: inset()の右側の値を100%から0%へ狭めることで、左から右へ払われるように現れます。

分割テキストマスクリビール
新しいタブで開く

08 · 分割テキストマスクリビール — 計測 累積面積 2.719% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-path: inset()scaleX underlinesweep reveal

コードの読み方

同じタイミングで下の下線もscaleXで伸びていくので、テキストと下線が息を合わせて登場します。

.headline__mask {
  clip-path: inset(0 100% 0 0);
  animation: reveal 2s $ease-spring infinite;
}
.headline__underline {
  transform: scaleX(0);
  animation: underline-grow 2s $ease-spring infinite;
}
@keyframes reveal {
  0%   { clip-path: inset(0 100% 0 0); }
  40%  { clip-path: inset(0 0% 0 0); }
  85%  { clip-path: inset(0 0% 0 0); }
  100% { clip-path: inset(0 100% 0 0); }
}

09@starting-styleでロード時演出

いつ使うか

通知トーストがdisplay: noneから画面に現れるその瞬間の遷移そのものを、@starting-styleとtransition-behavior: allow-discreteだけでキーフレームなしに作っています。

@starting-styleでロード時演出
新しいタブで開く

09 · @starting-styleでロード時演出 — 計測 累積面積 2.515% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

@starting-styletransition-behavior: allow-discretedisplay transition

コードの要点

デモの中で繰り返し登場しているのは、二重のrequestAnimationFrameでis-hiddenクラスを350ミリ秒ごとに付け外しする8行ほどのJSのおかげで、実際のサービスでトーストが一度だけ新しく現れる瞬間にはこのJS自体が不要です。

@supports (transition-behavior: allow-discrete) {
  .toast {
    transition: display .22s $ease-pop, opacity .22s $ease-pop, transform .22s $ease-pop;
  }
  .toast.is-hidden {
    display: none;
    opacity: 0;
    transform: translateY(12px) scale(.92);
  }
  @starting-style {
    .toast { opacity: 0; transform: translateY(12px) scale(.92); }
  }
}

ヒーローセクションアニメーションはどこで壊れるか

何が問題か

ヒーローセクション 参考 を集めながら09番を作っているときに驚いた落とし穴が一つあります。@starting-styleは、あとからDOMに追加された要素だけでなく、ページを開いた瞬間にすでに存在していた要素の最初のスタイル計算にも効くという点です。ページを開いて繰り返し用のJSが一度も走っていない段階なのに、トーストがもういちど登場の遷移を自分から再生していました。@starting-styleは「新しく描画されたように見える」あらゆる瞬間に働き、そこにはページが描かれた最初のフレームも含まれるということです。

どう避けるか

最初の演出をコンポーネントの中へ移したときに起きる再実行の問題は、ライブラリなしのReact CSSアニメーションでマウント時点を基準に整理しました。

つまりこの繰り返し用JSは登場を「はじめて作る」コードではなく、CSSがすでに一度やってのけたことを「もう一度見せる」ためのコードに近いといえます。9つ分のファイルが入ったzipの解凍パスワードは5p7qvp7zで、渡されたファイルにそのまま入力すれば開きます。03番のbox-shadow、08番のclip-pathにも似た種類の落とし穴があり、GPUが合成だけで済ませられるtransform・opacityと違って毎フレーム塗り直しが必要なので、レイヤーを欲張って重ねるほど非力な端末で先に動きの重さが目立ちます。

アクセシビリティ — 9つがreduced-motionで残すもの

reduced-motion で残るもの

システムで動きを減らす設定がオンになっていると、9つ全部でanimationがまるごとnoneになり止まります。01・02・03・04・05・07・08のような装飾的な動きはそのまま止まっていれば十分ですが、09だけはtransitionも切って.toastを常に表示状態のまま残し、動きが苦手な人にも通知の内容そのものは欠けずに届くようにしてあります。

あわせて読みたい

ボタン自体の反応を集めたパターンはCSSボタンアニメーション、同じtranslateX(-50%)の無限スクロールをロゴ以外にも応用した例はCSSマーキー無限スクロールにまとめています。

FAQ — よくある質問

ファーストビュー アニメーション css だけで作れますか?

はい、9つ全部がCSSだけで動きます。唯一09番だけがデモを繰り返し再生するために8行ほどのJSを添えていますが、実際のサービスでトーストが一度新しく現れる瞬間にはこのJS自体が要りません。

@starting-styleはどのブラウザで使えますか?

Chrome・Edgeの117以降で使えます。MDNの@starting-styleに対応状況がまとまっていて、未対応のブラウザでは09番のデモが@supportsブロックの外に出て、遷移なしで常に表示された状態のまま残ります。

スクロールアニメーションなのに、実際にスクロールしなくても動くのはなぜですか?

07番は本番のサービスならscroll-timelineでスクロールの進み具合に直接結びつきますが、このデモはキャプチャとブラウザ間の互換性のために、同じ見た目を時間ベースの@keyframesループで代わりに再生しています。実際に組み込むときはMDNのscroll-driven animationsを参考にanimation-timeline: view()へ置き換えれば動きます。日付や申込ボタンまで載る最初の画面が要るなら、イベントページのテンプレートに完成したセクションがあります。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。