みかづきブログ その3

3ヶ月つづけてみました。

Canvasで塗りつぶしを加算しつつ残像を残す

ボールが己の中に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);

思いのほかおしゃれに仕上がりましたね。
今回は以上です。