SVGアニメーションで作る！かわいいもふもふキャラクターGIF

## はじめに
SVGアニメーションを使って、かわいいもふもふキャラクターのGIFアニメーションを作成する方法を紹介します。PythonとSVGを組み合わせることで、軽量で高品質なアニメーションを実現できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/c7dff867-5f30-fdb2-b7a3-22955a968b14.png)

![fluffy_blob.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/4c328450-7c97-f228-c7d4-bbef0f89c770.gif)

## 必要なライブラリ
```python
import os
from PIL import Image
import cairosvg
import math
```
## 実装コード
以下のコードで、ふわふわと浮遊するもふもふキャラクターのアニメーションが作成できます。

```python
import os
from PIL import Image
import cairosvg
import math

def generate_svg_frame_fluffy_blob(frame_num):
    # 浮遊とスクイーズのアニメーション計算
    float_height = -15 * math.sin(frame_num * math.pi / 7)
    squeeze = 0.1 * math.sin(frame_num * math.pi / 4)
    eye_blink = 'scale(1, 1)' if frame_num % 15 != 0 else 'scale(1, 0.1)'
    fluff_wave = 3 * math.sin(frame_num * math.pi / 5)
    
    return f"""
    <svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
        <!-- 背景 -->
        <rect width="300" height="300" fill="#E6F3FF"/>
        
        <!-- 影 -->
        <ellipse cx="150" cy="250" rx="40" ry="15" fill="#A8C6FA" opacity="0.3"/>
        
        <!-- キャラクター本体 -->
        <g transform="translate(150, {200 + float_height}) scale({1 + squeeze}, {1 - squeeze})">
            <!-- もふもふ層1 -->
            {' '.join([f'''
                <circle cx="{40 * math.cos(i * math.pi / 8)}"
                        cy="{40 * math.sin(i * math.pi / 8)}"
                        r="{15 + fluff_wave + 2 * math.sin(frame_num * 0.2 + i)}"
                        fill="#E6EEFF" stroke="#CFE3FF" stroke-width="2"/>
            ''' for i in range(16)])}
            
            <!-- メインボディ -->
            <circle cx="0" cy="0" r="35" 
                    fill="#CFE3FF" stroke="#A8C6FA" stroke-width="2"/>
            
            <!-- 目 -->
            <g transform="{eye_blink}">
                <circle cx="-12" cy="-10" r="4" fill="#6B8EAE"/>
                <circle cx="12" cy="-10" r="4" fill="#6B8EAE"/>
                <circle cx="-12" cy="-11" r="1.5" fill="#FFF"/>
                <circle cx="12" cy="-11" r="1.5" fill="#FFF"/>
            </g>
            
            <!-- ほっぺ -->
            <circle cx="-20" cy="5" r="6" fill="#A8C6FA" opacity="0.4"/>
            <circle cx="20" cy="5" r="6" fill="#A8C6FA" opacity="0.4"/>
        </g>
        
        <!-- 周りのキラキラ -->
        {' '.join([f'''
            <path d="M {150 + 60 * math.cos(frame_num * 0.1 + i)} {100 + 40 * math.sin(frame_num * 0.1 + i)} 
                     l 3,-3 3,3 -3,3 -3,-3"
                  fill="#A8C6FA" opacity="{0.3 + 0.2 * math.sin(frame_num * 0.2 + i)}">
            </path>
        ''' for i in range(5)])}
    </svg>
    """

def create_animation(output_folder, num_frames):
    os.makedirs(output_folder, exist_ok=True)
    frames = []
    
    for i in range(num_frames):
        svg_content = generate_svg_frame_fluffy_blob(i)
        png_file = f"{output_folder}/frame_{i}.png"
        cairosvg.svg2png(bytestring=svg_content.encode('utf-8'), write_to=png_file)
        frames.append(Image.open(png_file))
    
    output_gif = "fluffy_blob.gif"
    frames[0].save(
        output_gif,
        save_all=True,
        append_images=frames[1:],
        optimize=True,
        duration=50,
        loop=0
    )
    
    return output_gif

# アニメーション生成
output_folder = "blob_frames"
num_frames = 30
animation = create_animation(output_folder, num_frames)
```

