はじめに
HTML5 Canvasでインタラクティブアートを作りました。
仕様
図形の種類
- 一般的な図形
- 円
- 三角形
- 四角形
- 五角形
- 六角形
- 文様
- 七宝文様
- マンダラ
- 麻の葉文様
- 青海波
- 菊菱
- 矢絣
- 市松模様
- その他
- 風車
- 桜
- 星
図形の描画
画面を右クリックすると、カーソル位置から図形が描画されます。
大きさ、色、動く方向はランダムです。
ボタン
図形の種類を選択できます。
クリアで、描画された図形を削除できます。
図形名
クリック時に描画された図形名が、右上に表示されます。
コード
interactiveArt.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>インタラクティブアート</title>
<style>
/* 基本のスタイル設定 */
body {
margin: 0;
overflow: hidden;
background-color: #f0f0f0; /* 背景色を設定 */
}
canvas {
display: block; /* キャンバスをブロック要素として表示 */
}
/* ボタンの配置とスタイル設定 */
.button-container {
position: absolute; /* 絶対位置で配置 */
top: 10px;
left: 10px;
z-index: 10; /* 他の要素よりも前面に表示 */
background: rgba(255, 255, 255, 0.9); /* 半透明の背景 */
padding: 10px;
border-radius: 8px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); /* 影を追加 */
}
.button-container button {
margin: 5px;
padding: 10px 15px;
font-size: 14px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s; /* 背景色と変形のトランジション */
}
.button-container button.active {
background-color: #4CAF50; /* アクティブボタンの色 */
color: white;
transform: scale(1.05); /* 拡大表示 */
}
.button-container button:hover {
background-color: #ddd; /* ボタンにホバーしたときの色 */
}
.button-container button.clear-button {
background-color: #FF6347; /* クリアボタンの色 */
color: white;
}
/* 描画された図形の名前を表示するスタイル */
.shape-name-display {
position: absolute; /* 絶対位置で配置 */
top: 10px;
right: 10px;
z-index: 10;
background: rgba(0, 0, 0, 0.7); /* 半透明の黒背景 */
color: white;
padding: 10px;
border-radius: 8px;
font-size: 16px;
}
</style>
</head>
<body>
<!-- 図形の種類を選択するボタン群 -->
<div class="button-container">
<button onclick="setShapeCategory('general')" id="generalBtn">一般的な図形</button>
<button onclick="setShapeCategory('patterns')" id="patternsBtn">文様</button>
<button onclick="setShapeCategory('other')" id="otherBtn">その他</button>
<button onclick="clearCanvas()" class="clear-button">クリア</button>
</div>
<!-- 描画された図形の名前を表示する領域 -->
<div class="shape-name-display" id="shapeNameDisplay">図形が表示されます</div>
<!-- 描画用のキャンバス -->
<canvas id="artCanvas"></canvas>
<script>
// キャンバスとそのコンテキストの設定
const canvas = document.getElementById('artCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const shapeNameDisplay = document.getElementById('shapeNameDisplay');
let shapes = []; // 生成された図形を格納する配列
let currentCategory = 'general'; // 現在の描画カテゴリ
// 図形名の対応表
const shapeNames = {
Circle: '円',
Triangle: '三角形',
Square: '四角形',
Pentagon: '五角形',
Hexagon: '六角形',
Shippo: '七宝文様',
Mandala: 'マンダラ',
Asanoha: '麻の葉文様',
Seigaiha: '青海波',
Kikubishi: '菊菱',
Yagasuri: '矢絣',
Ichimatsu: '市松模様',
Windmill: '風車',
Star: '星',
Sakura: '桜'
};
// カテゴリボタンの状態を更新する関数
function updateActiveButton() {
// すべてのボタンから 'active' クラスを削除し、現在のカテゴリに対応するボタンに 'active' クラスを追加
document.getElementById('generalBtn').classList.remove('active');
document.getElementById('patternsBtn').classList.remove('active');
document.getElementById('otherBtn').classList.remove('active');
document.getElementById(`${currentCategory}Btn`).classList.add('active');
}
// カテゴリを設定する関数
function setShapeCategory(category) {
currentCategory = category; // 現在のカテゴリを変更
updateActiveButton(); // ボタンの状態を更新
}
// キャンバスをクリアする関数
function clearCanvas() {
shapes = []; // 図形配列をクリア
ctx.clearRect(0, 0, canvas.width, canvas.height); // キャンバスをクリア
shapeNameDisplay.textContent = '図形がクリアされました'; // 図形名をリセット
}
// ランダムなカラーを生成する関数
const getRandomColor = () => '#' + Math.floor(Math.random() * 16777215).toString(16);
// 図形の基底クラス
class Shape {
constructor(x, y, size) {
this.x = x; // 図形のX座標
this.y = y; // 図形のY座標
this.size = size; // 図形のサイズ
this.color = getRandomColor(); // ランダムな色を生成
this.dx = (Math.random() - 0.5) * 8; // X方向の速度
this.dy = (Math.random() - 0.5) * 8; // Y方向の速度
this.rotation = 0; // 図形の回転角度
this.rotationSpeed = (Math.random() - 0.5) * 0.1; // 回転速度
}
// 図形の位置と回転を更新する関数
update() {
this.x += this.dx; // X座標の更新
this.y += this.dy; // Y座標の更新
this.rotation += this.rotationSpeed; // 回転の更新
// キャンバスの境界に達した場合、方向を反転
if (this.x + this.size > canvas.width || this.x - this.size < 0) this.dx = -this.dx;
if (this.y + this.size > canvas.height || this.y - this.size < 0) this.dy = -this.dy;
this.draw(); // 図形の描画
}
// 描画メソッド(サブクラスでオーバーライドされる)
draw() {}
}
// 各図形クラスの定義
// 円を描画するクラス
class Circle extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
// 三角形を描画するクラス
class Triangle extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.moveTo(0, -this.size / 2);
ctx.lineTo(-this.size / 2, this.size / 2);
ctx.lineTo(this.size / 2, this.size / 2);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
// 四角形を描画するクラス
class Square extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.size / 2, -this.size / 2, this.size, this.size);
ctx.restore();
}
}
// 五角形を描画するクラス
class Pentagon extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const angle = (Math.PI * 2 / 5) * i;
ctx.lineTo(this.size * Math.cos(angle), this.size * Math.sin(angle));
}
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
// 六角形を描画するクラス
class Hexagon extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i;
ctx.lineTo(this.size * Math.cos(angle), this.size * Math.sin(angle));
}
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
// 七宝文様を描画するクラス
class Shippo extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const radius = this.size / 2;
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.stroke();
ctx.rotate(Math.PI / 2);
}
ctx.restore();
}
}
// マンダラを描画するクラス
class Mandala extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const layers = 5;
const elementsPerLayer = 8;
for (let layer = 1; layer <= layers; layer++) {
const radius = (this.size / layers) * layer;
for (let i = 0; i < elementsPerLayer; i++) {
const angle = (i * Math.PI * 2) / elementsPerLayer;
ctx.save();
ctx.rotate(angle);
// 装飾的な要素の描画
ctx.beginPath();
ctx.moveTo(radius * 0.8, 0);
ctx.lineTo(radius, radius * 0.1);
ctx.lineTo(radius, -radius * 0.1);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
// 円の描画
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.strokeStyle = this.color;
ctx.lineWidth = 1;
ctx.stroke();
}
ctx.restore();
}
}
// 麻の葉文様を描画するクラス
class Asanoha extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const unit = this.size / 6;
for (let i = 0; i < 6; i++) {
ctx.save();
ctx.rotate(i * Math.PI / 3);
// 菱形の描画
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(unit * 2, -unit);
ctx.lineTo(unit * 4, 0);
ctx.lineTo(unit * 2, unit);
ctx.closePath();
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
ctx.restore();
}
}
// 青海波文様を描画するクラス
class Seigaiha extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const radius = this.size / 4;
const rows = 3;
const cols = 3;
const offset = radius * 2;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * offset - (row % 2 ? offset / 2 : 0);
const y = row * offset * 0.866; // √3/2 for hexagonal packing
for (let i = 3; i > 0; i--) {
ctx.beginPath();
ctx.arc(x, y, radius * (i / 3), 0, Math.PI);
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.stroke();
}
}
}
ctx.restore();
}
}
// 菊菱文様を描画するクラス
class Kikubishi extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const petalCount = 16;
const innerRadius = this.size * 0.3;
const outerRadius = this.size * 0.5;
ctx.beginPath();
for (let i = 0; i < petalCount; i++) {
const angle = (i * Math.PI * 2) / petalCount;
const nextAngle = ((i + 1) * Math.PI * 2) / petalCount;
const midAngle = (angle + nextAngle) / 2;
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(
Math.cos(midAngle) * outerRadius * 1.2,
Math.sin(midAngle) * outerRadius * 1.2,
Math.cos(nextAngle) * outerRadius,
Math.sin(nextAngle) * outerRadius
);
}
ctx.fillStyle = this.color;
ctx.fill();
ctx.beginPath();
for (let i = 0; i < 4; i++) {
const angle = (i * Math.PI * 2) / 4;
if (i === 0) {
ctx.moveTo(Math.cos(angle) * innerRadius, Math.sin(angle) * innerRadius);
} else {
ctx.lineTo(Math.cos(angle) * innerRadius, Math.sin(angle) * innerRadius);
}
}
ctx.closePath();
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
}
// 矢絣文様を描画するクラス
class Yagasuri extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const unitSize = this.size / 4;
const rows = 3;
const cols = 3;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * unitSize * 2 - this.size;
const y = row * unitSize * 2 - this.size;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + unitSize, y - unitSize);
ctx.lineTo(x + unitSize * 2, y);
ctx.lineTo(x + unitSize, y + unitSize);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
}
}
ctx.restore();
}
}
// 市松模様を描画するクラス
class Ichimatsu extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const squareSize = this.size / 4;
const rows = 4;
const cols = 4;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
if ((row + col) % 2 === 0) {
const x = col * squareSize - this.size / 2;
const y = row * squareSize - this.size / 2;
ctx.fillStyle = this.color;
ctx.fillRect(x, y, squareSize, squareSize);
}
}
}
ctx.restore();
}
}
// 風車を描画するクラス
class Windmill extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
for (let i = 0; i < 4; i++) {
ctx.moveTo(0, 0);
ctx.lineTo(this.size, this.size * 0.3);
ctx.lineTo(this.size, -this.size * 0.3);
ctx.closePath();
ctx.rotate(Math.PI / 2);
}
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
// 星型を描画するクラス
class Star extends Shape {
draw() {
const spikes = 5, outerRadius = this.size, innerRadius = this.size / 2;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.beginPath();
for (let i = 0; i < spikes; i++) {
ctx.lineTo(Math.cos((i * 2 * Math.PI) / spikes) * outerRadius, Math.sin((i * 2 * Math.PI) / spikes) * outerRadius);
ctx.lineTo(Math.cos(((i * 2 + 1) * Math.PI) / spikes) * innerRadius, Math.sin(((i * 2 + 1) * Math.PI) / spikes) * innerRadius);
}
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
}
}
// 桜の花を描画するクラス
class Sakura extends Shape {
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
const petalCount = 5;
const angle = (Math.PI * 2) / petalCount;
ctx.fillStyle = this.color;
for (let i = 0; i < petalCount; i++) {
ctx.save();
ctx.rotate(i * angle);
// 花びらの描画
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(
this.size * 0.3, -this.size * 0.2,
this.size * 0.5, 0
);
ctx.quadraticCurveTo(
this.size * 0.3, this.size * 0.2,
0, 0
);
ctx.fill();
ctx.restore();
}
// 中心の円
ctx.beginPath();
ctx.arc(0, 0, this.size * 0.1, 0, Math.PI * 2);
ctx.fillStyle = '#FFE3E3';
ctx.fill();
ctx.restore();
}
}
// 図形のカテゴリごとの配列
const shapeCategories = {
general: [Circle, Triangle, Square, Pentagon, Hexagon], // 一般的な図形のカテゴリ
patterns: [Shippo, Mandala, Asanoha, Seigaiha, Kikubishi, Yagasuri, Ichimatsu], // 文様のカテゴリ
other: [Windmill, Star, Sakura] // その他の図形のカテゴリ
};
// ランダムな形状を生成する関数
function getRandomShape(x, y) {
const size = Math.random() * 20 + 10; // ランダムなサイズを決定
const shapeClasses = shapeCategories[currentCategory]; // 現在のカテゴリの図形を取得
const ShapeClass = shapeClasses[Math.floor(Math.random() * shapeClasses.length)]; // ランダムに図形クラスを選択
const shapeInstance = new ShapeClass(x, y, size); // 図形のインスタンスを生成
shapeNameDisplay.textContent = `図形: ${shapeNames[ShapeClass.name]}`; // 図形名を表示
return shapeInstance;
}
// クリックイベントで図形を追加するイベントリスナー
canvas.addEventListener('click', (event) => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
shapes.push(getRandomShape(x, y)); // クリックした位置に図形を追加
});
// アニメーションの実行
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // キャンバスをクリア
shapes.forEach(shape => shape.update()); // すべての図形を更新
requestAnimationFrame(animate); // アニメーションフレームのリクエスト
}
animate(); // アニメーションを開始
// ウィンドウリサイズ時にキャンバスサイズを更新
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// 初期のアクティブボタンを設定
updateActiveButton();
</script>
</body>
</html>