🎰
男塾名物・戦吉兆占針盤のWebアプリを作った
ゆるWeb勉強会@札幌 Advent Calendar 2023 23日目の記事です。
前置き
ハンバーガーメニュー ケチャップ抜きで。DE-TEIUです。
成果物
名作漫画「魁!!男塾」に登場する戦吉兆占針盤のWebアプリを作りました。
100マス中99マスが「勝利」になっているルーレットです。
ほぼ「勝利」の目しか出ないため、戦の前の景気付けなどに使えます。
解説(超ざっくり)
以下、実装の解説など
Fabric.jsを導入
Fabric.jsとは、HTML5のcanvas要素の機能を拡張するライブラリです。
キャンバスに追加したオブジェクトを動かしたりできます。
インストール方法(npmを使う場合)
npm install fabric
TypeScriptでFabric.jsの型定義を使いたい場合は、以下も一緒にインストールしておきましょう。
npm install --save-dev @types/fabric
呼ぶ時はJSのコード内でこんな風にインポートできます。
import { fabric } from 'fabric';
canvasにルーレット盤を描画
ルーレット盤(文字を除く土台)の描画については、JavaScript標準のCanvas APIでやっています。
実装イメージは以下。
const CANVAS_WIDTH = 480; //キャンバスの幅
const CANVAS_HEIGHT = 480; //キャンバスの高さ
const CENTER = { //キャンバスの中央の座標
X: CANVAS_WIDTH / 2,
Y: CANVAS_HEIGHT / 2
};
const RESULT_COUNT = 100; //ルーレットのマス目の数
const RESULT_ANGLE = 360 / RESULT_COUNT; //ルーレット1マス分の円弧の角度
const drawRouletteBoard = () => {
//※HTML内に<canvas id="canvas"></canvas>の要素があるものとする
const rouletteBoardCanvas = document.getElementById("canvas");
const context = rouletteBoardCanvas.getContext('2d')!;
[...Array(RESULT_COUNT)].map((_, i) => { //マス目の数分ループ
context.beginPath(); //図形を描画する領域(パス)の生成を開始
context.moveTo(CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2); //パスをキャンバスの中央に移動
context.fillStyle = i % 2 === 0 ? '#666666' : '#999999'; //パス内を塗りつぶす色を指定
const startAngle = (RESULT_ANGLE * (i + 1) * Math.PI) / 180; //マスの描画開始角度
const endAngle = (RESULT_ANGLE * i * Math.PI) / 180; //マスの描画終了角度
context.arc(CENTER.X, CENTER.Y, CANVAS_WIDTH / 2, startAngle, endAngle, true); //孤を描画
context.fill(); //領域を塗りつぶす
});
};
針を回す
針は描画後に動かすので、fabric.jsを使って動的なオブジェクトとしてキャンバスに追加する。
const drawArrow = () => {
fabric.Image.fromURL('/arrow.png', (img) => { //針の画像を読み込んでfabric用のImageオブジェクトを生成
img.selectable = false;
img.originX = 'left';
img.originY = 'center';
img.left = CENTER.X;
img.top = CENTER.Y;
img.scale(0.5);
img.centeredRotation = false;
arrowImage = img;
fabricCanvas.add(arrowImage);
drawHead(); // ルーレット中央の黄色い円を描画
});
};
針を動かしてみる。
const startRotate = () => {
if (isStarted) {
return;
}
resultText = '';
isStarted = true;
acceleration = 0;
const MAX_SPEED = 17;
speed = MAX_SPEED;
move();
};
const move = () => {
const MIN_SPEED = 0.1;
speed += acceleration; //針の回転速度UP
arrowImage.rotate(arrowImage.angle! + speed); //針を回転させる
fabricCanvas.renderAll(); //fabricのキャンバスを再描画
if (speed <= MIN_SPEED) { //針の速度が一定以下になったら停止して勝利判定
speed = 0;
isStarted = false;
if (isWin()) {
resultText = '勝利';
// party.jsで紙吹雪をまく
party.confetti(document.body, {
count: party.variation.range(80, 100),
spread: 40,
speed: party.variation.range(50, 600),
size: party.variation.skew(1, 0.8),
rotation: () => party.random.randomUnitVector().scale(180)
});
} else {
resultText = '死';
}
return;
}
fabric.util.requestAnimFrame(move); //キャンバスが再描画できる状態になったらmoveメソッドを再実行
};
ソースコード
GitHubに置いてあります。
Discussion