スクロール
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から計算した--offsetpctをanimation-rangeに混ぜて作っています。
- 1行=
span.s__title__lineで分割 ::after=背景色のマスク(縮んで文字を露出)::before=赤→青のライン(少し遅れて追従)view-timeline+animation-rangeでスクロール連動--numで行ごとに開始位置をずらす
カスタム
色・タイミング・行間の見え方はCSSだけで調整できます。まずは「色」「ずらし量」「アニメ範囲」の3点を触ると変化が分かりやすいです。
- 色:
--color-red/--color-blue、::beforeのlinear-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調整が重要