AI先生のロボットキャラクター
第11章 - セクション1

曇りガラスに文字を書くSVGアニメーション

SVGフィルターとマスクを使った、曇りガラスに指で文字を書いたような印象的なヒーローセクション

男子生徒のアイコン

SVGで曇りガラスに文字を書いたようなエフェクトって作れるんですか?

AI先生のアイコン

作れるよ。SVGのフィルター機能とマスクを組み合わせると、曇ったガラスに指で文字を書いたような演出ができるんだ。ヒーローセクションとして活用できるよ。

女子生徒のアイコン

どんな仕組みで作るんですか?

AI先生のアイコン

ポイントは3つの層を重ねること。まず背景画像をクリアに表示し、その上に曇りガラス層を重ねる。そして文字部分だけをマスクでくり抜いて、下のクリアな背景が見えるようにするんだ。文字の描画はstroke-dashoffsetアニメーションで順番に表示していくよ。

作成したヒーローセクション

背景画像、曇りガラスフィルター、マスクによる文字のくり抜き、描画アニメーションという4つの要素を組み合わせています。冬の窓ガラスに息を吹きかけて、指で文字を書いているようなイメージで作成しました。

HTML
<svg viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice">
    <defs>
        <!-- 曇りガラスフィルター -->
        <filter id="fogFilter">
            <feGaussianBlur in="SourceGraphic" stdDeviation="12" result="blur" />
            <feComponentTransfer>
                <feFuncR type="linear" slope="1.25" />
                <feFuncG type="linear" slope="1.25" />
                <feFuncB type="linear" slope="1.25" />
            </feComponentTransfer>
        </filter>

        <!-- ガラスの影 -->
        <filter id="glassShadow">
            <feDropShadow dx="0" dy="28" stdDeviation="35" flood-color="rgba(0,0,0,0.18)" />
        </filter>

        <!-- 文字ハイライト用フィルター -->
        <filter id="brightFilter">
            <feColorMatrix type="saturate" values="1.2" />
            <feComponentTransfer>
                <feFuncR type="linear" slope="0.7" />
                <feFuncG type="linear" slope="0.7" />
                <feFuncB type="linear" slope="0.7" />
            </feComponentTransfer>
        </filter>

        <!-- 文字パス定義 -->
        <g id="textPaths" transform="translate(50, 0)">
            <path class="letter" d="M 120 190 Q 60 190 60 225 Q 60 260 120 260 Q 155 260 165 250" pathLength="100"/>
            <path class="letter" d="M 210 190 L 210 260 L 275 260" pathLength="100"/>
            <path class="letter" d="M 305 260 L 360 190 L 415 260" pathLength="100"/>
            <path class="letter" d="M 325 235 L 395 235" pathLength="100"/>
            <path class="letter" d="M 445 260 L 445 190" pathLength="100"/>
            <path class="letter" d="M 445 190 Q 445 190 490 190 Q 525 190 525 212 Q 525 234 490 234 L 445 234" pathLength="100"/>
            <path class="letter" d="M 485 234 L 525 260" pathLength="100"/>
            <path class="letter" d="M 575 190 L 575 260" pathLength="100"/>
            <path class="letter" d="M 610 190 L 695 190" pathLength="100"/>
            <path class="letter" d="M 652 190 L 652 260" pathLength="100"/>
            <path class="letter" d="M 730 190 L 775 220" pathLength="100"/>
            <path class="letter" d="M 820 190 L 775 220" pathLength="100"/>
            <path class="letter" d="M 775 220 L 775 260" pathLength="100"/>
        </g>

        <!-- マスク定義 -->
        <mask id="glassMask">
            <rect width="100%" height="100%" fill="black" />
            <g transform="translate(470, 280)">
                <rect x="0" y="0" width="980" height="520" rx="28" fill="white" />
                <g stroke="black" stroke-width="32" fill="none" stroke-linecap="round" stroke-linejoin="round">
                    <use href="#textPaths" />
                </g>
            </g>
        </mask>

        <!-- 文字ハイライト用マスク -->
        <mask id="textHighlightMask">
            <rect width="100%" height="100%" fill="black" />
            <g transform="translate(470, 280)">
                <g stroke="white" stroke-width="32" fill="none" stroke-linecap="round" stroke-linejoin="round">
                    <use href="#textPaths" />
                </g>
            </g>
        </mask>
    </defs>

    <!-- 1. クリアな背景画像 -->
    <image href="/img/ice-6538605_1920.jpg" 
           width="100%" height="100%" preserveAspectRatio="xMidYMid slice" />

    <!-- 2. 曇りガラス層 -->
    <g transform="translate(470, 280)" filter="url(#glassShadow)">
        <rect width="980" height="520" rx="28" fill="rgba(0,0,0,0.01)" />
    </g>

    <image href="/img/ice-6538605_1920.jpg" 
           width="100%" height="100%" preserveAspectRatio="xMidYMid slice"
           filter="url(#fogFilter)" 
           mask="url(#glassMask)" />

    <!-- 文字部分のハイライト -->
    <image href="/img/ice-6538605_1920.jpg" 
           width="100%" height="100%" preserveAspectRatio="xMidYMid slice"
           filter="url(#brightFilter)" 
           mask="url(#textHighlightMask)" />

    <!-- 3. ガラスの質感オーバーレイ -->
    <g transform="translate(470, 280)" pointer-events="none">
        <rect width="980" height="520" rx="28" 
              fill="none" stroke="rgba(255,255,255,0.28)" stroke-width="1" />
        <rect width="980" height="520" rx="28" 
              fill="rgba(255,255,255,0.05)" />

        <!-- サブタイトル -->
        <text class="subtitle" x="490" y="355" text-anchor="middle">
            <tspan x="490" dy="0">Through the glass, beauty transcends</tspan>
            <tspan x="490" dy="34">Where light and clarity become one</tspan>
        </text>
    </g>

