CSSだけで作る、スクロールでグラデラインがスライドして文字が現れる見出し

スクロール

CSSだけで作る、スクロールでグラデラインがスライドして文字が現れる見出し

投稿日2026/02/17

更新日2026/2/9

スクロールに合わせて、見出しが気持ちよく現れる演出を入れたい。
このサンプルは、文字の上を走る赤と青のラインがスライドし、マスクが剥がれるように見出しが浮かび上がるスクロールアニメーションです。CSSだけで完結し、行ごとにタイミングをずらして段階的に見せられます。

Preview プレビュー

Code コード

<section class="kumonosu-section">
    <div class="kumonosu-intro-text">
        Please scroll
        <small>スクロールしてください</small>
    </div>
</section>

<section class="kumonosu-section">
  <h1 class="kumonosu-title kumonosu-title--scroll">
    <span class="kumonosu-title__line" style="--kumonosu-num: 1">
      <strong class="">KUMONOSU</strong> Scroll
    </span>
    <span class="kumonosu-title__line" style="--kumonosu-num: 2">
      <strong class="">Animations</strong> that
    </span>
    <span class="kumonosu-title__line" style="--kumonosu-num: 3">
      <strong class="">Capture</strong> your <strong class="">Eyes</strong>.
    </span>
  </h1>
  
  <div class="kumonosu-content">
    <p>スクロールの動きに合わせて、赤と青のラインがスライドし、鮮やかに文字が浮かび上がります。</p>
  </div>
</section>
/* CSS内で外部フォントを読み込み */
@import url('https://fonts.googleapis.com/css2?family=Gabarito:wght@400..900&display=swap');
@property --kumonosu-fontweight {
	syntax: "<number>";
	inherits: false;
	initial-value: 500;
}
:root {
	--kumonosu-font-size-4: clamp(1rem, 1rem + 0.13vw, 1.25rem);
	--kumonosu-font-size-8: clamp(2.441rem, 2.441rem + 0.31vw, 3.052rem);
	--kumonosu-font-size-11: clamp(4.768rem, 4.768rem + 0.6vw, 5.96rem);
	--kumonosu-bg-color: #f8f8f8;
	/* 赤と青の定義 */
	--kumonosu-color-red: #e63946;
	--kumonosu-color-blue: #0077b6;
}
body {
	font-family: 'Gabarito', sans-serif;
	background: var(--kumonosu-bg-color);
	margin: 0;
	overflow-x: hidden;
	color: #1a1a1a;
}
/* セクション共通設定 */
.kumonosu-section {
	min-height: 100vh;
	display: grid;
	place-content: center;
	text-align: center;
	box-sizing: border-box;
}
/* 1個目のセクション: padding 2rem */
.kumonosu-section:nth-of-type(1) {
	padding: 2rem;
}
/* 2個目のセクション: padding 2rem 2rem 26rem */
.kumonosu-section:nth-of-type(2) {
	padding: 2rem 2rem 26rem;
}
/* --- 第1セクション: 案内 --- */
.kumonosu-intro-text {
	font-size: var(--kumonosu-font-size-8);
	font-weight: 600;
	line-height: 1.2;
}
.kumonosu-intro-text small {
	display: block;
	font-size: var(--kumonosu-font-size-4);
	font-weight: 400;
	margin-top: 1rem;
	color: #888;
	letter-spacing: 0.1em;
}
/* --- 第2セクション: アニメーションタイトル --- */
.kumonosu-title {
	font-size: var(--kumonosu-font-size-8);
	font-variation-settings: "wght" var(--kumonosu-fontweight);
	position: relative;
	z-index: 2;
	margin: 0;
	white-space: nowrap;
	text-align: left;
}
@media (min-width: 600px) {
	.kumonosu-title {
		font-size: var(--kumonosu-font-size-11);
	}
}
.kumonosu-title__line {
	--kumonosu-x: 2vw;
	--kumonosu-num: 1;
	--kumonosu-delay: calc(var(--kumonosu-num) * 0.2s);
	--kumonosu-offsetpct: calc(var(--kumonosu-num) * 15%);
	position: relative;
	display: block;
	width: fit-content;
	transform: rotate(-2deg) translate3d(var(--kumonosu-x), 0, 0);
}
.kumonosu-title__line:nth-child(odd) strong {
	color: var(--kumonosu-color-red);
}
.kumonosu-title__line:nth-child(even) strong {
	color: var(--kumonosu-color-blue);
}
.kumonosu-title__line::before,
.kumonosu-title__line::after {
	content: "";
	position: absolute;
	inset: 0.1em -0.15em 0.015em -0.5em;
	display: block;
	transform: translateZ(0) scaleX(1);
	transform-origin: 100% 50%;
	will-change: transform;
}
.kumonosu-title__line::before {
	top: calc(0.2em + 1px);
	right: calc(-0.15em + 1px);
	bottom: calc(0.015em + 1px);
	left: calc(-0.15em + 1px);
	background: linear-gradient(90deg,
			var(--kumonosu-color-red),
			var(--kumonosu-color-blue));
}
.kumonosu-title__line::after {
	background: var(--kumonosu-bg-color);
}
/* スクロールアニメーション設定 */
.kumonosu-title--scroll {
	view-timeline-name: --kumonosu-reveal-timeline;
	view-timeline-axis: block;
}
@keyframes kumonosu-reveal-line {
	from {
		transform: translate3d(0, 0, 0) scaleX(1);
	}
	to {
		transform: translate3d(0.1em, 0, 0) scaleX(0);
	}
}
@supports (animation-timeline: --kumonosu-reveal-timeline) {
	.kumonosu-title--scroll .kumonosu-title__line::after {
		animation: kumonosu-reveal-line 2s cubic-bezier(1, 0, 0, 1) both;
		animation-timeline: --kumonosu-reveal-timeline;
		animation-range: entry calc(0% + var(--kumonosu-offsetpct)) cover calc(30% + var(--kumonosu-offsetpct));
	}
	.kumonosu-title--scroll .kumonosu-title__line::before {
		animation: kumonosu-reveal-line 2s cubic-bezier(1, 0, 0, 1) both;
		animation-timeline: --kumonosu-reveal-timeline;
		animation-range: entry calc(10% + var(--kumonosu-offsetpct)) cover calc(35% + var(--kumonosu-offsetpct));
	}
}
.kumonosu-content {
	padding: 4rem 0;
	text-align: left;
	line-height: 1.8;
	margin: 0 auto;
	color: #444;
}

