GODRICH

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

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

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

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

01見出しが順番に登場

ヒーローの見出しが3行同時にではなく、1行ずつ少し遅れて登場します。行ごとに別々の@keyframesを用意し、10%・20%・30%の地点から動き出すタイミングをずらすことで、opacityとtranslateYの2つだけで順番の登場を作っています。

@keyframesopacity · translateYstagger delay
.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で切り取った状態でtranslaterotateだけをゆっくり動かします。background-positionを動かす方法と違って、カードのサイズが変わってもグラデーションの計算が崩れません。

::beforeconic-gradienttranslate · rotate
.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パルスグロー

ボタンがscalebox-shadowを一緒に行き来させながら、呼吸するようにパルスします。ループ用のアニメーションとは別に:hover:focus-visibleにも実際に反応が入っているので、ループを止めてもボタン自体はちゃんと生きています。

box-shadowscale: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を行き来しながらふわふわ浮いているように見えます。どちらもtransformだけを動かしているので、レイアウトの再計算は発生しません。

transform: translateYrotate@keyframes
.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バッジが横から滑り込んできて、しばらく留まってからまた滑り出ていく動きを繰り返します。30%〜65%の区間をあえて停止させているので、ノンストップで往復するのではなく、読める時間を確保してから消えます。

transform: translateXopacitykeyframe hold
.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%)で途切れなく流れます。間隔はflexgapではなくピルごとのmargin-rightで取っていて、gapを使うとセットの継ぎ目だけ間隔が半分になり、一周するたびに位置がずれてしまいます。

translateX(-50%)margin-right gapdoubled track
.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がヒーロー見出しの大きさ・不透明度・位置をスクロールの進み具合に直接結びつけます。ただブラウザごとの対応差がまだ大きいため、このデモでは同じ見た目を@keyframesの繰り返しループで代わりに再現しています。

scale · opacitytranslateYtransform-origin: center top
.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%へ狭めることで、左から右へ払われるように現れます。同じタイミングで下の下線もscaleXで伸びていくので、テキストと下線が息を合わせて登場します。

clip-path: inset()scaleX underlinesweep reveal
.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-styletransition-behavior: allow-discreteだけでキーフレームなしに作っています。デモの中で繰り返し登場しているのは、二重のrequestAnimationFrameis-hiddenクラスを350ミリ秒ごとに付け外しする8行ほどのJSのおかげで、実際のサービスでトーストが一度だけ新しく現れる瞬間にはこのJS自体が不要です。

@starting-styletransition-behavior: allow-discretedisplay transition
@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は「新しく描画されたように見える」あらゆる瞬間に働き、そこにはページが描かれた最初のフレームも含まれるということです。つまりこの繰り返し用JSは登場を「はじめて作る」コードではなく、CSSがすでに一度やってのけたことを「もう一度見せる」ためのコードに近いといえます。9つ分のファイルが入ったzipの解凍パスワードは5p7qvp7zで、渡されたファイルにそのまま入力すれば開きます。03番のbox-shadow、08番のclip-pathにも似た種類の落とし穴があり、GPUが合成だけで済ませられるtransformopacityと違って毎フレーム塗り直しが必要なので、レイヤーを欲張って重ねるほど非力な端末で先に動きの重さが目立ちます。

アクセシビリティ — 9つが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()へ置き換えれば動きます。

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

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