Skip to content Skip to sidebar Skip to footer

Canvas Particle Text

I'm new to canvas and particles but I'm having a play around with it here. There's a couple of things I'm trying to achieve here but I'm a bit stuck. Make the text visible for lo

Solution 1:

I think you need a function that moves a point from a position to a desired location, with some randomness added to it. This is what I got:

functiongetRandomMove(pos,end,speed,randomness) {
    var a=Math.random()<randomness
        ? Math.random()*Math.PI/2
        : Math.atan2(end.y-pos.y,end.x-pos.x);
    dist=Math.sqrt(Math.pow(end.x-pos.x,2)+Math.pow(end.y-pos.y,2));
    var spd=Math.min(speed,dist);
    return { x: pos.x+spd*Math.cos(a), y: pos.y+spd*Math.sin(a), dist:dist-spd };
}

Where speed is the speed you want the particle to move and randomness is something between 0 and 0.5 exclusively (more is possible, but then you just get a very slow or even memory breaking execution). You can set randomness to a small value, because it will adapt to the ticks anyway if it is too small.

The problem with this is that if you use it with every particle, you will probably not get all particles where you want them all at the same time. That is why I thought of this function to generate the entire path from one location to another in a specified number of steps:

functionreduceMoves(moves,ticks) {
    var temp=[];
    for (var i=0; i<moves.length-2; i++) {
        var m1=moves[i];
        var m2=moves[i+2];
        temp.push({index:i+1,dist:Math.sqrt(Math.pow(m1.x-m2.x,2)+Math.pow(m1.y-m2.y,2))});
    }
    temp.sort(function(a,b) { return a.dist-b.dist; });
    temp=temp.splice(0,moves.length-ticks);
    temp.sort(function(a,b) { return b.index-a.index });
    temp.forEach(function(t) {
        moves.splice(t.index,1);
    });
}

functionmoveRandomly(pos,end,speed,randomness,ticks) {
    var move=pos;
    var result=[move];
    var dist=100000;
    while (dist>0.1) {
        move=getRandomMove(move,end,speed,randomness);
        result.push(move);
        dist=move.dist;
    }
    move.x=end.x;
    move.y=end.y;
    if (result.length<ticks) {
        returnmoveRandomly(pos,end,speed,randomness+0.1,ticks);
    }
    reduceMoves(result,ticks);
    return result;
}

Use it as

var pos={x:Math.random()*500,y:Math.random()*500};
var end={x:Math.random()*500,y:Math.random()*500};
var moves=moveRandomly(pos,end,1,0.2,100);

I haven't had time to test it and I don't know if it is what you're looking for, but I hope so.

Solution 2:

Just delay the physics for a few seconds so the user can see the text:

// set a time that's 3 seconds in the futurevar nextTime=performance.now()+3000;

// wait until the current time is >= nextTimeif(nextTime && performance.now()<nextTime){return;}else{nextTime=null;}

Example code and a Demo:

var random=Math.random;


/**
 * Init
 */var canvas = document.getElementById('canvas');

window.onresize = function () {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
};

window.onresize();

var ctx = canvas.getContext('2d');

ctx.font = 'bold 50px "Arial"';
ctx.textBaseline = 'center';
ctx.fillStyle = '#fff';

var _particles = [];
var particlesLength = 0;

var currentText = "Create something beautiful";

/**
 * Create one particle
 * @paramx
 * @paramy
 */var createParticle = functioncreateParticle(x, y) {
    _particles.push(newParticle(x, y));
};

/**
 * Check if pixel has alpha
 * @parampixels
 * @parami
 * @returns {boolean}
 */var checkAlpha = functioncheckAlpha(pixels, i) {
    return pixels[i * 4 + 3] > 0;
};

/**
 * Create _particles
 */var createParticles = functioncreateParticles() {
    var textSize = ctx.measureText(currentText);
    ctx.fillText(
        currentText,
        Math.round((canvas.width / 2) - (textSize.width / 2)),
        Math.round(canvas.height / 2)
    );

    var imageData = ctx.getImageData(1, 1, canvas.width, canvas.height);
    var pixels = imageData.data;
    var dataLength = imageData.width * imageData.height;

    //Loop through all pixelsfor (var i = 0; i < dataLength; i++) {
        var currentRow = Math.floor(i / imageData.width);
        var currentColumn = i - Math.floor(i / imageData.height);

        if (currentRow % 2 || currentColumn % 2) {
            continue;
        }

        //If alpha channel is greater than 0if (checkAlpha(pixels, i)) {
            var cy = ~~(i / imageData.width);
            var cx = ~~(i - (cy * imageData.width));

            createParticle(cx, cy);
        }
    }

    particlesLength = _particles.length;
};