Explanation 詳しい説明

仕様

見出しは複数行をspanで分割し、各行(.s__title__line)に擬似要素::before::afterを重ねています。

::beforeは赤→青のグラデーション線、::afterは背景色の“隠しマスク”として働き、最初は文字の上を覆うことで「まだ見えていない」状態を作ります。

スクロールに応じて::after(マスク)がscaleX(1) → scaleX(0)で縮み、下から文字が現れます。同時に::before(グラデ線)も少し遅れて同じ動きをするため、線が走って剥がしているように見えます。行ごとの差は、--numから計算した--offsetpctanimation-rangeに混ぜて作っています。

  • 1行=span.s__title__lineで分割
  • ::after=背景色のマスク(縮んで文字を露出)
  • ::before=赤→青のライン(少し遅れて追従)
  • view-timelineanimation-rangeでスクロール連動
  • --numで行ごとに開始位置をずらす

カスタム

色・タイミング・行間の見え方はCSSだけで調整できます。まずは「色」「ずらし量」「アニメ範囲」の3点を触ると変化が分かりやすいです。

  • 色:--color-red / --color-blue::beforelinear-gradient
  • 行のずらし:.s__title__line { --x: 2vw; transform: rotate(-2deg) translate3d(var(--x),0,0); }
  • 行ごとの遅れ:--offsetpct: calc(var(--num) * 15%);
  • 表示するスクロール範囲:animation-range: entry ... cover ...
  • マスクの“太さ感”:inset::before/::after)の値で縁や余白を調整

行を増やしたい場合はspanを増やし、style="--num: n"を連番で付ければ同じ仕組みで展開できます。

注意点

この実装はview-timeline(Scroll-driven Animations)に依存するため、未対応ブラウザではスクロール連動アニメーションが動きません。

@supports (animation-timeline: --reveal-timeline)で囲っているので崩れにくい一方、非対応環境では「演出なしの静的見出し」になります。

また、::afterは背景色でマスクしているため、背景が写真や複雑な柄の場合はそのままだと成立しません。画像背景に載せる場合は、マスクを半透明にする/別レイヤーを用意するなどの設計が必要です。

  • view-timeline未対応環境では演出が無効(静的表示)
  • 背景色マスク前提(写真背景では別設計が必要)
  • 行数を増やすほどanimation-range調整が重要