ライブデモ Live Demo
文字が1つずつグロー発光しながらフェードインし、末尾のドットが続いて出現します。クロマティックな光のスキャンが背後を走り、AI生成中の雰囲気を演出します。スライダーとプリセットボタンで見た目を自由に調整できます。
Each letter glows and fades in one by one, followed by trailing dots. A chromatic scan sweeps behind to reinforce the "AI generating" feel. Adjust speed, glow, and scan color with the controls below.
AI向け説明 AI Description
`S-010` は各 `.gen-letter` に `animation-delay` を約0.105s 間隔で設定し、`@keyframes genLetterAnim` で opacity 0→1→0 のフェードとスケール微動を付けることで「タイプされていく」演出を作ります。末尾の `.gen-dot` 3つは最後の文字の後に続いて出現するオリジナル要素で、AIがまだ処理中であることを示します。`.gen-scan::after` は CSS変数 `--scan-c1`〜`--scan-c5` で色を制御する複合 `radial-gradient` をリング形 `mask` で切り抜き、左右に往復しながら不透明度を変化させてクロマティックスキャンを表現します。グロー強度は `--gen-glow` 変数で `text-shadow` のブラー量を制御します。
`S-010` staggers each `.gen-letter` at ~0.105 s intervals using `@keyframes genLetterAnim` (opacity 0→1→0 with scale bounce) for the letter-by-letter typeout. Three trailing `.gen-dot` elements appear after the last letter — an original addition that signals ongoing AI processing. The `.gen-scan::after` pseudo-element composites five CSS-variable-driven `radial-gradient`s masked to a ring, sweeping horizontally while fading for the scan effect. Glow intensity is controlled by `--gen-glow` applied to `text-shadow` inside the keyframe.
調整可能パラメータ Adjustable Parameters
- --gen-duration — 文字フェードとスキャンの1サイクル時間(デフォルト: 4s) full cycle duration for letter fade and scan opacity (default: 4s)
- --gen-glow — 文字発光のブラー半径(デフォルト: 4px)。大きくするほど強い光を放つ blur radius for the letter glow (default: 4px); larger = stronger bloom
- --scan-c1 〜 --scan-c5 — スキャンビームを構成する5つのラジアルグラデーション色。プリセット: Chromatic / AI Blue / Amber five radial gradient colors forming the scan blob. Presets: Chromatic / AI Blue / Amber
- animation-delay (per letter) — 各文字の遅延(約0.105s 間隔)。文字数変更時は nth-child を比例追加する per-letter stagger (~0.105 s); add nth-child entries proportionally for different word lengths
- .gen-dot delays — トレーリングドットの遅延(最終文字 + 約0.18s ずつ)。ドット数を変えれば長さを調整できる trailing dot delays (last letter + ~0.18 s steps); adjust dot count to change the ellipsis length
- prefers-reduced-motion — すべてのアニメーションを停止し、文字・ドットを常時表示に切り替える disables all animations and shows letters and dots at full opacity
実装 Implementation
HTML + CSS
<div class="gen-wrapper">
<span class="gen-letter">G</span>
<span class="gen-letter">e</span>
<span class="gen-letter">n</span>
<span class="gen-letter">e</span>
<span class="gen-letter">r</span>
<span class="gen-letter">a</span>
<span class="gen-letter">t</span>
<span class="gen-letter">i</span>
<span class="gen-letter">n</span>
<span class="gen-letter">g</span>
<span class="gen-dot">·</span>
<span class="gen-dot">·</span>
<span class="gen-dot">·</span>
<div class="gen-scan"></div>
</div>
<style>
:root {
--gen-duration: 4s;
--gen-glow: 4px;
--scan-c1: #ff0; --scan-c2: #f00; --scan-c3: #0ff;
--scan-c4: #0f0; --scan-c5: #00f;
}
.gen-wrapper {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 120px;
padding: 0 16px;
font-size: 2.8em;
font-weight: 700;
color: #fff;
user-select: none;
}
/* scanline overlay */
.gen-scan {
position: absolute;
inset: 0;
z-index: 1;
background-color: transparent;
mask: repeating-linear-gradient(
90deg,
transparent 0, transparent 6px,
black 7px, black 8px
);
}
/* chromatic sweep */
.gen-scan::after {
content: "";
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 50%, var(--scan-c1) 0%, transparent 50%),
radial-gradient(circle at 45% 45%, var(--scan-c2) 0%, transparent 45%),
radial-gradient(circle at 55% 55%, var(--scan-c3) 0%, transparent 45%),
radial-gradient(circle at 45% 55%, var(--scan-c4) 0%, transparent 45%),
radial-gradient(circle at 55% 45%, var(--scan-c5) 0%, transparent 45%);
mask: radial-gradient(circle at 50% 50%,
transparent 0%, transparent 10%, black 25%);
/* 移動と不透明度を統合。opacity=0 の間に右端→左端へ瞬間ジャンプし
常に左→右の一方向スイープに見せる。--gen-duration に連動する。 */
animation: genScanCombined var(--gen-duration) linear infinite;
}
.gen-letter {
display: inline-block;
opacity: 0;
animation: genLetterAnim var(--gen-duration) infinite linear;
position: relative;
z-index: 2;
}
/* trailing dots — original element */
.gen-dot {
display: inline-block;
opacity: 0;
animation: genLetterAnim var(--gen-duration) infinite linear;
position: relative;
z-index: 2;
color: rgba(255,255,255,0.55);
font-size: 0.55em;
align-self: flex-end;
margin-bottom: 0.18em;
letter-spacing: 0.15em;
}
/* letter delays */
.gen-letter:nth-child(1) { animation-delay: 0.100s; }
.gen-letter:nth-child(2) { animation-delay: 0.205s; }
.gen-letter:nth-child(3) { animation-delay: 0.310s; }
.gen-letter:nth-child(4) { animation-delay: 0.415s; }
.gen-letter:nth-child(5) { animation-delay: 0.521s; }
.gen-letter:nth-child(6) { animation-delay: 0.626s; }
.gen-letter:nth-child(7) { animation-delay: 0.731s; }
.gen-letter:nth-child(8) { animation-delay: 0.837s; }
.gen-letter:nth-child(9) { animation-delay: 0.942s; }
.gen-letter:nth-child(10) { animation-delay: 1.047s; }
/* dot delays */
.gen-dot:nth-child(11) { animation-delay: 1.200s; }
.gen-dot:nth-child(12) { animation-delay: 1.380s; }
.gen-dot:nth-child(13) { animation-delay: 1.560s; }
@keyframes genScanCombined {
0% { transform: translate(-70%); opacity: 0; }
13% { transform: translate(-70%); opacity: 0; }
18% { opacity: 1; }
62% { transform: translate(70%); opacity: 0.8; }
68% { opacity: 0; }
100% { transform: translate(70%); opacity: 0; }
}
@keyframes genLetterAnim {
0% { opacity: 0; }
5% { opacity: 1;
text-shadow: 0 0 var(--gen-glow, 4px) #fff;
transform: scale(1.1) translateY(-2px); }
20% { opacity: 0.2; }
100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.gen-scan::after { animation: none; opacity: 0; }
.gen-letter, .gen-dot { animation: none; opacity: 1; }
}
</style>
<!-- scan color preset switcher (JavaScript) -->
<script>
const presets = {
chromatic: ['#ff0','#f00','#0ff','#0f0','#00f'],
'ai-blue': ['#00e5ff','#0066ff','#00b4d8','#0040ff','#48cae4'],
amber: ['#ffd60a','#ff8c00','#ffc107','#ff6d00','#ffb300'],
};
document.querySelectorAll('[data-preset]').forEach(btn => {
btn.addEventListener('click', () => {
const colors = presets[btn.dataset.preset];
if (!colors) return;
const el = document.querySelector('.gen-wrapper');
colors.forEach((c, i) => el.style.setProperty(`--scan-c${i + 1}`, c));
document.querySelectorAll('[data-preset]').forEach(b => b.setAttribute('aria-pressed', 'false'));
btn.setAttribute('aria-pressed', 'true');
});
});
</script>
React (JSX + CSS)
// react/S-010.jsx
import "./S-010.css";
const STEP_MS = 105;
const DOTS = 3;
const DOT_STEP_MS = 180;
export default function GeneratingLoader({
text = "Generating",
duration = 4,
glow = 4,
fontSize = "2.8em",
scanPreset = "chromatic", // "chromatic" | "ai-blue" | "amber"
}) {
const letters = text.split("");
const lastDelay = 0.1 + (letters.length - 1) * STEP_MS / 1000;
return (
<div
className={`gen-loader-wrapper gen-loader-wrapper--${scanPreset}`}
style={{
"--gen-duration": `${duration}s`,
"--gen-glow": `${glow}px`,
fontSize,
}}
>
{letters.map((char, i) => (
<span
key={i}
className="gen-loader-letter"
style={{ animationDelay: `${(0.1 + (i * STEP_MS) / 1000).toFixed(3)}s` }}
>
{char}
</span>
))}
{Array.from({ length: DOTS }, (_, i) => (
<span
key={`dot-${i}`}
className="gen-loader-dot"
style={{ animationDelay: `${(lastDelay + STEP_MS / 1000 + (i * DOT_STEP_MS) / 1000).toFixed(3)}s` }}
>
·
</span>
))}
<div className="gen-loader-scan" />
</div>
);
}
AIへの指示テンプレート AI Prompt Template
以下のテンプレートをコピーしてAIアシスタントに貼り付けると、このパターンの実装を依頼できます。 Copy the template below and paste it into your AI assistant to request an implementation of this pattern.