/**
 * new Point(x, y)
 * @param x pointer
 * @param y pointer
 * @constructor
 */varPoint = functionPoint(x, y) {
    this.set(x, y);
};

Point.prototype = {
    set: function (x, y) {
        x = x || 0;
        y = y || x || 0;

        /**
         * x start pointer
         * @type {*|number}
         * @private
         */this._sX = x;

        /**
         * y start pointer
         * @type {*|number}
         * @private
         */this._sY = y;

        /**
         * Call reset
         */this.reset();
    },

    /**
     * add one point to another
     * @parampoint
     */add: function (point) {
        this.x += point.x;
        this.y += point.y;
    },

    /**
     * multiply two points
     * @parampoint
     */multiply: function (point) {
        this.x *= point.x;
        this.y *= point.y;
    },

    /**
     * Reset point
     */reset: function () {
        /**
         * x pointer
         * @type {*|number}
         */this.x = this._sX;

        /**
         * y pointer
         * @type {*|number}
         */this.y = this._sY;

        returnthis;
    },
};

varFRICT = newPoint(0.98);
/**
 * Particle constructor
 * @paramx
 * @paramy
 * @constructor
 */varParticle = functionParticle(x, y) {
    this.startPos = newPoint(x, y);

    /**
     * Movement variables
     */this.v = newPoint();
    this.a = newPoint();

    /**
     * First init reset
     */this.reset();
};

Particle.prototype = {
    /**
     * Reset particle
     */reset: function () {
        this.x = this.startPos.x;
        this.y = this.startPos.y;

        this.life = Math.round(random() * 300);
        this.isActive = true;

        /**
         * Movement variables
         */this.v.reset();
        this.a.reset();
    },
    /**
     * Particle tick
     */tick: function () {
        if (!this.isActive) return;

        this.physics();
        this.checkLife();

        this.draw();

        returnthis.isActive;
    },
    /**
     * Calculate life
     */checkLife: function () {
        this.life -= 1;

        this.isActive = !(this.life < 1);
    },

    /**
     * Draw particle
     */draw: function () {
        ctx.fillRect(this.x, this.y, 1, 1);
    },

    /**
     * Calculate particle movement
     */physics: function () {
        if(performance.now()<nextTime){return;}
        this.a.x = (random() - 0.5) * 0.8;
        this.a.y = (random() - 0.5) * 0.8;

        this.v.add(this.a);
        this.v.multiply(FRICT);

        this.x += this.v.x;
        this.y += this.v.y;

        this.x = Math.round(this.x * 10) / 10;
        this.y = Math.round(this.y * 10) / 10;
    }
};

/**
 * Start the party
 */var nextTime=performance.now()+3000;
createParticles();

/**
 * Clear canvas
 */functionclearCanvas() {
    ctx.fillStyle = 'rgba(255,255,255,0.2)';

    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

(functionclearLoop() {
    /**
     * Do clearing
     */clearCanvas();

    /**
     * next loop
     */requestAnimationFrame(clearLoop);
})();

/**
 * Main animation loop
 */
(functionanimLoop(time) {

    ctx.fillStyle = '#2c87c4';
    var isAlive = true;

    /**
     * Loop _particles
     */for (var i = 0; i < particlesLength; i++) {
        /**
         * If particle is active
         */if (_particles[i].tick()) isAlive = true;
    }

   

    /**
     * next loop
     */requestAnimationFrame(animLoop);
})();

functionresetParticles() {
    for (var i = 0; i < particlesLength; i++) {
        _particles[i].reset();
    }
}
body{ background-color: ivory; }
canvas{border:1px solid red; margin:0 auto; }
<canvasid="canvas"></canvas>

I can think of at least 2 other ways to have variable speed animation.

  1. Use Robert Penner's easing functions to slow the beginning of your animation (to show the text longer) and speed up the ending of your animation (to show the dissolve faster). Here's a previous Stackoverflow Q&A with an example using easing in an animation.

  2. requestAnimationFrame automatically sends in a timestamp argument that you can use to vary the execution of your animation. Use the timestamp to calculate elapsed time since the last animation loop. Allow a longer elapsed time at the beginning of your animation sequence. This allows the text to be displayed longer.

Post a Comment for "Canvas Particle Text"