円形カウントダウン
SVGリングのstroke-dashoffsetを操作し残り秒を可視化する円形タイマー。完了でリングと数字が赤に変化します。リダイレクトや自動遷移の予告に。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura 新曲MVプレミア公開:円形カウントダウンで公開までの残り秒を可視化 -->
<div class="sk-premiere">
<header class="sk-premiere__bar">
<span class="sk-premiere__name">🌸 Sakura</span>
<span class="sk-premiere__tag">MV PREMIERE</span>
</header>
<div class="sk-premiere__mv" style="--img:url('https://picsum.photos/420/260?random=61')">
<span class="sk-premiere__title">「春風メロディー」</span>
<span class="sk-premiere__sub">7th Single Music Video</span>
<!-- 円形カウントダウン(主役) -->
<div class="sk-ring">
<svg viewBox="0 0 120 120">
<circle class="sk-ring__bg" cx="60" cy="60" r="52"/>
<circle class="sk-ring__fg" id="skRingFg" cx="60" cy="60" r="52"/>
</svg>
<div class="sk-ring__center">
<span class="sk-ring__num" id="skNum">10</span>
<span class="sk-ring__lbl">公開まで</span>
</div>
</div>
</div>
<p class="sk-premiere__note" id="skNote">まもなくプレミア公開がはじまります</p>
</div>
CSS
/* Sakura MVプレミア:円形カウントダウンリング */
:root {
--pink: #ffd1e0;
--pink2: #ff8fb3;
--ink: #4a4450;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
display: grid;
place-items: center;
font-family: "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background: linear-gradient(180deg, #fff 0%, var(--pink) 100%);
color: var(--ink);
overflow: hidden;
}
.sk-premiere {
width: 340px;
background: #fff;
border-radius: 20px;
padding: 14px;
box-shadow: 0 16px 40px rgba(255,143,179,0.3);
}
.sk-premiere__bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 4px 12px;
}
.sk-premiere__name { font-size: 15px; font-weight: 700; color: #7a3b53; }
.sk-premiere__tag { font-size: 10px; letter-spacing: 0.2em; color: var(--pink2); font-weight: 700; }
/* MVサムネ上にリングを重ねる */
.sk-premiere__mv {
position: relative;
height: 230px;
border-radius: 14px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
text-align: center;
background:
linear-gradient(180deg, rgba(122,59,83,0.35), rgba(122,59,83,0.6)),
var(--img) center/cover no-repeat;
}
.sk-premiere__title { font-size: 19px; font-weight: 700; text-shadow: 0 2px 8px rgba(0,0,0,0.4); }
.sk-premiere__sub { font-size: 11px; opacity: 0.9; margin-top: 2px; }
/* リング */
.sk-ring { position: relative; width: 120px; height: 120px; margin-top: 14px; }
.sk-ring svg { width: 120px; height: 120px; transform: rotate(-90deg); }
.sk-ring__bg {
fill: none;
stroke: rgba(255,255,255,0.3);
stroke-width: 7;
}
.sk-ring__fg {
fill: none;
stroke: #fff;
stroke-width: 7;
stroke-linecap: round;
/* JSで stroke-dasharray / offset を制御 */
transition: stroke-dashoffset 1s linear, stroke 0.3s ease;
}
.sk-ring.is-done .sk-ring__fg { stroke: #ff4d6d; }
.sk-ring__center {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.sk-ring__num { font-size: 38px; font-weight: 800; line-height: 1; text-shadow: 0 2px 6px rgba(0,0,0,0.3); }
.sk-ring.is-done .sk-ring__num { color: #ff4d6d; }
.sk-ring__lbl { font-size: 10px; opacity: 0.9; margin-top: 4px; letter-spacing: 0.1em; }
.sk-premiere__note { margin: 12px 4px 4px; font-size: 11.5px; color: #9a8f96; text-align: center; }
.sk-premiere__note.is-done { color: var(--pink2); font-weight: 700; }
@media (prefers-reduced-motion: reduce) {
.sk-ring__fg { transition: stroke 0.3s ease; }
}
JavaScript
// Sakura MVプレミア:SVGリングの stroke-dashoffset で残り秒を可視化(ループ)
const fg = document.getElementById('skRingFg');
const num = document.getElementById('skNum');
const note = document.getElementById('skNote');
const ring = fg ? fg.closest('.sk-ring') : null;
const R = 52;
const CIRC = 2 * Math.PI * R; // 円周
const TOTAL = 10; // 秒
let remain = TOTAL;
let timer = null;
if (fg) fg.style.strokeDasharray = CIRC.toFixed(1);
function render() {
if (!fg || !num) return;
// 残り割合に応じてリングを減らす
const ratio = remain / TOTAL;
fg.style.strokeDashoffset = (CIRC * (1 - ratio)).toFixed(1);
num.textContent = remain;
}
function tick() {
remain -= 1;
render();
if (remain <= 0) {
clearInterval(timer);
ring?.classList.add('is-done');
if (num) num.textContent = '0';
if (note) { note.textContent = 'プレミア公開がはじまりました!🌸'; note.classList.add('is-done'); }
// しばらくして再カウント(ループ)
setTimeout(start, 2800);
}
}
function start() {
remain = TOTAL;
clearInterval(timer);
ring?.classList.remove('is-done');
if (note) { note.textContent = 'まもなくプレミア公開がはじまります'; note.classList.remove('is-done'); }
render();
timer = setInterval(tick, 1000);
}
// 初回起動
start();
コード
HTML
<!-- 円形カウントダウン: SVGリングが減りながら残り秒を表示。完了で色が変化 -->
<div class="cd-stage">
<div class="cd-wrap">
<svg class="cd-svg" viewBox="0 0 120 120" aria-hidden="true">
<defs>
<linearGradient id="cdGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#34d399"/>
<stop offset="50%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#818cf8"/>
</linearGradient>
</defs>
<!-- 背景トラック -->
<circle class="cd-track" cx="60" cy="60" r="52"/>
<!-- 進捗リング(JSで dashoffset を制御) -->
<circle class="cd-ring" id="cdRing" cx="60" cy="60" r="52"/>
</svg>
<div class="cd-center">
<span class="cd-num" id="cdNum">10</span>
<span class="cd-unit">SEC</span>
</div>
</div>
<button class="cd-btn" id="cdBtn" type="button">スタート</button>
</div>
CSS
:root {
--bg: #0a0f1f;
--track: rgba(255, 255, 255, .08);
--txt: #eaf0ff;
--done: #f87171;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(700px 500px at 50% -10%, #16224a 0%, transparent 60%),
var(--bg);
color: var(--txt);
}
.cd-stage {
display: flex;
flex-direction: column;
align-items: center;
gap: 22px;
padding: 24px;
}
.cd-wrap {
position: relative;
width: 180px;
height: 180px;
display: grid;
place-items: center;
}
.cd-svg {
width: 100%;
height: 100%;
transform: rotate(-90deg); /* 12時方向から開始 */
}
.cd-track {
fill: none;
stroke: var(--track);
stroke-width: 9;
}
.cd-ring {
fill: none;
stroke: url(#cdGrad);
stroke-width: 9;
stroke-linecap: round;
/* 円周 ≒ 2πr = 326.7。JSで stroke-dashoffset を変えて減らす */
stroke-dasharray: 326.7;
stroke-dashoffset: 0;
transition: stroke-dashoffset 1s linear, stroke .4s ease;
filter: drop-shadow(0 0 6px rgba(56, 189, 248, .5));
}
.cd-wrap.is-done .cd-ring { stroke: var(--done); }
.cd-center {
position: absolute;
display: grid;
justify-items: center;
gap: 2px;
}
.cd-num {
font-size: 52px;
font-weight: 700;
line-height: 1;
font-variant-numeric: tabular-nums;
letter-spacing: -.02em;
}
.cd-unit {
font-size: 11px;
letter-spacing: .3em;
color: rgba(234, 240, 255, .5);
padding-left: .3em;
}
.cd-wrap.is-done .cd-num { color: var(--done); animation: cd-pop .5s ease; }
@keyframes cd-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.15); } 100% { transform: scale(1); } }
.cd-btn {
border: 1px solid rgba(255, 255, 255, .16);
background: rgba(255, 255, 255, .04);
color: var(--txt);
padding: 10px 26px;
border-radius: 999px;
font-size: 13px;
letter-spacing: .04em;
cursor: pointer;
transition: background .2s, transform .1s;
}
.cd-btn:hover { background: rgba(255, 255, 255, .12); }
.cd-btn:active { transform: scale(.95); }
@media (prefers-reduced-motion: reduce) {
.cd-ring { transition: stroke-dashoffset .3s linear, stroke .3s; }
.cd-wrap.is-done .cd-num { animation: none; }
}
JavaScript
// SVGリングで表す円形カウントダウン
const ring = document.getElementById('cdRing');
const num = document.getElementById('cdNum');
const btn = document.getElementById('cdBtn');
const wrap = document.querySelector('.cd-wrap');
const TOTAL = 10; // 秒数
const CIRC = 2 * Math.PI * 52; // 円周 ≒ 326.7
let remain = TOTAL;
let timer = null;
// リングと数字を現在の残り秒に合わせて更新
function render() {
const ratio = remain / TOTAL;
if (ring) ring.style.strokeDashoffset = String(CIRC * (1 - ratio));
if (num) num.textContent = String(remain);
}
function step() {
remain -= 1;
render();
if (remain <= 0) {
clearInterval(timer);
timer = null;
wrap?.classList.add('is-done');
if (num) num.textContent = '0';
if (btn) btn.textContent = 'リセット';
}
}
function start() {
clearInterval(timer);
wrap?.classList.remove('is-done');
remain = TOTAL;
render();
if (btn) btn.textContent = '実行中…';
timer = setInterval(step, 1000);
}
btn?.addEventListener('click', start);
// 初期描画(満タン表示)
render();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「円形カウントダウン」の効果を追加してください。
# 追加してほしい効果
円形カウントダウン(ローダー & スケルトン)
SVGリングのstroke-dashoffsetを操作し残り秒を可視化する円形タイマー。完了でリングと数字が赤に変化します。リダイレクトや自動遷移の予告に。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 円形カウントダウン: SVGリングが減りながら残り秒を表示。完了で色が変化 -->
<div class="cd-stage">
<div class="cd-wrap">
<svg class="cd-svg" viewBox="0 0 120 120" aria-hidden="true">
<defs>
<linearGradient id="cdGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#34d399"/>
<stop offset="50%" stop-color="#22d3ee"/>
<stop offset="100%" stop-color="#818cf8"/>
</linearGradient>
</defs>
<!-- 背景トラック -->
<circle class="cd-track" cx="60" cy="60" r="52"/>
<!-- 進捗リング(JSで dashoffset を制御) -->
<circle class="cd-ring" id="cdRing" cx="60" cy="60" r="52"/>
</svg>
<div class="cd-center">
<span class="cd-num" id="cdNum">10</span>
<span class="cd-unit">SEC</span>
</div>
</div>
<button class="cd-btn" id="cdBtn" type="button">スタート</button>
</div>
【CSS】
:root {
--bg: #0a0f1f;
--track: rgba(255, 255, 255, .08);
--txt: #eaf0ff;
--done: #f87171;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(700px 500px at 50% -10%, #16224a 0%, transparent 60%),
var(--bg);
color: var(--txt);
}
.cd-stage {
display: flex;
flex-direction: column;
align-items: center;
gap: 22px;
padding: 24px;
}
.cd-wrap {
position: relative;
width: 180px;
height: 180px;
display: grid;
place-items: center;
}
.cd-svg {
width: 100%;
height: 100%;
transform: rotate(-90deg); /* 12時方向から開始 */
}
.cd-track {
fill: none;
stroke: var(--track);
stroke-width: 9;
}
.cd-ring {
fill: none;
stroke: url(#cdGrad);
stroke-width: 9;
stroke-linecap: round;
/* 円周 ≒ 2πr = 326.7。JSで stroke-dashoffset を変えて減らす */
stroke-dasharray: 326.7;
stroke-dashoffset: 0;
transition: stroke-dashoffset 1s linear, stroke .4s ease;
filter: drop-shadow(0 0 6px rgba(56, 189, 248, .5));
}
.cd-wrap.is-done .cd-ring { stroke: var(--done); }
.cd-center {
position: absolute;
display: grid;
justify-items: center;
gap: 2px;
}
.cd-num {
font-size: 52px;
font-weight: 700;
line-height: 1;
font-variant-numeric: tabular-nums;
letter-spacing: -.02em;
}
.cd-unit {
font-size: 11px;
letter-spacing: .3em;
color: rgba(234, 240, 255, .5);
padding-left: .3em;
}
.cd-wrap.is-done .cd-num { color: var(--done); animation: cd-pop .5s ease; }
@keyframes cd-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.15); } 100% { transform: scale(1); } }
.cd-btn {
border: 1px solid rgba(255, 255, 255, .16);
background: rgba(255, 255, 255, .04);
color: var(--txt);
padding: 10px 26px;
border-radius: 999px;
font-size: 13px;
letter-spacing: .04em;
cursor: pointer;
transition: background .2s, transform .1s;
}
.cd-btn:hover { background: rgba(255, 255, 255, .12); }
.cd-btn:active { transform: scale(.95); }
@media (prefers-reduced-motion: reduce) {
.cd-ring { transition: stroke-dashoffset .3s linear, stroke .3s; }
.cd-wrap.is-done .cd-num { animation: none; }
}
【JavaScript】
// SVGリングで表す円形カウントダウン
const ring = document.getElementById('cdRing');
const num = document.getElementById('cdNum');
const btn = document.getElementById('cdBtn');
const wrap = document.querySelector('.cd-wrap');
const TOTAL = 10; // 秒数
const CIRC = 2 * Math.PI * 52; // 円周 ≒ 326.7
let remain = TOTAL;
let timer = null;
// リングと数字を現在の残り秒に合わせて更新
function render() {
const ratio = remain / TOTAL;
if (ring) ring.style.strokeDashoffset = String(CIRC * (1 - ratio));
if (num) num.textContent = String(remain);
}
function step() {
remain -= 1;
render();
if (remain <= 0) {
clearInterval(timer);
timer = null;
wrap?.classList.add('is-done');
if (num) num.textContent = '0';
if (btn) btn.textContent = 'リセット';
}
}
function start() {
clearInterval(timer);
wrap?.classList.remove('is-done');
remain = TOTAL;
render();
if (btn) btn.textContent = '実行中…';
timer = setInterval(step, 1000);
}
btn?.addEventListener('click', start);
// 初期描画(満タン表示)
render();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。