Canvasで赤いLEDが残光を曳くアニメーション
夏休みに電子工作の気分を味わいたかったので、canvas と JavaScript で電飾を作ってみました。効果音が必要な方は脳内再生で。
Windows10 上の Edge と Chrome と Firefox で動作を確認しています。
実行画面
実行するとこんな感じです。
プログラムの構成
単体の赤LEDを表すクラス Lamp
現在の輝度を100段階で保持します。
インスタンスを18個生成して、横一列に並べることとします。
点灯パターンを保持するクラス LightingPattern
どのランプをどの輝度で光らせるかを2次元配列で保持します。
動作開始時と動作継続時の点灯パターンを別にするため、配列の添え字をマイナス方向にも伸ばしています。
下記のような感じで6の字型で添え字をカウントアップします。
- マイナス方向の終端からスタート
- プラス側の終端までいったら、0に戻る
- 0とプラス側の終端の間でループ
Animation オブジェクト
アニメーションの開始、ループ、停止の制御です。
LightingPattern クラスから一定時間ごとに次の点灯パターンを取り出して再描画します。
デバッグ用にコマ送りの機能があります。
#ソース
UTF-8 でローカルに保存してください。
red_led_scanner.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>RED LED SCANNER</title>
<script>
'use strict';
let $ = e => document.getElementById(e);
class Lamp {
constructor() {
this.width = 15;
this.height = 25;
this.x = 0;
this.y = 0;
this.brightness = 100; //0 <= brightness <= 100
}
color() {
const alpha = ((this.brightness == 0) ? 0 : 1);
return {r:Math.round(255 * this.brightness / 100), g:0, b:0, a:alpha};
//return {r:Math.round(255 * this.brightness / 100), g:Math.round(255 * this.brightness / 128), b:0, a:alpha};
}
drawon(context) {
const mycolor = this.color();
context.fillStyle = "rgba(" + mycolor.r + "," + mycolor.g + "," + mycolor.b + "," + mycolor.a + ")";
context.fillRect(this.x, this.y, this.width, this.height);
}
}
const LEDArray = {
vLamp: [],
length: 18,
init() {
const gap = 0;
let x = 68;
for (let i = 0; i < this.length; i++) {
this.vLamp[i] = new Lamp();
Object.assign(this.vLamp[i], {x:x, y:35, brightness:0});
x += (this.vLamp[i].width + gap);
}
}
}
class LightingPattern {
constructor() {
this.pattern = [];
this.patternLength = 1;
this.counter = 0;
this.frequency = 50; //msec
this.version = 2000;
}
init(version) {
this.version = version;
(this.version == 2000) ? this.initPattern_2000() : this.initPattern_3000();
}
pattern_off() {
return [ 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0];
}
initPattern_2000() {
this.pattern = [];
let i = -25;
this.pattern[i++] = [ 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0];
this.pattern[i++] = [ 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0];
this.pattern[i++] = [ 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0];
this.pattern[i++] = [ 0, 18, 18, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0];
this.pattern[i++] = [ 0, 12, 12, 18, 18, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 78, 78, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 84, 84, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 84, 84, 90, 90, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 84, 84, 96, 96, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 42, 42, 54, 54, 72, 72, 84, 84, 96, 96,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 30, 30, 42, 42, 54, 54, 72, 72, 96, 96,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 24, 24, 30, 30, 42, 42, 84, 84,100,100,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 24, 24, 24, 24, 30, 30, 76, 76, 96, 96,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 24, 24, 24, 24, 30, 30, 54, 54,100,100, 93, 93, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 24, 24, 30, 30, 30, 30,100,100, 93, 93, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 18, 18, 0];
this.pattern[i++] = [ 0, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 24, 24,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0,100,100, 76, 76, 66, 66, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0,100,100, 93, 93, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0,100,100, 93, 93, 39, 39, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 93, 93,100,100, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 93, 93,100,100, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 76, 76,100,100, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 76, 76,100,100, 48, 48, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 18, 18, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 24, 24, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48, 66, 66, 76, 76,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48, 93, 93,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 39, 39, 93, 93,100,100, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48,100,100, 93, 93, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48,100,100, 93, 93, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48,100,100, 93, 93, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48,100,100, 76, 76, 0];
this.pattern[i++] = [ 0, 18, 18, 18, 18, 18, 18, 18, 18, 24, 24, 48, 48,100,100, 76, 76, 0];
this.patternLength = i;
this.counter = -23;
this.frequency = 52;
}
initPattern_3000() {
this.pattern = [];
this.pattern[0] = [ 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6];
this.pattern[1] = [ 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6];
this.pattern[2] = [ 42, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 42];
this.pattern[3] = [ 66, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 66];
this.pattern[4] = [ 93, 18, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 18, 93];
this.pattern[5] = [100, 93, 18, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 18, 93,100];
this.pattern[6] = [ 96,100, 93, 18, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 18, 93,100, 96];
this.pattern[7] = [ 93, 96,100, 93, 18, 6, 6, 6, 0, 0, 6, 6, 6, 18, 93,100, 96, 93];
this.pattern[8] = [ 93, 93, 96,100, 93, 18, 6, 6, 0, 0, 6, 6, 18, 93,100, 96, 93, 93];
this.pattern[9] = [ 93, 93, 93, 96,100, 93, 18, 6, 0, 0, 6, 18, 93,100, 96, 93, 93, 93];
this.pattern[10] = [ 93, 93, 93, 93, 96,100, 93, 18, 0, 0, 18, 93,100, 96, 93, 93, 93, 93];
this.pattern[11] = [ 96, 96, 96, 96, 96, 96,100, 96, 0, 0, 96,100, 96, 96, 96, 96, 96, 96];
this.pattern[12] = [100,100,100,100,100,100,100,100, 0, 0,100,100,100,100,100,100,100,100];
this.pattern[13] = [ 93, 96,100,100,100,100,100,100, 0, 0,100,100,100,100,100,100, 96, 93];
this.pattern[14] = [ 93, 93, 96,100,100,100,100,100, 0, 0,100,100,100,100,100, 96, 93, 93];
this.pattern[15] = [ 78, 93, 93, 96,100,100,100,100, 0, 0,100,100,100,100, 96, 93, 93, 78];
this.pattern[16] = [ 66, 78, 93, 93, 96,100,100,100, 0, 0,100,100,100, 96, 93, 93, 78, 66];
this.pattern[17] = [ 54, 66, 78, 93, 93, 96,100,100, 0, 0,100,100, 96, 93, 93, 78, 66, 54];
this.pattern[18] = [ 42, 54, 66, 78, 93, 93, 96,100, 0, 0,100, 96, 93, 93, 78, 66, 54, 42];
this.pattern[19] = [ 30, 42, 54, 66, 78, 93, 96,100, 0, 0,100, 96, 93, 78, 66, 54, 42, 30];
this.pattern[20] = [ 18, 30, 42, 54, 66, 78, 93, 96, 0, 0, 96, 93, 78, 66, 54, 42, 30, 18];
this.pattern[21] = [ 6, 18, 30, 42, 54, 66, 78, 93, 0, 0, 93, 78, 66, 54, 42, 30, 18, 6];
this.pattern[22] = [ 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6];
this.pattern[23] = [ 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6];
this.pattern[24] = [ 6, 6, 6, 6, 6, 6, 42, 18, 0, 0, 18, 42, 6, 6, 6, 6, 6, 6];
this.pattern[25] = [ 6, 6, 6, 6, 6, 6, 66, 42, 0, 0, 42, 66, 6, 6, 6, 6, 6, 6];
this.pattern[26] = [ 6, 6, 6, 6, 6, 18, 93, 78, 0, 0, 78, 93, 18, 6, 6, 6, 6, 6];
this.pattern[27] = [ 6, 6, 6, 6, 18, 93,100, 93, 0, 0, 93,100, 93, 18, 6, 6, 6, 6];
this.pattern[28] = [ 6, 6, 6, 18, 93,100, 96, 93, 0, 0, 93, 96,100, 93, 18, 6, 6, 6];
this.pattern[29] = [ 6, 6, 18, 93,100, 96, 93, 93, 0, 0, 93, 93, 96,100, 93, 18, 6, 6];
this.pattern[30] = [ 6, 18, 93,100, 96, 93, 93, 93, 0, 0, 93, 93, 93, 96,100, 93, 18, 6];
this.pattern[31] = [ 18, 93,100, 96, 93, 93, 93, 93, 0, 0, 93, 93, 93, 93, 96,100, 93, 18];
this.pattern[32] = [ 93,100, 96, 93, 93, 93, 93, 93, 0, 0, 93, 93, 93, 93, 93, 96,100, 93];
this.pattern[33] = [100, 96, 96, 96, 96, 96, 96, 96, 0, 0, 96, 96, 96, 96, 96, 96, 96,100];
this.pattern[34] = [100,100,100,100,100,100,100,100, 0, 0,100,100,100,100,100,100,100,100];
this.pattern[35] = [100,100,100,100,100, 96, 93, 78, 0, 0, 78, 93, 96,100,100,100,100,100];
this.pattern[36] = [100,100,100,100, 96, 93, 93, 78, 0, 0, 78, 93, 93, 96,100,100,100,100];
this.pattern[36] = [100,100,100, 96, 93, 93, 78, 66, 0, 0, 66, 78, 93, 93, 96,100,100,100];
this.pattern[37] = [100,100, 96, 93, 93, 78, 66, 54, 0, 0, 54, 66, 78, 93, 93, 96,100,100];
this.pattern[38] = [100, 96, 93, 93, 78, 66, 54, 42, 0, 0, 42, 54, 66, 78, 93, 93, 96,100];
this.pattern[39] = [ 96, 93, 93, 78, 66, 54, 42, 30, 0, 0, 30, 42, 54, 66, 78, 93, 93, 96];
this.pattern[40] = [ 96, 93, 78, 66, 54, 42, 30, 18, 0, 0, 18, 30, 42, 54, 66, 78, 93, 96];
this.pattern[41] = [ 93, 78, 66, 54, 42, 30, 18, 6, 0, 0, 6, 18, 30, 42, 54, 66, 78, 93];
this.pattern[42] = [ 78, 66, 54, 42, 30, 18, 6, 6, 0, 0, 6, 6, 18, 30, 42, 54, 66, 78];
this.pattern[43] = [ 54, 42, 30, 18, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 18, 30, 42, 54];
this.pattern[44] = [ 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6];
this.patternLength = this.pattern.length;
this.counter = 0;
this.frequency = 60;
}
countUP() {
this.counter = (this.counter < 0) ? (this.counter + 1) : (this.counter + 1) % this.patternLength;
return this.counter;
}
currentPattern() {
const n = this.countUP();
return this.pattern[n];
}
}
const Animation = {
callbackId: 0,
context: 0,
AnimationPattern: new LightingPattern(),
lastTime: 0,
loop(timestamp) {
const nowTime = performance.now(), time = nowTime - this.lastTime;
if (time > this.AnimationPattern.frequency) {
this.lastTime = nowTime;
this.redraw();
}
this.callbackId = window.requestAnimationFrame((ts) => this.loop(ts));
},
start() {
if (this.callbackId) { this.stop(); }
this.AnimationPattern.init(($("form1").rdoVersion.value == 2000) ? 2000 : 3000);
const canvas = $("scanner");
this.context.fillStyle = "rgba(17,17,17,1)";
this.context.fillRect(0, 0, canvas.clientWidth, canvas.clientHeight);
this.lastTime = performance.now();
window.requestAnimationFrame((ts) => this.loop(ts));
},
stop() {
cancelAnimationFrame(this.callbackId);
this.callbackId = 0;
},
step() {
const p = this.AnimationPattern.currentPattern();
for (let i = 0; i < LEDArray.length; i++) {
Object.assign(LEDArray.vLamp[i], {brightness:p[i]});
LEDArray.vLamp[i].drawon(this.context);
}
},
redraw() {
const p = (this.callbackId) ? this.AnimationPattern.currentPattern()
: this.AnimationPattern.pattern_off();
for (let i = 0; i < LEDArray.length; i++) {
Object.assign(LEDArray.vLamp[i], {brightness:p[i]});
LEDArray.vLamp[i].drawon(this.context);
}
}
}
window.onload = function() {
$("btnStart").onclick = () => {
$("btnStart").disabled = true;
$("btnStep").disabled = true;
Animation.start();
}
$("btnStop").onclick = () => {
$("btnStart").disabled = false;
$("btnStep").disabled = false;
Animation.stop();
}
$("btnStep").onclick = () => {
Animation.step();
}
LEDArray.init();
const canvas = $("scanner");
if (!canvas.getContext) { return; }
Animation.context = canvas.getContext("2d");
Animation.redraw();
}
</script>
</head>
<body>
<form id="form1" name="form1">
赤いLEDが残光を曳くアニメーション
<input type="button" id="btnStart" value="start" />
<input type="button" id="btnStop" value="stop" />
<input type="button" id="btnStep" value="step" disabled style="display:inline-block;" /><br />
点灯パターン
<input type="radio" name="rdoVersion" value="2000" checked />往復
<input type="radio" name="rdoVersion" value="3000" />両側から
<br />
<canvas id="scanner" width="410" height="95" style="background-color:#111111;">
canvas
</canvas>
</form>
</body>
</html>