</svg>
CSS
body, html { 
    margin: 0; 
    padding: 0; 
    height: 100%; 
    overflow: hidden; 
    background: #0b0f16; 
}

svg { 
    width: 100vw; 
    height: 100vh; 
    display: block; 
}

/* フォント設定 */
.subtitle {
    font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: 24px;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    font-weight: 600;
    fill: rgba(255,255,255,1);
    filter: drop-shadow(0 2px 6px rgba(0,0,0,0.2));
}

/* アニメーション */
.letter {
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
}

.letter:nth-of-type(1) { animation: drawLetter 0.8s ease-out forwards; animation-delay: 1.5s; }
.letter:nth-of-type(2) { animation: drawLetter 0.6s ease-out forwards; animation-delay: 2.3s; }
.letter:nth-of-type(3) { animation: drawLetter 0.6s ease-out forwards; animation-delay: 2.9s; }
.letter:nth-of-type(4) { animation: drawLetter 0.3s ease-out forwards; animation-delay: 3.5s; }
.letter:nth-of-type(5) { animation: drawLetter 0.5s ease-out forwards; animation-delay: 3.8s; }
.letter:nth-of-type(6) { animation: drawLetter 0.6s ease-out forwards; animation-delay: 4.3s; }
.letter:nth-of-type(7) { animation: drawLetter 0.4s ease-out forwards; animation-delay: 4.9s; }
.letter:nth-of-type(8) { animation: drawLetter 0.5s ease-out forwards; animation-delay: 5.3s; }
.letter:nth-of-type(9) { animation: drawLetter 0.5s ease-out forwards; animation-delay: 5.8s; }
.letter:nth-of-type(10) { animation: drawLetter 0.5s ease-out forwards; animation-delay: 6.3s; }
.letter:nth-of-type(11) { animation: drawLetter 0.4s ease-out forwards; animation-delay: 6.8s; }
.letter:nth-of-type(12) { animation: drawLetter 0.4s ease-out forwards; animation-delay: 7.2s; }
.letter:nth-of-type(13) { animation: drawLetter 0.4s ease-out forwards; animation-delay: 7.6s; }

@keyframes drawLetter {
    to { stroke-dashoffset: 0; }
}

.fade-in {
    opacity: 0;
    animation: fadeIn 0.5s ease-out forwards;
    animation-delay: 2s;
}

@keyframes fadeIn {
    to { opacity: 1; }
}

AIへのプロンプト例

