CSSアニメーションとJinja2で実装する表情が変化するキャラクター

## はじめに
jinja2はPythonのテンプレートエンジンで、HTMLやCSSを動的に生成するのに便利です。本記事では、jinja2を使ってCSSアニメーションを生成し、キャラクターの表情を変化させる方法を紹介します。  
アニメーションのパラメータはPython側で調整でき、柔軟な設計が可能です。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/ae7d74ea-3db8-602a-9578-10eb89a3478d.png)

## 出力例
![animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b213a9f9-d5d3-d028-73ff-c4fb17ed5c83.gif)

## 実装コード

```python
from jinja2 import Template

# HTMLテンプレートの定義
html_template = """
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>キャラクター表情アニメーション</title>
    <style>
        @keyframes eye-transform {
            0%, 15% { transform: scale(1); opacity: 1; }
            35%, 65% { transform: scale(0.5); opacity: 0; }
            85%, 100% { transform: scale(1); opacity: 1; }
        }
        
        @keyframes angry-eye-transform {
            0%, 15% { transform: scale(0.5); opacity: 0; }
            35%, 65% { transform: scale(1); opacity: 1; }
            85%, 100% { transform: scale(0.5); opacity: 0; }
        }

        @keyframes tail-swing {
            0%, 100% { transform: rotate(0deg); }
            50% { transform: rotate({{ tail_swing_degree }}deg); }
        }
        
        .normal-eye {
            animation: eye-transform {{ expression_speed }}s ease-in-out infinite;
        }
        
        .angry-eye {
            animation: angry-eye-transform {{ expression_speed }}s ease-in-out infinite;
        }
        
        #tail {
            transform-origin: 35px 30px;
            animation: tail-swing {{ tail_speed }}s infinite;
        }
    </style>
</head>
<body>
    <svg width="200" height="200" viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg">
        <ellipse cx="0" cy="0" rx="50" ry="45" fill="#4CAF50" />
        <ellipse cx="0" cy="0" rx="30" ry="25" fill="#FDF1D2" />
        <!-- 耳 -->
        <path d="M -20 -32 C -30 -50, -10 -60, -15 -40 C -18 -35, -18 -35, -20 -32 Z" fill="#4CAF50" />
        <path d="M 20 -32 C 30 -50, 10 -60, 15 -40 C 18 -35, 18 -35, 20 -32 Z" fill="#4CAF50" />
        
        <!-- 普通の目 -->
        <g class="normal-eye">
            <circle cx="-15" cy="-5" r="2.5" fill="#000" />
            <circle cx="15" cy="-5" r="2.5" fill="#000" />
        </g>
        
        <!-- >< の目 -->
        <g class="angry-eye">
            <path d="M -16 -8 L -10 0" stroke="#000" stroke-width="2" fill="none" />
            <path d="M -10 0 L -16 8" stroke="#000" stroke-width="2" fill="none" />
            <path d="M 16 -8 L 10 0" stroke="#000" stroke-width="2" fill="none" />
            <path d="M 10 0 L 16 8" stroke="#000" stroke-width="2" fill="none" />
        </g>
        
        <!-- ヒゲ -->
        <path d="M -35 -4 L -28 -2" stroke="#000" stroke-width="0.5" fill="none" />
        <path d="M -35 0 L -28 1" stroke="#000" stroke-width="0.5" fill="none" />
        <path d="M -35 4 L -28 4" stroke="#000" stroke-width="0.5" fill="none" />
        <path d="M 28 -2 L 35 -4" stroke="#000" stroke-width="0.5" fill="none" />
        <path d="M 28 1 L 35 0" stroke="#000" stroke-width="0.5" fill="none" />
        <path d="M 28 4 L 35 4" stroke="#000" stroke-width="0.5" fill="none" />
        
        <!-- 鼻 -->
        <circle cx="-2" cy="0" r="1" fill="#000" />
        <circle cx="2" cy="0" r="1" fill="#000" />
        
        <!-- 口 -->
        <path d="M -5 5 Q 0 8 5 5" stroke="#000" stroke-width="1" fill="none" />
        
        <!-- 尻尾 -->
        <path id="tail" d="M 35 30 C 45 40, 65 35, 55 20 C 50 10, 40 20, 35 30 Z" fill="#4CAF50" />
    </svg>
</body>
</html>
"""

# テンプレートのレンダリングとファイル出力
def generate_character_html(
    expression_speed=2.0,   # 表情の変化速度
    tail_speed=3.0,         # 尻尾のスイング速度
    tail_swing_degree=20    # 尻尾の振り角度
):
    template = Template(html_template)
    rendered_html = template.render(
        expression_speed=expression_speed,
        tail_speed=tail_speed,
        tail_swing_degree=tail_swing_degree
    )
    
    with open("character_expressions.html", "w", encoding="utf-8") as f:
        f.write(rendered_html)

# HTMLファイルの生成
generate_character_html()
```

## 実装のポイント

### テンプレートエンジンの活用
- Jinja2を使用してHTMLとCSSを動的に生成
- アニメーションのパラメータを変数化して柔軟な調整が可能
  - `expression_speed`: 表情の切り替え速度
  - `tail_speed`: 尻尾の動く速度
  - `tail_swing_degree`: 尻尾の振れ幅

### CSSアニメーション設計
- `transform`と`opacity`を組み合わせた自然な表情遷移
- 丸目と><目の切り替えタイミングを微調整
- `ease-in-out`タイミング関数で滑らかな動きを実現

## カスタマイズ例

```python
# アニメーションをよりゆっくりに
generate_character_html(
    expression_speed=3.0,
    tail_speed=4.0,
    tail_swing_degree=15
)

# より活発な動きに
generate_character_html(
    expression_speed=1.5,
    tail_speed=2.0,
    tail_swing_degree=25
)
```

## まとめ
Jinja2とCSSアニメーションを組み合わせることで、柔軟にカスタマイズ可能なアニメーションキャラクターを実装できました。パラメータを変更することで、キャラクターの表情や動きを簡単に調整できます。