GitHub のプロフィール README に、今日の日付を毛筆で書き順どおりに書いていくアニメーションを置けるようにしました。
GitHub Action として公開しているので、ワークフローを 1 つ足すだけで誰でも使えます。
きっかけ
昔、自分のプロフィールに「今日の漢字」という日付の画像を置いていたのですが、
配信元のサービスが止まって 404 になっていました。
作り直すなら、ただ文字が出るのではなく筆で書いているように見せたいと思ったのが始まりです。
できること
| 任意の文字 + 添え書き + 落款 | 西暦の日付 |
- 漢字・ひらがな・カタカナなら何でも書けます(字形と書き順はその場で取得)
- 今日の日付モード(令和 / 西暦、タイムゾーン指定)
- 小さな添え書きと、朱色の落款(はんこ)
- テーマは
washi(和紙)、sumi(墨)、背景なしのclear/clear-dark
使い方
プロフィール用リポジトリ(<ユーザー名>/<ユーザー名>)に、このワークフローを置くだけです。
name: fude
on:
schedule:
- cron: "5 15 * * *" # 毎日 0:05 (JST)
workflow_dispatch:
permissions:
contents: write
jobs:
write:
runs-on: ubuntu-latest
steps:
- uses: q0v0p/fude@v1
with:
theme: sumi
output: dist/today.svg
- name: output ブランチへ置く
working-directory: dist
run: |
git init -q -b output
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add . && git commit -q -m "fude"
git push -f "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" output
README からはこう参照します。
<img src="https://raw.githubusercontent.com/<ユーザー名>/<ユーザー名>/output/today.svg" width="480"/>
生成物は output ブランチに上書きで push しているので、main の履歴が毎日のコミットで埋まりません。
好きな言葉を書くだけなら、手元で一度作ってコミットしても OK です。
pip install fonttools
python fude.py --text "一期一会" --sub "いちごいちえ" --seal 縁 --theme sumi -o ichigo.svg
仕組み
1. 字形は毛筆フォント、書き順は KanjiVG
最初は KanjiVG の書き順データ(1 画ずつの中心線)をそのまま線で描いていました。
書き順は完璧なのですが、線の太さが一定なのでペン習字にしか見えません。
そこで役割を分けました。
- 見える形: 毛筆体フォント Yuji Boku の輪郭(入り・はね・払い・かすれがある)
- 書く順番: KanjiVG の中心線
KanjiVG の線を**太い筆(マスク)**として 1 画ずつ伸ばしていき、その下にあるフォントの輪郭を現していきます。
こうすると、形は毛筆のまま、書き順どおりに書かれていくように見えます。
<mask id="m0">
<!-- KanjiVG の 1 画。太い白線として伸びていく -->
<path class="st" pathLength="1" d="M31.5,24.5c1.12,1.12 ..."/>
...
</mask>
<!-- 毛筆フォントの輪郭をマスク越しに見せる -->
<path class="ink" mask="url(#m0)" d="..."/>
KanjiVG もフォントも 109×109 の枠に揃えてあるので、座標変換なしで重なります。
マスクの線だけでは筆の輪郭を覆いきれない部分があるので、1 文字書き終えたらマスクを全面に開いて仕上げています。
2. pathLength="1" で線の長さを気にしない
線を伸ばすアニメーションの定番は stroke-dasharray と stroke-dashoffset ですが、
普通は線の長さを事前に知っておく必要があります。
pathLength="1" を付けると、どんな線でも長さが 1 として扱われるので、
全部の画を同じ CSS で動かせます。
.st {
stroke-dasharray: 1 2;
stroke-dashoffset: 1; /* 1 → 0 で書き終わり */
}
3. 筆の緩急
全部の画が同じ速さだと機械的に見えるので、
- 画の長さ(パスの端点をつないだ折れ線で近似)に比例した時間をかける
-
cubic-bezier(.55,0,.35,1)で、入りでためて、送りで速く、収筆でゆるめる
ようにしています。細かいことですが、これで一気に「書いている」感じが出ました。
4. README の <img> の中で動かすための制約
GitHub の README では、SVG は <img> として(しかもプロキシ経由で)表示されます。そのため、
- JavaScript は動かない → CSS アニメーションだけで作る
- 外部のフォントや画像は読めない → 字形はパスとして SVG に埋め込む
という制約があります。fude の出力は完全に 1 ファイルで閉じた SVG なので、そのまま動きます。
ループは、全体の周期を決めて各画の @keyframes をパーセントで書き出すことで実現しています。
5. 地味にハマったところ
-
画像キャッシュ:
raw.githubusercontent.comは 5 分ほどキャッシュします。テーマを変えたのにプロフィールが変わらない……というときは、出力ファイル名を変えるとすぐ反映されます -
スケジュール実行の自動停止: 公開リポジトリの
scheduleは、60 日間動きがないと止まります。ワークフローの最後でgh workflow enableを呼んで延命しています -
Windows の
zoneinfo: Windows の Python には tz データベースがなくAsia/Tokyoが引けません。+09:00のような時差表記も受け付けるようにしました
ライセンス
- コード: MIT
- 書き順データ: KanjiVG © Ulrich Apel(CC BY-SA 3.0)。生成した SVG もこれに従います(クレジットは SVG 内に埋め込み済み)
- 字形: Yuji Boku(SIL Open Font License 1.1)
おわりに
プロフィール README にちょっと和の要素を足したい方は、ぜひ使ってみてください。
気に入ってもらえたら ⭐ をもらえると励みになります!