AIで作成する際は、視覚的な雰囲気を伝えると良いでしょう。技術的な詳細ではなく、実現したいイメージを言葉で表現します。

以下のようなプロンプトの例です:

SVGを使用して、曇りガラスに指で文字を書いたような印象的なヒーローセクションを作成してください。

### 全体の雰囲気
- 冬の窓ガラスに息を吹きかけて曇らせ、指で文字を書いたような自然な質感
- シンプルで落ち着いた見た目のレイアウト

### 構成イメージ
- 背景には美しい氷の結晶のような画像を全画面で配置
- 画面中央に角丸の曇りガラスパネルを浮かせる
- パネル内に「CLARITY」という文字を大きく表示
- 文字部分だけ曇りが拭き取られたように、下の背景がクリアに透けて見える
- 文字の下にはエレガントな英文サブタイトルを配置

### 曇りガラスの質感
- ガラスパネル部分は全体的にぼかしを入れて曇った質感
- 曇りの強さは適度に、背景の雰囲気が感じられる程度
- パネル表面には薄く白いオーバーレイで光沢感を追加
- パネルの下には柔らかい影を落として、浮遊している印象を演出

### 文字描画のアニメーション
- 文字は線(ストローク)として一筆書きのように描画
- ページ読み込みから少し間を置いて、ゆっくりと文字が現れ始める
- 各文字が順番に自然なリズムで描かれていく
- 描画速度は文字の複雑さに応じて変化し、全体で心地よいテンポ
- すべての文字が描かれた後、サブタイトルがフェードイン

このヒーローセクションの特徴

  • 3層レイヤー構造 背景画像、曇りガラス層、ハイライト層を重ねることで、奥行きのある表現にしています。層の重なりによって質感を表現している点が特徴です。
  • SVGフィルターによる質感表現 feGaussianBlurでぼかしを作り、feComponentTransferで明るさを調整することで、曇りガラスの質感を再現しています。
  • マスクを活用した透明効果 文字部分だけを透明にして、下の背景を見せるマスク処理です。白と黒で表示・非表示を制御する仕組みを使い、文字の形にガラスをくり抜いたような表現にしています。
  • 順次描画アニメーション stroke-dashoffsetアニメーションで、文字が一筆書きのように順番に描かれていきます。各文字の描画時間とタイミングを個別に設定しています。
  • ガラスの質感を出すディテール 影、光沢、ボーダーといった要素の組み合わせで、ガラスの質感を表現しています。

コードのポイント

フィルターとマスクという2つの技術を組み合わせています。これにより、ブラウザ上で画像加工のような表現や、通常のCSSでは難しい「特定の形だけ透明にする」という表現が可能になります。

曇りガラスフィルターの仕組み

<filter id="fogFilter">
    <feGaussianBlur in="SourceGraphic" stdDeviation="12" result="blur" />
    <feComponentTransfer>
        <feFuncR type="linear" slope="1.25" />
        <feFuncG type="linear" slope="1.25" />
        <feFuncB type="linear" slope="1.25" />
    </feComponentTransfer>
</filter>

feGaussianBlurで画像をぼかし、feComponentTransferで明るさを調整することで、曇りガラスの質感を作り出しています。

この2つのフィルターの組み合わせがポイントです。単にぼかすだけでは暗くなってしまうため、明るさを調整することで、曇りガラスの質感に近づけています。

stdDeviationの値を大きくするとより強くぼけ、slopeの値を変えることで明るさをコントロールできます。背景画像の明るさや色味に合わせて、これらの値を微調整することで、最適な見た目を作り出せます。

マスクによる文字のくり抜き

<mask id="glassMask">
    <rect width="100%" height="100%" fill="black" />
    <g transform="translate(470, 280)">
        <rect x="0" y="0" width="980" height="520" rx="28" fill="white" />
        <g stroke="black" stroke-width="32" fill="none">
            <use href="#textPaths" />
        </g>
    </g>
</mask>

マスクの基本原理は「白い部分が表示され、黒い部分が非表示になる」というシンプルなものです。

このパーツでは、まず全体を黒(非表示)にします。次にガラスパネル部分だけを白(表示)にして、曇りガラス効果が見えるようにします。そして最後に、文字部分を再び黒(非表示)にすることで、文字の形に穴が開いた状態を作ります。

