デザイナーがAIとペアプロで作る「くす玉おみくじ」

SVGアニメ・確率抽選・外部コメント・Xシェア・OG画像の設計まで

AI(ChatGPT)と人間がペアで進めながら、HTML/CSS/JSだけで「くす玉おみくじ」を作りました。
この記事は、その過程で得た学びをコーダー/Webデザイナー/フロントエンドエンジニア向けに、実装の勘所と失敗例も含めてコンパクトにまとめたものです。

AIでコーディングをしてみるか!と思い立ってからおおよそ2時間半でひとまず生成完了しました。うち1時間以上はくす玉の挙動にハマっていたので、スムーズにいけば1時間程度であるていどのページは生成できそうです。

実際に作ったページはこちらです→https://mamenari.com/omikuji/

1. ゴールと完成像

最終的に実装したのは以下です。

  • くす玉の半円が上端のヒンジを支点に外側へ開くSVGアニメ
  • 紐を3回引くモーション(紐の長さも同期)
  • 紙吹雪がくす玉中心±半径/2の範囲から扇状に降下
  • 7種類のおみくじ(プリセットCの重み)から重み付きランダム抽選
  • 各吉凶ごとに外部JSONから1コメントだけ添付(差し替え容易)
  • 結果は中央表示&吹き出しKaisei Decol Boldを採用
  • X(Twitter)シェア:メンション・本文テンプレ・現在のURL
  • アクセシビリティprefers-reduced-motion対応、aria-live

2. 要件の分解(仕様を先に固める)

実装前に、AIに丸投げせず仕様を箇条書きにしました。
おみくじの種類や確率などはAIにいくつかパターンを出してもらって決めました。

  • おみくじ:
    大吉/吉/中吉/小吉/末吉/凶/大凶(重み=C:15/22/18/13/10/17/5)プリセットC
  • コメント:
    各結果に3候補comments.jsonで管理 → 1件だけ表示
  • 見た目:
    パステルのグラデ、吹き出しは中央揃え、フォントはKaisei Decol Bold
  • アニメ順:
    紐3回半円が±90°で開く紙吹雪 → 結果表示 → くす玉消去
  • 共有:
    Xインテントでメンション付き投稿(URLはページ自身)

3. 最小ファイル構成

  • index.html … 構造・フォント読み込み・結果/シェアUI
  • styles.css … レイアウト・配色トークン・SVG/紐/紙吹雪アニメ
  • script.js … 重み抽選・コメント読込・モーション制御・共有URL生成
  • comments.json … 結果→コメント配列(差し替えOK)

ポイントはコンテンツ(コメント)を外出しして、あとから更新できるようにしていることです。
例えばクライアント側での更新や、Wordpressなどで生成ということも想定して別ファイルを読み込む仕様にしています。

4. SVGアニメの落とし穴と突破口

SVGの回転アニメでつまずきやすいポイントを先に共有します。

今回の失敗

  • マスクされる問題:SVGや親要素のoverflow: hiddenで、回転中に半円が切れる
  • 形がゆがむ問題:半円のパス定義が曖昧で、回転時にラグビーボール化
  • 向きが逆問題:回転方向(符号)が直感と逆で、閉じ方が反転して見える

成功パターン(コアロジック)

  • “縦半円×2”で正円を作る(左半円・右半円を別パスで)
  • それぞれに
.semi { transform-box: view-box; transform-origin: 50% 0%; } /* 上端中央=ヒンジ */
.open .left  { transform: rotate( 90deg); }  /* 外側へ */
.open .right { transform: rotate(-90deg); }  /* 外側へ */
  • さらに、SVG側にも overflow: visible; を付けて切れ防止

半円パスの設定

<!-- 左半円(縦割り):上(120,0) → 左弧で下(120,240) → 縦の弦で上に戻る -->
<path class="semi left"  d="M120 0 A120 120 0 0 0 120 240 L120 0 Z"/>
<!-- 右半円(縦割り):上 → 右弧で下 → 縦の弦で上に戻る -->
<path class="semi right" d="M120 0 A120 120 0 0 1 120 240 L120 0 Z"/>

くす玉の開くモーションが一番ハマった…
総制作時間2時間半のうち1時間以上ここでハマる。
挙動を動画にして、AIに投げて確認してもらうなども行っています。
この部分、JSでアニメーションをつくるプログラミング的な知識があればハマらずに自分で調整ができたと思う。地頭大切。

5. 重み付き抽選(Weighted Random/確率に重みを付けた抽選)とコメント選択

偏りを出したいときの定番です。7種類のおみくじは、15/22/18/13/10/17/5 の重みで抽選します。

const FORTUNES = [
  { key:"daikichi", label:"大吉", weight:15 },
  { key:"kichi",    label:"吉",   weight:22 },
  { key:"chuukichi",label:"中吉", weight:18 },
  { key:"shoukichi",label:"小吉", weight:13 },
  { key:"suekichi", label:"末吉", weight:10 },
  { key:"kyou",     label:"凶",   weight:17 },
  { key:"daikyou",  label:"大凶", weight:5  },
];

