ライブデモ 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.

G e n e r a t i n g · · ·
4s
4px
2.8em
スキャンカラー Scan color

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

実装 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.