数字カウントアップ
要素が画面に入った瞬間、実績数値を0からアニメーションで加算表示します。サービス紹介の統計に。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk 実績セクション。枠に入った瞬間に数値がカウントアップ -->
<div class="fdc-scroller" id="cuScroller">
<header class="fdc-hero">
<span class="fdc-badge">TRUSTED BY TEAMS</span>
<h1>数字で見る FlowDesk</h1>
<p>導入企業とともに積み上げてきた、いまのわたしたち。</p>
<span class="fdc-arrow" aria-hidden="true">↓</span>
</header>
<section class="fdc-stats">
<div class="fdc-stat">
<span class="fdc-num" data-target="12800" data-suffix="社">0</span>
<span class="fdc-cap">導入企業数</span>
</div>
<div class="fdc-stat">
<span class="fdc-num" data-target="340" data-suffix="万人">0</span>
<span class="fdc-cap">アクティブユーザー</span>
</div>
<div class="fdc-stat">
<span class="fdc-num" data-target="99" data-suffix=".9%">0</span>
<span class="fdc-cap">稼働率(SLA)</span>
</div>
<div class="fdc-stat">
<span class="fdc-num" data-target="42" data-suffix="%">0</span>
<span class="fdc-cap">作業時間の削減</span>
</div>
</section>
<footer class="fdc-cta">
<h2>あなたのチームでも。</h2>
<button class="fdc-btn" type="button">14日間 無料で始める</button>
<p class="fdc-foot">FlowDesk — 仕事の流れを、ひとつに。</p>
</footer>
</div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--navy: #0f1b34;
--blue: #4f7cff;
--white: #ffffff;
}
body {
font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", sans-serif;
background: var(--navy);
color: var(--white);
-webkit-font-smoothing: antialiased;
}
/* 内部スクロール領域 */
.fdc-scroller {
width: 100%;
height: 100vh;
max-height: 100%;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: var(--blue) transparent;
}
/* ヒーロー */
.fdc-hero {
height: 220px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
text-align: center;
padding: 0 20px;
background:
radial-gradient(circle at 25% 20%, rgba(79,124,255,.35), transparent 60%),
radial-gradient(circle at 80% 90%, rgba(79,124,255,.2), transparent 55%),
var(--navy);
}
.fdc-badge {
font-size: .6rem;
letter-spacing: .26em;
padding: 5px 13px;
border-radius: 999px;
background: rgba(79,124,255,.18);
color: #aac0ff;
}
.fdc-hero h1 { font-size: 1.9rem; font-weight: 800; }
.fdc-hero p { font-size: .82rem; color: #aeb9da; }
.fdc-arrow { margin-top: 4px; font-size: 1.4rem; color: var(--blue); animation: fdcBob 1.6s ease-in-out infinite; }
@keyframes fdcBob {
0%,100% { transform: translateY(0); opacity: .6; }
50% { transform: translateY(8px); opacity: 1; }
}
/* 統計グリッド */
.fdc-stats {
max-width: 540px;
margin: 0 auto;
padding: 40px 24px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.fdc-stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 26px 14px;
border-radius: 16px;
background: rgba(255,255,255,.04);
border: 1px solid rgba(79,124,255,.18);
}
.fdc-num {
font-size: 2rem;
font-weight: 800;
letter-spacing: .01em;
background: linear-gradient(120deg, #8fb0ff, var(--blue));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-variant-numeric: tabular-nums;
}
.fdc-cap { font-size: .76rem; color: #aeb9da; letter-spacing: .04em; }
/* CTA */
.fdc-cta {
text-align: center;
padding: 24px 24px 80px;
}
.fdc-cta h2 { font-size: 1.3rem; font-weight: 800; margin-bottom: 16px; }
.fdc-btn {
font: inherit;
font-weight: 700;
font-size: .92rem;
color: #fff;
padding: 13px 30px;
border: none;
border-radius: 999px;
background: linear-gradient(120deg, #6a93ff, var(--blue));
box-shadow: 0 16px 30px -14px rgba(79,124,255,.9);
cursor: pointer;
transition: transform .2s ease;
}
.fdc-btn:hover { transform: translateY(-2px); }
.fdc-foot { font-size: .72rem; color: #8595c0; letter-spacing: .08em; margin-top: 18px; }
@media (prefers-reduced-motion: reduce) {
.fdc-arrow { animation: none; }
.fdc-btn { transition: none; }
}
JavaScript
// FlowDesk 実績:枠に入った瞬間に数値を0からカウントアップ
(() => {
const scroller = document.getElementById('cuScroller');
const nums = Array.from(document.querySelectorAll('.fdc-num'));
if (!nums.length) return; // null安全
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 1つの数字をアニメーションで増やす
function animateNum(el) {
const target = Number(el.dataset.target) || 0;
const prefix = el.dataset.prefix || '';
const suffix = el.dataset.suffix || '';
const duration = 1600;
if (reduce) {
el.textContent = prefix + target.toLocaleString('ja-JP') + suffix;
return;
}
const start = performance.now();
function tick(now) {
const t = Math.min((now - start) / duration, 1);
// easeOutCubic で減速
const eased = 1 - Math.pow(1 - t, 3);
const val = Math.round(target * eased);
el.textContent = prefix + val.toLocaleString('ja-JP') + suffix;
if (t < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
if (scroller && 'IntersectionObserver' in window) {
const io = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateNum(entry.target);
obs.unobserve(entry.target); // 一度だけ
}
});
}, { root: scroller, threshold: 0.5 });
nums.forEach(n => io.observe(n));
} else {
nums.forEach(animateNum);
}
// 操作がなくてもカウントが見えるよう、ゆっくり自動スクロール
if (scroller && !reduce) {
let auto = true;
const stop = () => { auto = false; };
['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
scroller.addEventListener(ev, stop, { passive: true }));
setTimeout(function step() {
if (!auto) return;
const max = scroller.scrollHeight - scroller.clientHeight;
if (scroller.scrollTop >= max - 1) return;
scroller.scrollTop += 3;
requestAnimationFrame(step);
}, 800);
}
})();
コード
HTML
<!-- 自前のスクロール領域(プレビュー枠内で完結) -->
<div class="cu-scroller" id="cuScroller">
<div class="cu-spacer">
<span>↓ スクロールで数字が動く</span>
</div>
<!-- 枠に入ったらカウントアップするスタッツ -->
<section class="cu-stats" id="cuStats">
<h2 class="cu-title">私たちの実績</h2>
<div class="cu-grid">
<div class="cu-item">
<span class="cu-num" data-target="128000" data-suffix="+">0</span>
<span class="cu-label">ダウンロード</span>
</div>
<div class="cu-item">
<span class="cu-num" data-target="98" data-suffix="%">0</span>
<span class="cu-label">満足度</span>
</div>
<div class="cu-item">
<span class="cu-num" data-target="42" data-prefix="">0</span>
<span class="cu-label">対応言語</span>
</div>
<div class="cu-item">
<span class="cu-num" data-target="1500" data-suffix="万円">0</span>
<span class="cu-label">調達額</span>
</div>
</div>
</section>
</div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--accent: #18d6c4;
--accent2: #5b8cff;
}
body {
font-family: "Segoe UI", system-ui, sans-serif;
background: #0d1117;
color: #eef1f7;
}
/* プレビュー枠を埋める自前スクロール領域 */
.cu-scroller {
width: 100%;
height: 100vh;
max-height: 100%;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
}
.cu-spacer {
height: 280px;
display: flex;
align-items: center;
justify-content: center;
color: #8b93a7;
font-size: .9rem;
letter-spacing: .06em;
background:
radial-gradient(circle at 50% 120%, rgba(91,140,255,.18), transparent 60%);
}
.cu-stats {
padding: 56px 24px 90px;
text-align: center;
}
.cu-title {
font-size: 1.6rem;
font-weight: 800;
margin-bottom: 38px;
background: linear-gradient(90deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.cu-grid {
max-width: 640px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18px;
}
.cu-item {
padding: 28px 16px;
border-radius: 16px;
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.08);
}
.cu-num {
display: block;
font-size: 2.3rem;
font-weight: 800;
font-variant-numeric: tabular-nums; /* 桁ブレ防止 */
background: linear-gradient(90deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
line-height: 1.1;
}
.cu-label {
display: block;
margin-top: 8px;
font-size: .85rem;
color: #9aa2b6;
letter-spacing: .04em;
}
@media (max-width: 480px) {
.cu-grid { grid-template-columns: 1fr; }
}
JavaScript
// 自前スクロール領域内で、枠に入った瞬間にカウントアップ
(() => {
const scroller = document.getElementById('cuScroller');
const nums = Array.from(document.querySelectorAll('.cu-num'));
if (!nums.length) return; // null安全
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 1つの数字をアニメーションで増やす
function animateNum(el) {
const target = Number(el.dataset.target) || 0;
const prefix = el.dataset.prefix || '';
const suffix = el.dataset.suffix || '';
const duration = 1600;
if (reduce) {
el.textContent = prefix + target.toLocaleString('ja-JP') + suffix;
return;
}
const start = performance.now();
function tick(now) {
const t = Math.min((now - start) / duration, 1);
// easeOutCubic で減速
const eased = 1 - Math.pow(1 - t, 3);
const val = Math.round(target * eased);
el.textContent = prefix + val.toLocaleString('ja-JP') + suffix;
if (t < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
if (scroller && 'IntersectionObserver' in window) {
const io = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateNum(entry.target);
obs.unobserve(entry.target); // 一度だけ
}
});
}, { root: scroller, threshold: 0.5 });
nums.forEach(n => io.observe(n));
} else {
nums.forEach(animateNum);
}
// 操作がなくてもカウントが見えるよう、ゆっくり自動スクロール
if (scroller && !reduce) {
let auto = true;
const stop = () => { auto = false; };
['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
scroller.addEventListener(ev, stop, { passive: true }));
setTimeout(function step() {
if (!auto) return;
const max = scroller.scrollHeight - scroller.clientHeight;
if (scroller.scrollTop >= max - 1) return;
scroller.scrollTop += 3;
requestAnimationFrame(step);
}, 800);
}
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「数字カウントアップ」の効果を追加してください。
# 追加してほしい効果
数字カウントアップ(スクロール演出)
要素が画面に入った瞬間、実績数値を0からアニメーションで加算表示します。サービス紹介の統計に。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 自前のスクロール領域(プレビュー枠内で完結) -->
<div class="cu-scroller" id="cuScroller">
<div class="cu-spacer">
<span>↓ スクロールで数字が動く</span>
</div>
<!-- 枠に入ったらカウントアップするスタッツ -->
<section class="cu-stats" id="cuStats">
<h2 class="cu-title">私たちの実績</h2>
<div class="cu-grid">
<div class="cu-item">
<span class="cu-num" data-target="128000" data-suffix="+">0</span>
<span class="cu-label">ダウンロード</span>
</div>
<div class="cu-item">
<span class="cu-num" data-target="98" data-suffix="%">0</span>
<span class="cu-label">満足度</span>
</div>
<div class="cu-item">
<span class="cu-num" data-target="42" data-prefix="">0</span>
<span class="cu-label">対応言語</span>
</div>
<div class="cu-item">
<span class="cu-num" data-target="1500" data-suffix="万円">0</span>
<span class="cu-label">調達額</span>
</div>
</div>
</section>
</div>
【CSS】
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--accent: #18d6c4;
--accent2: #5b8cff;
}
body {
font-family: "Segoe UI", system-ui, sans-serif;
background: #0d1117;
color: #eef1f7;
}
/* プレビュー枠を埋める自前スクロール領域 */
.cu-scroller {
width: 100%;
height: 100vh;
max-height: 100%;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
}
.cu-spacer {
height: 280px;
display: flex;
align-items: center;
justify-content: center;
color: #8b93a7;
font-size: .9rem;
letter-spacing: .06em;
background:
radial-gradient(circle at 50% 120%, rgba(91,140,255,.18), transparent 60%);
}
.cu-stats {
padding: 56px 24px 90px;
text-align: center;
}
.cu-title {
font-size: 1.6rem;
font-weight: 800;
margin-bottom: 38px;
background: linear-gradient(90deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.cu-grid {
max-width: 640px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18px;
}
.cu-item {
padding: 28px 16px;
border-radius: 16px;
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.08);
}
.cu-num {
display: block;
font-size: 2.3rem;
font-weight: 800;
font-variant-numeric: tabular-nums; /* 桁ブレ防止 */
background: linear-gradient(90deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
line-height: 1.1;
}
.cu-label {
display: block;
margin-top: 8px;
font-size: .85rem;
color: #9aa2b6;
letter-spacing: .04em;
}
@media (max-width: 480px) {
.cu-grid { grid-template-columns: 1fr; }
}
【JavaScript】
// 自前スクロール領域内で、枠に入った瞬間にカウントアップ
(() => {
const scroller = document.getElementById('cuScroller');
const nums = Array.from(document.querySelectorAll('.cu-num'));
if (!nums.length) return; // null安全
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 1つの数字をアニメーションで増やす
function animateNum(el) {
const target = Number(el.dataset.target) || 0;
const prefix = el.dataset.prefix || '';
const suffix = el.dataset.suffix || '';
const duration = 1600;
if (reduce) {
el.textContent = prefix + target.toLocaleString('ja-JP') + suffix;
return;
}
const start = performance.now();
function tick(now) {
const t = Math.min((now - start) / duration, 1);
// easeOutCubic で減速
const eased = 1 - Math.pow(1 - t, 3);
const val = Math.round(target * eased);
el.textContent = prefix + val.toLocaleString('ja-JP') + suffix;
if (t < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
if (scroller && 'IntersectionObserver' in window) {
const io = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateNum(entry.target);
obs.unobserve(entry.target); // 一度だけ
}
});
}, { root: scroller, threshold: 0.5 });
nums.forEach(n => io.observe(n));
} else {
nums.forEach(animateNum);
}
// 操作がなくてもカウントが見えるよう、ゆっくり自動スクロール
if (scroller && !reduce) {
let auto = true;
const stop = () => { auto = false; };
['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
scroller.addEventListener(ev, stop, { passive: true }));
setTimeout(function step() {
if (!auto) return;
const max = scroller.scrollHeight - scroller.clientHeight;
if (scroller.scrollTop >= max - 1) return;
scroller.scrollTop += 3;
requestAnimationFrame(step);
}, 800);
}
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。