ボールが己の中に2つCanvasを持つように改修してみました。
調子に乗ってボールの数も増やしているので結構重めの処理になってしまってます。
DEMO
JavaScript
(function(win, doc, ns) { "use strict"; function _reverseX(deg) { return (180 - deg) % 360; } function _reverseY(deg) { return (360 - deg) % 360; } function Ball(obj) { var _this = this; _init(); function _init() { _this.x = obj.x; _this.y = obj.y; _this.r = obj.r; _this.v = (obj.v || (Math.random() * 80 | 0) || 1) * 10; _this.deg = obj.deg % 360 || (Math.random() * 360 | 0); _this.color = obj.color || "rgb(" + (Math.random() * 255 | 0) + ", " + (Math.random() * 255 | 0) + ", " + (Math.random() * 255 | 0) + ")"; _this.sCanvas = doc.createElement("canvas"); // 描画用Canvas _this.sCtx = _this.sCanvas.getContext("2d"); _this.mCanvas = doc.createElement("canvas"); // 保存用Canvas _this.mCtx = _this.mCanvas.getContext("2d"); _this.width = null; _this.height = null; } } Ball.prototype = new ns.EventDispatcher(); Ball.prototype.constructor = Ball; Ball.prototype.draw = function(ctx) { var _this = this, sCanvas = _this.sCanvas, sCtx = _this.sCtx, mCanvas = _this.mCanvas, mCtx = _this.mCtx; sCtx.save(); sCtx.clearRect(0, 0, _this.width, _this.height); sCtx.drawImage(mCanvas, 0, 0); // 前回の結果を描画 sCtx.fillStyle = _this.color; sCtx.beginPath(); sCtx.arc(_this.x, _this.y, _this.r, 0, Math.PI * 2, false); sCtx.fill(); sCtx.restore(); mCtx.save(); // 今回の結果を半透明にして保存 mCtx.clearRect(0, 0, _this.width, _this.height); mCtx.globalAlpha = 0.5; mCtx.drawImage(sCanvas, 0, 0); mCtx.restore(); ctx.drawImage(sCanvas, 0, 0); }; Ball.prototype.appendTo = function(canvas) { var _this = this; _this.stage = _this.stage || {}; _this.stage.top = 0; _this.stage.bottom = canvas.height; _this.stage.left = 0; _this.stage.right = canvas.width; _this.mCanvas.width = _this.sCanvas.width = _this.width = canvas.width; _this.mCanvas.height = _this.sCanvas.height = _this.height = canvas.height; }; Ball.prototype.set = function(obj) { var _this = this; _this.x = obj.x || _this.x; _this.y = obj.y || _this.y; _this.r = obj.r || _this.r; _this.v = obj.v || _this.v; _this.deg = obj.deg % 360 || _this.deg; }; Ball.prototype.move = function(delta) { var _this = this, dx = _this.v * Math.cos(_this.deg * (Math.PI / 180)), dy = _this.v * Math.sin(_this.deg * (Math.PI / 180)), x, y; x = _this.x + dx * delta / 1000; y = _this.y + dy * delta / 1000; if (_this._isOutX(x)) { _this.set({deg: _reverseX(_this.deg)}); } else { _this.x = x; } if (_this._isOutY(y)) { _this.set({deg: _reverseY(_this.deg)}); } else { _this.y = y; } }; Ball.prototype._isOutX = function(x) { var _this = this; if (x <= _this.r || x >= _this.stage.right - _this.r) { return true; } else { return false; } }; Ball.prototype._isOutY = function(y) { var _this = this; if (y <= _this.r || y >= _this.stage.bottom - _this.r) { return true; } else { return false; } }; ns.Ball = Ball; })(this, document, App); (function(win, doc, ns) { "use strict"; var ticker = new ns.Ticker(30), max = 10, balls = [], canvas = doc.getElementById("canvas"), ctx = canvas.getContext("2d"), sub = doc.createElement("canvas"), sCtx = sub.getContext("2d"), fps = doc.getElementById("fps"); setup(); function setup() { canvas.width = sub.width = win.innerWidth; canvas.height = sub.height = win.innerHeight; for (var i = 0; i < max; ++i) { balls.push(new ns.Ball({ x : 200, y : 100, r : 20 })); } for (var i = 0, length = balls.length; i < length; ++i) { balls[i].appendTo(canvas); } ticker.addEventListener("tick", function(evt) { fps.innerHTML = evt.measuredFPS; update(evt.delta); draw(); }); ticker.start(); } function update(delta) { for (var i = 0, length = balls.length; i < length; ++i) { balls[i].move(delta); } } function draw() { canvas.width = win.innerWidth; canvas.height = win.innerHeight; ctx.save(); ctx.globalCompositeOperation = "lighter"; for (var i = 0, length = balls.length; i < length; ++i) { balls[i].draw(ctx); } ctx.restore(); } })(this, document, App);
思いのほかおしゃれに仕上がりましたね。
今回は以上です。