可変フォント風ウェイトアニメ
JSで文字ごとに font-weight をsin波で連続変化させ、可変フォントのような波打つ太さ変化を再現します。動的なロゴやヘッダーに。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<div class="page">
<header class="nav">
<div class="brand"><span class="logo"></span>FlowDesk</div>
<nav class="links"><a>機能</a><a>料金</a><a class="btn">ログイン</a></nav>
</header>
<section class="hero">
<p class="eyebrow">ALL-IN-ONE WORKSPACE</p>
<h1 class="wave" data-text="FLOWDESK"></h1>
<p class="lead">太さが波打つ、生きたブランドロゴ。<br>あなたのチームに、流れと勢いを。</p>
<div class="metrics">
<div class="m"><b>12,400+</b><span>導入チーム</span></div>
<div class="m"><b>99.98%</b><span>稼働率</span></div>
<div class="m"><b>4.9</b><span>満足度</span></div>
</div>
</section>
</div>
CSS
/* FlowDesk:太さが波打つロゴ見出しが主役 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(800px 380px at 50% -15%, #1a2c57 0%, transparent 60%),
linear-gradient(160deg, #0f1b34 0%, #0b1326 100%);
color: #e7ecf7;
min-height: 400px;
overflow: hidden;
}
.page { padding: 16px 26px; }
.nav { display: flex; align-items: center; justify-content: space-between; }
.brand { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 16px; }
.logo {
width: 17px; height: 17px; border-radius: 6px;
background: linear-gradient(135deg, #4f7cff, #8ab4ff);
box-shadow: 0 0 10px rgba(79,124,255,0.6);
}
.links { display: flex; align-items: center; gap: 16px; }
.links a { font-size: 13px; color: #aeb9d4; cursor: pointer; }
.links .btn { color: #fff; background: #4f7cff; padding: 7px 14px; border-radius: 8px; font-weight: 600; }
.hero { text-align: center; padding: 26px 6px 0; }
.eyebrow { font-size: 11px; letter-spacing: 0.32em; color: #6f86c2; font-weight: 700; }
/* 波打つロゴ本体:文字ごとにJSがfont-weightを変える */
.wave {
margin-top: 14px;
font-size: clamp(40px, 9vw, 76px);
line-height: 1;
letter-spacing: 0.04em;
color: #fff;
font-weight: 400;
}
.wave .ch {
display: inline-block;
/* 太さ変化をなめらかに */
transition: font-weight 0.08s linear;
background: linear-gradient(180deg, #ffffff, #aac2ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.lead { margin-top: 16px; font-size: 13.5px; line-height: 1.7; color: #aeb9d4; }
.metrics {
margin-top: 22px;
display: flex; gap: 14px; justify-content: center;
}
.m {
background: rgba(79,124,255,0.08);
border: 1px solid #233459;
border-radius: 12px;
padding: 12px 18px; min-width: 92px;
}
.m b { display: block; font-size: 19px; color: #8ab4ff; }
.m span { font-size: 11px; color: #8593b5; }
JavaScript
// 文字ごとにfont-weightをsin波で連続変化させる
(function () {
const el = document.querySelector('.wave');
if (!el) return; // null安全
const text = el.dataset.text || 'FLOWDESK';
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 1文字ずつspan化
const spans = [...text].map((c) => {
const s = document.createElement('span');
s.className = 'ch';
s.textContent = c;
el.appendChild(s);
return s;
});
// 控えめ設定では中庸の太さで固定
if (reduce) { spans.forEach((s) => (s.style.fontWeight = 600)); return; }
const MIN = 200, MAX = 900;
let t = 0;
function frame() {
t += 0.06;
spans.forEach((s, i) => {
// 位相を文字ごとにずらして波を進める
const wave = (Math.sin(t - i * 0.5) + 1) / 2; // 0..1
s.style.fontWeight = Math.round(MIN + wave * (MAX - MIN));
});
requestAnimationFrame(frame);
}
frame();
})();
コード
HTML
<main class="stage">
<p class="label">VARIABLE WEIGHT</p>
<!-- JSが各文字を<span>化し、font-weightを波打たせる -->
<h1 class="wave" data-text="Typography"></h1>
<p class="note">font-weight を文字ごとに波形で変化</p>
</main>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 360px;
display: grid;
place-items: center;
/* 明るくクリーンな背景 */
background:
radial-gradient(900px 500px at 50% 0%, #ffffff 0%, #eef1f7 70%, #e6eaf3 100%);
font-family: "Segoe UI", "Helvetica Neue", system-ui, sans-serif;
color: #14181f;
overflow: hidden;
padding: 24px;
}
.stage { text-align: center; }
.label {
font-size: 12px;
letter-spacing: 0.45em;
font-weight: 600;
color: #8a93a6;
margin-bottom: 16px;
padding-left: 0.45em;
}
.wave {
font-size: clamp(44px, 11vw, 92px);
line-height: 1.1;
letter-spacing: 0.01em;
/* システムフォントは可変軸が無いことが多いので、font-weightで近似 */
white-space: nowrap;
}
/* 各文字。weightとスケールはJSが毎フレーム更新 */
.wave .g {
display: inline-block;
/* will-changeで重み変化のちらつきを軽減 */
will-change: font-weight, transform;
transition: none;
/* 重み変化でにじむグラデの下線アクセント */
background-image: linear-gradient(120deg, #6366f1, #ec4899);
background-size: 100% 2px;
background-repeat: no-repeat;
background-position: 0 100%;
}
.note {
margin-top: 28px;
font-size: 12px;
letter-spacing: 0.16em;
color: #97a0b2;
}
@media (prefers-reduced-motion: reduce) {
.wave .g { font-weight: 600 !important; transform: none !important; }
}
JavaScript
// 可変フォント風: font-weight を文字ごとに波形でアニメ
(function () {
const el = document.querySelector('.wave');
if (!el) return; // null安全
const text = el.dataset.text || '';
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 文字を<span>に分解
const spans = [...text].map((char) => {
const s = document.createElement('span');
s.className = 'g';
s.textContent = char;
el.appendChild(s);
return s;
});
if (reduce || spans.length === 0) return; // 静的表示で終了
const MIN = 200, MAX = 900; // ウェイト範囲
const SPEED = 0.0028; // 波の進む速さ
const SPREAD = 0.55; // 隣接文字との位相差
let start = null;
// requestAnimationFrameで滑らかに更新
function frame(now) {
if (start === null) start = now;
const t = (now - start) * SPEED;
spans.forEach((s, i) => {
// sinで0..1の波を作り、文字位置で位相をずらす
const wave = (Math.sin(t - i * SPREAD) + 1) / 2;
const weight = Math.round(MIN + wave * (MAX - MIN));
s.style.fontWeight = weight;
// 重い文字をわずかに持ち上げて立体感
s.style.transform = 'translateY(' + (-wave * 6).toFixed(2) + 'px)';
});
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「可変フォント風ウェイトアニメ」の効果を追加してください。
# 追加してほしい効果
可変フォント風ウェイトアニメ(タイポグラフィ)
JSで文字ごとに font-weight をsin波で連続変化させ、可変フォントのような波打つ太さ変化を再現します。動的なロゴやヘッダーに。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<main class="stage">
<p class="label">VARIABLE WEIGHT</p>
<!-- JSが各文字を<span>化し、font-weightを波打たせる -->
<h1 class="wave" data-text="Typography"></h1>
<p class="note">font-weight を文字ごとに波形で変化</p>
</main>
【CSS】
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 360px;
display: grid;
place-items: center;
/* 明るくクリーンな背景 */
background:
radial-gradient(900px 500px at 50% 0%, #ffffff 0%, #eef1f7 70%, #e6eaf3 100%);
font-family: "Segoe UI", "Helvetica Neue", system-ui, sans-serif;
color: #14181f;
overflow: hidden;
padding: 24px;
}
.stage { text-align: center; }
.label {
font-size: 12px;
letter-spacing: 0.45em;
font-weight: 600;
color: #8a93a6;
margin-bottom: 16px;
padding-left: 0.45em;
}
.wave {
font-size: clamp(44px, 11vw, 92px);
line-height: 1.1;
letter-spacing: 0.01em;
/* システムフォントは可変軸が無いことが多いので、font-weightで近似 */
white-space: nowrap;
}
/* 各文字。weightとスケールはJSが毎フレーム更新 */
.wave .g {
display: inline-block;
/* will-changeで重み変化のちらつきを軽減 */
will-change: font-weight, transform;
transition: none;
/* 重み変化でにじむグラデの下線アクセント */
background-image: linear-gradient(120deg, #6366f1, #ec4899);
background-size: 100% 2px;
background-repeat: no-repeat;
background-position: 0 100%;
}
.note {
margin-top: 28px;
font-size: 12px;
letter-spacing: 0.16em;
color: #97a0b2;
}
@media (prefers-reduced-motion: reduce) {
.wave .g { font-weight: 600 !important; transform: none !important; }
}
【JavaScript】
// 可変フォント風: font-weight を文字ごとに波形でアニメ
(function () {
const el = document.querySelector('.wave');
if (!el) return; // null安全
const text = el.dataset.text || '';
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 文字を<span>に分解
const spans = [...text].map((char) => {
const s = document.createElement('span');
s.className = 'g';
s.textContent = char;
el.appendChild(s);
return s;
});
if (reduce || spans.length === 0) return; // 静的表示で終了
const MIN = 200, MAX = 900; // ウェイト範囲
const SPEED = 0.0028; // 波の進む速さ
const SPREAD = 0.55; // 隣接文字との位相差
let start = null;
// requestAnimationFrameで滑らかに更新
function frame(now) {
if (start === null) start = now;
const t = (now - start) * SPEED;
spans.forEach((s, i) => {
// sinで0..1の波を作り、文字位置で位相をずらす
const wave = (Math.sin(t - i * SPREAD) + 1) / 2;
const weight = Math.round(MIN + wave * (MAX - MIN));
s.style.fontWeight = weight;
// 重い文字をわずかに持ち上げて立体感
s.style.transform = 'translateY(' + (-wave * 6).toFixed(2) + 'px)';
});
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。