function drawFortune(){
  const total = FORTUNES.reduce((s,f)=>s+f.weight,0);
  let r = Math.random()*total;
  for(const f of FORTUNES){ if(r < f.weight) return f; r -= f.weight; }
  return FORTUNES[FORTUNES.length-1];
}

async function loadComments(){
  const res = await fetch("comments.json",{ cache:"no-store" });
  return res.ok ? res.json() : {};
}

const pickOne = arr => Array.isArray(arr)&&arr.length
  ? arr[(Math.random()*arr.length)|0] : null;
  • コメントは各くじに3つ用意し、毎回そのうち1件だけをランダムに表示します。くじの当たり外れに加えてコメントもランダムになることで、同じ体験が続きにくく、何度引いても新鮮で楽しくおみくじを遊べるようにするためです。
  • comments.json差し替え可能なので、運用面でも安心です。

6. くす玉のモーションを作り込む

紐の揺れと長さ同期

紐とくす玉本体を同じタイミングの「引っ張りアニメーション(yank」で同期させ、紐は基準長(--ropeBase)を中心に height を上下させます。

/* くす玉を上下に3回 */
@keyframes yank {
  0% { transform:translate(-50%,0) }
  30%{ transform:translate(-50%,18px) }
  60%{ transform:translate(-50%,-10px) }
  100%{ transform:translate(-50%,0) }
}

/* 紐の高さを同期(--ropeBaseはJSで“ヒンジまで”にセット) */
@keyframes ropeYank {
  0%   { height: var(--ropeBase); }
  30%  { height: calc(var(--ropeBase) + 18px); }
  60%  { height: calc(var(--ropeBase) - 10px); }
  100% { height: var(--ropeBase); }
}

紙吹雪の“起点範囲”と落下角

くす玉中央**±半径/2**の範囲に発生させ、**70〜110°**の扇形で落とします。
見た目が一気にリッチになります。

モバイル最適化

スマホではステージの枠(影・角丸)を削除して、余計な囲いをなくしました。

7. Xでのシェア設計(インテントの制約)

  • WebインテントではテキストとURLのみ事前入力できます(画像は添付不可)。
  • そのため、メンション+テンプレ+結果+コメント+現在URLを組み立てています。
function buildXShareUrl(label, comment){
  const handle = "@kk_mamenari";
  const text = `ChatGPT-5で文字だけでおみくじページ作ってみた! ${handle}\n`
             + `結果:${label}${comment ? `\n「${comment}」` : ""}`;
  const params = new URLSearchParams({ text, url: location.href.split('#')[0] });
  return `https://twitter.com/intent/tweet?${params}`;
}

画像も付けたい場合は、Web Share API(対応端末限定)か、
OG画像の導入(次章)を検討します。

※これ以降のOG画像は未実装です。

8. OG画像はどう作る?(選択肢と難しさ)

3つの選択肢があります。

  1. 固定OG画像:サイト共通の1枚 → 最も簡単
  2. 7種パターン:結果ごとに7枚用意 → コメントは載らない
  3. ダイナミックOG:結果+コメントから都度生成 → 要サーバ

ダイナミックOGの難所

  • クローラはJSを実行しないため、結果ごとの専用URL(例 /share/abc123)でメタタグをサーバ出力する必要があります。
  • 画像は**Satori(React→SVG)+ ResVG(SVG→PNG)**が軽量で現実的です。
  • 日本語フォントを同梱(Noto系のサブセット化推奨)。
  • レート制限署名で悪用対策。
  • キャッシュ戦略(CDN/KV)でバズ時の負荷を吸収。

共有サーバでの現実解

一般的な共有レンタルサーバはNodeランタイムを提供しない場合が多いです。
その場合は、ページ本体はサーバ(PHP等)画像生成は外部サーバレス(Cloudflare Workers / Vercel Edge)というハイブリッドが取りやすいです。

まずは固定OG7種パターンから始め、余力が出たらダイナミックへ、が失敗しにくい進め方です。

9. デプロイと運用のコツ

  • キャッシュバスティング:CSS/JSは?v=...で管理、comments.jsonno-storeで即時反映
  • prefers-reduced-motion:動きをカットして即結果表示にフォールバック
  • エラーハンドリングcomments.jsonが落ちた時はデフォルト文言で表示

10. まとめ(AI×人間の勝ち筋)

  • 要件を先に分割してAIに渡すと、生成のブレが減ります
  • SVGは剛体回転の発想(transform-origin正しいパス定義)で崩れません
  • コンテンツは外部ファイルに出して、運用で育てるのが吉
  • 共有はまずテキスト+URLで実装 → 将来OG画像へ拡張

11.実際に作ってみた感想

AIといっしょにコーディングをしていくことの可能性は十分に感じられた。
AIのプログラミング能力はデザイナーである自分よりも高いが、その能力を引き出すためには適切な指示を与えて上げる必要があるし、コーディングやJSの基本的な知識がないと、おかしな部分に気が付けないし、指示も与えにくい。

これからさらにAIは進化していくと思うが、それを使う自分の頭もアップデートしていかないと、それを活かしたクリエイティブを作ることができないと実感した。

頭を柔らかく、いろいろなことに興味を持って試していくマインドが大切!