普通のサイコロ転がしです
機能は特にありません。サイコロを転がすボタン・サイコロの目を予想する入力ボタン・当たりの演出だけです。
このサイコロ転がしは、ゲームというよりはプログラムの全体コード・部分コード等を参考にしていただくのが本来の目的です
サイコロ画像に付きましてはフリー素材からお借りした画像になります。なので独自に開発してWEBで公開する場合は画像使用の配慮が必要かもしれません。


| HTML |
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>サイコロ</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="startTitle">ふつうのさいころ</div>
<div id="game-area">
<canvas id="diceCanvas"width="600"height="400"></canvas>
<!-- キャンバス内上部の操作エリア -->
<div id="dice-controls">
<!-- 1行目:ボタン+結果 -->
<div class="control-row">
<button id="rollBtn">サイコロを振る</button>
<div id="result">結果:</div>
</div>
<!-- 2行目:予想数字+判定 -->
<div class="guess-row">
<label for="targetNumber">
あなたが出したい数字
</label>
<input
type="number"
id="targetNumber"
min="1"
max="6"
placeholder="1~6"
/>
<div id="judgement"></div>
</div>
</div>
</div>
<script src="main.js"></script>
</body>
</html>| JS |
const canvas = document.getElementById("diceCanvas");
const ctx = canvas.getContext("2d");
const rollBtn = document.getElementById("rollBtn");
const result = document.getElementById("result");
const targetNumber = document.getElementById("targetNumber");
const judgement = document.getElementById("judgement");
const bgImg = new Image();
const centerDiceImg = new Image();
bgImg.src = "img/background.png";//ファイル構成は環境に応じて
centerDiceImg.src = "img/center-dice.png";
let bgLoaded = false;
let diceLoaded = false;
bgImg.onload = function () {
bgLoaded = true;
drawStartScreen();
};
centerDiceImg.onload = function () {
diceLoaded = true;
drawStartScreen();
};
function drawStartScreen() {
if (!bgLoaded || !diceLoaded) return;
// 背景
ctx.drawImage(
bgImg,
0,
0,
canvas.width,
canvas.height
);
// 中央の薄いサイコロ
const centerSize = 240;
ctx.save();
ctx.globalAlpha = 0.12;
ctx.drawImage(
centerDiceImg,
// 元画像800×600から600×600を切り出す
100,
0,
600,
600,
// Canvas中央
(canvas.width - centerSize) / 2,
(canvas.height - centerSize) / 2,
centerSize,
centerSize
);
ctx.restore();
}
centerDiceImg.src = "img/dice1.png";
bgImg.onerror = function () {
console.error(
"background.png の読み込みに失敗しました"
);
};
// 紙吹雪
let confetti = [];
function createConfetti() {
confetti = [];
for (let i = 0; i < 60; i++) {
confetti.push({
x: Math.random() * canvas.width,
y: -10 - Math.random() * 80,
size: 4 + Math.random() * 5,
vx: -1.5 + Math.random() * 3,
vy: 1 + Math.random() * 2,
gravity: 0.03 + Math.random() * 0.04,
rotation:
Math.random() * Math.PI * 2,
rotationSpeed:
-0.08 + Math.random() * 0.16,
swing:
Math.random() * Math.PI * 2,
swingSpeed:
0.03 + Math.random() * 0.05
});
}
}
// 紙吹雪を描画
function drawConfetti() {
for (const p of confetti) {
// 横方向に少しヒラヒラ
p.swing += p.swingSpeed;
p.x += p.vx +Math.sin(p.swing) * 0.5;
p.vy += p.gravity;
p.y += p.vy;
p.rotation += p.rotationSpeed;
// 描画
ctx.save();
ctx.translate(p.x,p.y);
ctx.rotate(p.rotation);
ctx.fillStyle = `hsl(${Math.random() * 360}, 80%, 55%)`;
ctx.fillRect(-p.size / 2,-p.size / 2,p.size,p.size);
ctx.restore();
}
// 画面外を削除
confetti =confetti.filter(p => p.y < canvas.height + 20);
}
// 設定
// サイコロの大きさ
const DICE_SIZE = 100;
// キャンバス下端からの余白
const BOTTOM_MARGIN = 25;
// サイコロ画像
const diceImages = [];
for (let i = 1; i <= 6; i++) {
const img = new Image();
img.src = `img/dice${i}.png`;
diceImages.push(img);
}
// サイコロボタン
rollBtn.addEventListener("click", function (e) {
// ボタン連打禁止
if (this.disabled) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
this.disabled = true;
result.textContent = "結果:";
judgement.textContent = "";
judgement.className = "";
confetti = [];
playThrowAnimation();
});
// 背景描画
function drawBackground() {
if (!bgImg.complete) {
return;
}
// 背景画像
ctx.drawImage(bgImg,0,0,canvas.width,canvas.height);
// 中央の薄いサイコロ
if (centerDiceImg.complete) {
const centerSize = 240;
ctx.save();
// 薄く表示
ctx.globalAlpha = 0.12;
ctx.drawImage(
centerDiceImg,
// 元画像 800×600
// 600×600 の正方形を切り出す
100,
0,
600,
600,
// Canvas中央
(canvas.width - centerSize) / 2,
(canvas.height - centerSize) / 2,
// 表示サイズ
centerSize,
centerSize
);
ctx.restore();
}
}
// 投げる前の「手」の演出
function playThrowAnimation() {
const startTime = performance.now();
const duration = 600;
function animateHand(now) {
const elapsed = now - startTime;
let t = elapsed / duration;
if (t > 1) {
t = 1;
}
// 背景
drawBackground();
// 手の位置
const x = canvas.width - 45;
const y = 55 + Math.sin(t * Math.PI * 4) * 8;
// 手の角度
const angle = Math.PI / 2 + Math.sin(t * Math.PI * 4) * 0.18;
// 手の絵文字
ctx.save();
// 手の位置
ctx.translate(x, y);
// 左右反転
ctx.scale(-1, 1);
// 回転軸を右側(手元側)へ
ctx.translate(15, 0);
// 上下の振り
ctx.rotate(angle);
// 元の位置へ戻す
ctx.translate(-15, 0);
ctx.font = "42px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("✋",0,0);
ctx.restore();
// 手の演出終了
if (t >= 1) {
rollDice();
return;
}
requestAnimationFrame(
animateHand
);
}
requestAnimationFrame(
animateHand
);
}
// 角度を -π ~ +π に整理
function normalizeAngle(angle) {
const twoPi = Math.PI * 2;
angle = ((angle + Math.PI) % twoPi + twoPi) % twoPi - Math.PI;
return angle;
}
// イージング
// 最後に向かって非常にゆっくり
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
let x = canvas.width - DICE_SIZE - 20;
let y = 20;
// 地面の高さ
const groundY = canvas.height - DICE_SIZE - BOTTOM_MARGIN;
// サイコロを振る
function rollDice() {
// 最終結果
const finalNumber =
Math.floor(Math.random() * 6) + 1;
// 最初の目
let currentNumber =
Math.floor(Math.random() * 6) + 1;
// 初期位置
let x = canvas.width - DICE_SIZE - 20;
let y = 20;
// 移動
let vx = -5 - Math.random() * 2;
let vy = 0;
const gravity = 0.35;
// 回転
let angle = 0;
let rotationSpeed = 0.25 + Math.random() * 0.15;
// 状態
let state = "air";
let confettiStarted = false;
let bounceCount = 0;
let bounceVelocity = 0;
const bounceGravity = 0.45;
// 目の切り替え
let faceTimer = 0;
let faceInterval = 3;
// 最終停止用
let settleStartTime = 0;
let settleStartAngle = 0;
let settleEndAngle = 0;
// 最後は1.5秒かけて
// ゆっくり正立させる
const settleDuration = 1500;
// 正立後の小さな揺れ
const wobbleDuration = 350;
// サイコロ描画
function drawDice(number, drawAngle) {
const img = diceImages[number - 1];
if (!img) { return;}
const size = DICE_SIZE;
ctx.save();
// サイコロの中心
ctx.translate( x + size / 2, y + size / 2);
// 回転
ctx.rotate(drawAngle);
// サイコロの影
ctx.shadowColor = "rgba(0, 0, 0, 0.35)";
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 6;
// サイコロ描画
ctx.drawImage( img,100, 0,600, 600,-size / 2,-size / 2,size,size);
ctx.restore();
}
// アニメーション
function animate(now) {
ctx.clearRect(0,0,canvas.width,canvas.height);
// 毎フレーム背景を最初に描画
drawBackground();
// 空中
if (state === "air") {
// 重力
vy += gravity;
// 移動
x += vx;
y += vy;
// 回転
angle += rotationSpeed;
// 目を変更
faceTimer++;
if (faceTimer >= faceInterval) {
currentNumber = Math.floor(Math.random() * 6) + 1;faceTimer = 0;
}
// 着地
if (y >= groundY) {
y = groundY;
// 小さく跳ねる
bounceVelocity = -5.0;
bounceCount = 0;
state = "bounce";
vx *= 0.45;
rotationSpeed *= 0.55;
}
}
else if (state === "bounce") {
// 上下方向
bounceVelocity += bounceGravity;
y += bounceVelocity;
// 回転も少し続ける
angle += rotationSpeed * 0.5;
// 地面に戻った
if (y >= groundY) {
y = groundY;
bounceCount++;
// もう一度だけ小さく跳ねる
if (bounceCount < 2) {
bounceVelocity = -3.0;
} else {
// バウンド終了
state = "ground";
}
}
// 空中でも少し回転
angle += rotationSpeed * 0.5;
}
// 地面を転がる
else if (state === "ground") {
// 横移動
x += vx;
// 回転
angle += rotationSpeed;
// 横方向の摩擦
vx *= 0.96;
// 回転減速
rotationSpeed *= 0.985;
// 目を変更
faceTimer++;
if (faceTimer >= faceInterval) {
currentNumber = Math.floor(Math.random() * 6) + 1;
faceTimer = 0;
// 目の変化を徐々に遅くする
faceInterval += 0.7;
}
// 最終停止へ
if (
Math.abs(vx) < 0.5 &&rotationSpeed < 0.045
) {
state = "settling";
// 最終結果
currentNumber = finalNumber;
// 現在角度を整理
settleStartAngle = normalizeAngle(angle);
// 正立に近い角度へ補正
if (
Math.abs(settleStartAngle) >
Math.PI / 2
) {
settleStartAngle = settleStartAngle > 0? Math.PI - settleStartAngle: -Math.PI - settleStartAngle;
}
// 最終角度
settleEndAngle = 0;
// 開始時間
settleStartTime = now;
}
}
// 最後のゆっくり停止
else if (state === "settling") {
const elapsed =
now - settleStartTime;
// ① 1.5秒かけてゆっくり正立
if (elapsed < settleDuration) {
let t = elapsed / settleDuration;
if (t > 1) {
t = 1;
}
const eased = easeOutCubic(t);
angle = settleStartAngle +(settleEndAngle - settleStartAngle) * eased;
currentNumber =finalNumber;
}
// 最後に小さく揺れる
else {
const wobbleElapsed = elapsed - settleDuration;
let t = wobbleElapsed / wobbleDuration;
if (t > 1) {
t = 1;
}
const wobbleAngle = Math.sin(t * Math.PI * 2) *(1.5 * Math.PI / 180);
angle = settleEndAngle + wobbleAngle;
currentNumber = finalNumber;
// 完全停止
if (t >= 1) {
angle = settleEndAngle;
// ここで結果を確定表示
result.textContent = `結果:${finalNumber}`;
state = "done";
rollBtn.disabled = false; //連打禁止解除
}
}
}
// 完全停止
if (state === "done") {
// 最終結果
drawDice(finalNumber,0);
// あたり・はずれ
const target = Number(targetNumber.value);
if (
target >= 1 &&
target <= 6
) {
if (
target === finalNumber
) {
judgement.textContent = "🎯 あたり おめでとう!";
judgement.className = "hit";
// 紙吹雪を開始
if (!confettiStarted) {
createConfetti();
confettiStarted = true;
}
} else {
judgement.textContent = "はずれ";
judgement.className = "miss";
}} else {
judgement.textContent = "";
judgement.className = "";
}
// 紙吹雪を描画
drawConfetti();
// 紙吹雪が残っている間は
// アニメーションを継続
if (confetti.length > 0) {
requestAnimationFrame(animate);
}
return;
}
// 描画
if (state === "settling") {
// 最終結果だけを表示
drawDice(finalNumber,angle);
} else {
// 転がっている途中
drawDice(currentNumber,angle);
}
// 紙吹雪
drawConfetti();
// 次のフレーム
requestAnimationFrame(
animate
);
}
// スタート
requestAnimationFrame(
animate
);
}| CSS |
body {margin: 0;background: #979797;text-align: center;font-family: sans-serif;}
#startTitle {margin: 10px auto 0; color: #dfbdff; font-size: 32px; font-weight: bold; text-align: center;
text-shadow: 0 0 8px rgba(37, 37, 37, 0.911);}
#game-area {position: relative; margin: 6px auto; width: 600px; height: 400px; border-radius: 22px;
overflow: hidden; box-shadow:0 8px 20px rgba(0,0,0,0.18),0 2px 5px rgba(0,0,0,0.18);}
#game-area::before {content: ""; position: absolute;inset: 0;padding: 4px;background:linear-gradient(90deg,#ff6b6b,#ffd93d,
#6bcB77,#4d96ff,#c77dff,#ff6b6b);background-size: 300% 300%;animation:borderMove 5s linear infinite;border-radius: 22px;
mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask-composite: exclude;pointer-events: none;}
@keyframes borderMove {0% {background-position: 0% 50%;}50% {background-position: 100% 50%;}100% {background-position: 0% 50%;}}
#rollBtn {background-color: #ccc;width : 200px;padding: 10px 20px;font-size: 1rem;text-align: center;color: #fff;
text-shadow: 1px 1px 1px #000;border-radius: 10px;background-color: rgb(118, 37, 250);background-image: linear-gradient(
to top left, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2) 30%, rgba(0, 0, 0, 0) );
box-shadow:inset 2px 2px 3px rgba(255, 255, 255, 0.6),inset -2px -2px 3px rgba(0, 0, 0, 0.6);}
#rollBtn:hover {background: #add8fc;transform: translateY(-1px);box-shadow: 0 4px 8px rgba(0, 0, 0, 0.20);}
#rollBtn:active {transform: translateY(2px);box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);}
#diceCanvas {display: block;width: 600px;height: 400px;border-radius: 19px;}
#dice-controls {position: absolute;top: 15px;left: 40px;z-index: 10;text-align: left;}
.control-row {display: flex;align-items: center;gap: 15px;}
.guess-row {display: flex;align-items: center;gap: 8px; color: #8d6417;
text-shadow: 0 0 4px rgba(204, 204, 204, 0.911); margin-top: 10px;font-weight: bold; font-size: 18px;}
#targetNumber {width: 70px;height: 30px;padding: 2px 6px;box-sizing: border-box;font-size: 16px;text-align: center;
border: 2px solid #cf2172;border-radius: 6px;background: #fff;}
#result {background-color: #ccc; width: 60px; padding: 10px 20px; font-size: 1rem; text-align: left; color: #fff;
text-shadow: 1px 1px 1px #000; border-radius: 10px; background-color: rgb(40, 94, 9); background-image: linear-gradient(to top left,
rgba(0, 0, 0, 0.2),rgba(0, 0, 0, 0.2) 30%,rgba(0, 0, 0, 0)); box-shadow:inset 2px 2px 3px rgba(255, 255, 255, 0.6),inset -2px -2px 3px rgba(0, 0, 0, 0.6);}
#judgement {margin-left: 8px;font-size: 18px;font-weight: bold;min-width: 70px;}
#judgement {margin-left: 8px;font-size: 18px;font-weight: bold;min-width: 80px;}
#judgement.hit {color: red;animation: hitBlink 0.5s infinite;}
@keyframes hitBlink {0% {opacity: 1;}50% {opacity: 0.2;}100% {opacity: 1;}}
#judgement.miss {color: blue;animation: none;}
@media screen and (max-width: 650px)
{#game-area {width: calc(100vw - 20px);height: auto;aspect-ratio: 3 / 2;margin: 15px auto;border-radius: 18px;}
#diceCanvas {width: 100%;height: 100%;border-radius: 15px;}
#dice-controls {top: 12px;left: 20px;}
.control-row {gap: 10px;}
.guess-row {gap: 6px;margin-top: 8px;font-size: 14px;}
#rollBtn { padding: 8px 14px;font-size: 15px;}
#result {min-width: 70px;padding: 6px 9px;font-size: 17px;}
#targetNumber {width: 60px;height: 30px;font-size: 16px;}
#judgement {font-size: 17px;min-width: 70px;}}
@media screen and (max-width: 480px) {
body {overflow-x: hidden;}
#game-area {width: calc(100vw - 10px);margin: 8px auto;border-width: 2px;border-radius: 15px;}
#diceCanvas {border-radius: 13px;}
#dice-controls {top: 8px;left: 10px;}
.control-row {gap: 7px;}
#rollBtn {padding: 7px 10px;font-size: 13px;border-radius: 8px;min-height: 38px;}
#result {min-width: 65px;padding: 5px 7px;font-size: 15px;border-radius: 7px;}
.guess-row {gap: 5px;margin-top: 7px;font-size: 12px;white-space: nowrap;}
#targetNumber {width: 50px;height: 32px;padding: 2px;font-size: 16px;border-radius: 6px;}
#judgement {margin-left: 3px;font-size: 15px;min-width: 55px;}}以上です・・
デモサイトは紹介コードとは多少違います。1個・2個のサイコロを振るのボタン、当たりの場合はサイコロがアニメーションする、ボタン類の配置、背景とタイトルなどです。よろしかったら見てください。