## 実装のポイント

### 1. もふもふ表現
- 複数の円を重ねてもふもふ感を表現
- `math.sin`を使って毛並みの揺れを表現
- 異なる周期で動かすことでより自然な動きを実現

### 2. アニメーションの工夫
- 浮遊感: `float_height`による上下運動
- スクイーズ: `squeeze`による伸縮
- まばたき: `eye_blink`による目の開閉
- キラキラエフェクト: 周囲を浮遊する装飾要素

### 3. カラーリング
- 青系の優しい色調で統一
- 半透明を活用した柔らかい印象
- グラデーション的な重なりで立体感を表現

## カスタマイズのヒント

1. **色の変更**
   - `fill`属性の値を変更することで、好みの色に調整可能
   - カラーコードは`#E6F3FF`のような16進数形式で指定

2. **動きの調整**
   - `duration`の値を変更してアニメーション速度を調整
   - `math.sin`の係数を変更して動きの大きさを調整
   - `num_frames`を増やしてよりなめらかな動きに

3. **キャラクターのサイズ**
   - `viewBox`や各要素の半径を調整してサイズを変更可能
   - `stroke-width`を調整して線の太さを変更

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/82b7f045-e628-28f2-ce0f-0ee2ce33cdf3.png)

SVGとPythonを組み合わせることで、かわいいアニメーションGIFを作成できました。数学的な関数を使って自然な動きを表現し、SVGの特性を活かした軽量な実装が可能です。このコードを基に、独自のキャラクターやアニメーションを作成してみてください！

## 参考リンク
- [SVG仕様書](https://developer.mozilla.org/ja/docs/Web/SVG)
- [CairoSVG Documentation](https://cairosvg.org/)
- [Pillow Documentation](https://pillow.readthedocs.io/)

## クラス図
```mermaid
classDiagram
    class SVGAnimationGenerator {
        -output_folder: str
        -num_frames: int
        +generate_frames()
        +create_gif()
    }

    class CharacterFrame {
        -frame_num: int
        -config: AnimationConfig
        +generate_svg(): str
        -calculate_float_motion(): float
        -calculate_fluff_wave(): float
        -calculate_squeeze(): float
    }

    class AnimationConfig {
        +float_height_range: float
        +fluff_wave_range: float
        +squeeze_range: float
        +blink_interval: int
        +colors: ColorScheme
    }

    class ColorScheme {
        +background: str
        +main_body: str
        +fluff: str
        +eyes: str
        +cheeks: str
    }

    class GIFConverter {
        -frames: List[Image]
        -duration: int
        -loop: int
        +frames_to_gif(output_path: str)
        -optimize_gif()
    }

    SVGAnimationGenerator --> CharacterFrame : creates >
    SVGAnimationGenerator --> GIFConverter : uses >
    CharacterFrame --> AnimationConfig : uses >
    AnimationConfig --> ColorScheme : contains >
```

## シーケンス図
```mermaid
sequenceDiagram
    actor User
    participant Generator as SVGAnimationGenerator
    participant Frame as CharacterFrame
    participant Config as AnimationConfig
    participant Converter as GIFConverter
    participant FileSystem as File System

    User->>Generator: アニメーション生成開始
    activate Generator
    
    Generator->>Config: 設定読み込み
    activate Config
    Config-->>Generator: 設定返却
    deactivate Config

    loop フレーム数分
        Generator->>Frame: SVGフレーム生成
        activate Frame
        Frame->>Frame: アニメーション計算
        Frame-->>Generator: SVG文字列返却
        deactivate Frame

        Generator->>FileSystem: PNG保存
        activate FileSystem
        FileSystem-->>Generator: 保存完了
        deactivate FileSystem
    end

    Generator->>Converter: GIF変換開始
    activate Converter
    Converter->>FileSystem: フレーム読み込み
    FileSystem-->>Converter: PNG読み込み完了
    Converter->>Converter: フレーム合成
    Converter->>FileSystem: GIF保存
    FileSystem-->>Converter: 保存完了
    Converter-->>Generator: 変換完了
    deactivate Converter

    Generator-->>User: アニメーション生成完了
    deactivate Generator
```