この穴から下のクリアな背景が見えるため、まるで曇りを指で拭き取ったような表現が実現できるのです。マスクはPhotoshopのレイヤーマスクと同じ考え方なので、画像編集経験があれば理解しやすいでしょう。

文字描画アニメーション

.letter {
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
}

.letter:nth-of-type(1) { 
    animation: drawLetter 0.8s ease-out forwards; 
    animation-delay: 1.5s; 
}

@keyframes drawLetter {
    to { stroke-dashoffset: 0; }
}

stroke-dasharraystroke-dashoffsetを使った、SVGアニメーションの手法です。線を「点線のようにして、その点線の開始位置をずらす」という仕組みを利用しています。

最初は線全体が非表示の状態(offset: 100)から始まり、アニメーションで徐々に表示(offset: 0)されていきます。これにより、まるで誰かが目の前で線を描いているような効果が生まれます。

各文字に異なるanimation-delayを設定することで、順番に描画される効果を実現しています。このタイミングの調整が、アニメーション全体の印象を大きく左右します。速すぎると慌ただしく、遅すぎると間延びしてしまうため、適切なリズム感を見つけることが重要です。

3層の画像レイヤー

<!-- 1. クリアな背景 -->
<image href="/img/ice-6538605_1920.jpg" ... />

<!-- 2. 曇りガラス層(マスク適用) -->
<image href="/img/ice-6538605_1920.jpg" 
       filter="url(#fogFilter)" 
       mask="url(#glassMask)" />

<!-- 3. 文字ハイライト層 -->
<image href="/img/ice-6538605_1920.jpg" 
       filter="url(#brightFilter)" 
       mask="url(#textHighlightMask)" />

同じ背景画像を3回使用していますが、それぞれ異なる役割と効果を持っています。この重ね合わせの技術が、このパーツの立体感を生み出す核心部分です。

  • 1層目のクリアな背景
    • 文字部分で見える本来の画像です。これがあることで、文字の下だけが鮮明に見えるという対比が生まれます。
  • 2層目の曇りガラス層
    • マスクで切り抜かれた状態で、ガラスパネル部分にのみ表示されます。ぼかしフィルターが適用されているため、背景がぼんやりと見える曇りガラスの質感を表現します。
  • 3層目の文字ハイライト層
    • 文字部分だけを少し明るくする役割です。これにより、文字が単に透明なだけでなく、まるで光が透過しているような印象を与え、より自然な見た目になります。

この3層の重ね合わせによって、質感と奥行きを表現しています。

まとめ

女子生徒のアイコン

SVGのフィルターとマスクを組み合わせると、本当にガラスに文字を書いたような表現ができるんですね。

AI先生のアイコン

そういうこと。今回学んだ3つの技術、feGaussianBlur(ぼかし)、mask(マスク)、stroke-dashoffset(線の描画)を組み合わせることで、印象的なヒーローセクションが完成したね。

男子生徒のアイコン

この技術、他にも応用できそうですね!

AI先生のアイコン

もちろん。例えば曇りガラスの代わりに磨りガラス風にしたり、文字ではなくロゴやイラストを描画したり、いろいろな応用ができるよ。

曇りガラスエフェクトのポイント
  • レイヤー構造の理解 3層の画像レイヤー(背景、曇りガラス、ハイライト)を重ねることで、奥行きのある表現を実現します。それぞれの層が異なる役割を持ち、組み合わせることで完成します。
  • フィルターの調整 stdDeviationでぼかしの強さ、slopeで明るさを調整できます。背景画像に合わせて数値を変更することで、最適な見た目を作り出せます。
  • マスクの仕組み 白い部分が表示され、黒い部分が非表示になるというマスクの基本原理を理解することが重要です。この仕組みを使って、文字の形にくり抜く表現を実現しています。
  • アニメーションタイミング 各文字のanimation-delayを調整することで、描画のリズムをコントロールできます。速すぎず遅すぎない、心地よいテンポを見つけましょう。

学習チェック

このレッスンを理解できたら「完了」をクリックしてください。
後で見直したい場合は「未完了に戻す」で進捗をリセットできます。

レッスン完了!🎉

お疲れさまでした!