Morphing text particles



Here's another short code snippet for you that produces the cool text transition effect above.

It extracts bitmap data from a PNG font and creates particles for non-blank pixels. Then it moves the particles to form another word:

<html>
<body>
<style>body {background-colorblack;}</style>
<script>
let phrases = ['     :-(', '     :-)','Welcome', 'to another', 'JavaScript', 'tutorial:', '> morphing', '| text', '<particles','----------','__________',];
let framesPerPhase = 127;
let size = 10;
let imgData;
let canvas = document.createElement("canvas");
canvas.width = 1200;
let counter = 0;
let phraseCounter = -1;
let phase = 2;
let canvas2 = document.createElement("canvas");
canvas2.width = 800;
canvas2.height = 400;
let particles = [];
let activeParticleCount = 0;
let phraseParticleCount;
let spread = 100;
document.body.appendChild(canvas2);
let context = canvas.getContext("2d");
let context2 = canvas2.getContext("2d");
let img = new Image();
img.src = 'fnt.png';
 
function setTarget(particlexy) {
  particle.targetX = x;
  particle.targetY = y;
  particle.speedX = (particle.targetX - particle.x) / framesPerPhase;
  particle.speedY = (particle.targetY - particle.y) / framesPerPhase;
}
 
function calculatePhrase() {
  phraseParticleCount = 0;
  let phrase = phrases[phraseCounter];
  for (let letter = 0letter < phrase.lengthletter++) {
    let char = phrase.charCodeAt(letter);
    for (let y = 0y < 8y++)
      for (let x = 0x < 8x++) {
        let offset = x * 4;
        let pixel = imgData.data[char * 8 * 4 + offset + y * (canvas.width * 4)];
        if (!pixel) {
          let particle;
          if (!particles[phraseParticleCount]) {
            particle = {
              targetXx * size + letter * 8 * size,
              targetY100 + y * size,
              color0,
              brightnessIncrease2
            };
            particle.x = particle.targetX - spread / 2 + Math.floor(Math.random() * spread);
            particle.y = particle.targetY - spread / 2 + Math.floor(Math.random() * spread);
            particles.push(particle);
          } else {
            particle = particles[phraseParticleCount];
            particle.brightnessIncrease = 2;
          }
          setTarget(particlex * size + letter * 8 * size100 + y * size);
          phraseParticleCount++;
        }
      }
  }
  if (phraseParticleCount >= activeParticleCount) {
    activeParticleCount = phraseParticleCount;
  } else {
    for (let i = phraseParticleCounti < activeParticleCounti++) {
      let particle = particles[i];
      particle.brightnessIncrease = -2;
      setTarget(particleparticle.xparticle.y);
    }
  }
}
 
function animate() {
  if(phase == 1) {
    context2.clearRect(00, canvas2.width, canvas2.height);
    for (let i = 0i < activeParticleCounti++) {
      let particle = particles[i];
      particle.x = particle.x + particle.speedX;
      particle.y = particle.y + particle.speedY;
      let color = particle.color;
      if (color > 2) {
        context2.fillStyle = 'rgb(' + color + ',' + color + ',' + color + ')';
        context2.fillRect(particle.xparticle.ysizesize);
      }
      color = color + particle.brightnessIncrease;
      if (color > 254)
        color = 254;
      if (color < 0)
        color = 0;
      particle.color = color;
    }
  }
  if (counter % framesPerPhase == 0) {
    phase++;
    if (phase == 3) {
      phase = 1;
      phraseCounter++;
      if (phraseCounter == phrases.length)
        phraseCounter = 0;
      calculatePhrase();
    }
  }
  counter++;
  window.requestAnimationFrame(animate);
}
 
img.onload = function() {
  context.drawImage(img00);
  imgData = context.getImageData(00canvas.widthcanvas.height);
  animate();
};
 
</script>
</body>
</html>


Details:

[1-4] HTML setup
Variables:
[5] phrases to display
[6] number of frames per one phase of animation
[7] particle size
[8] an array with the bitmap of the source font set
[9-10] canvas with the source font (will not be visible)
[11] animation frame counter
[12] current phrase
[13] current animation phase (1=animate, 2=wait)
[14-16] the target canvas that will show the animation
[17] the array of particles that will do all the work
[18] the number of particles that are currently active
[19] the number of particles needed to display a given phrase
[20] the distance between the randomized particle location and the target location (only for the first phrase)
[21-25] HTML setup for the two canvases and the source image (taken from here)

[27-32] Set target coordinates for a given particle, calculate its speed

[34-73] Convert a phrase to particles:
[35] reset the particle counter
[36] get the phrase from the array
[37-38] Get the ASCII code of each letter
[39-40] For each pixel in the 8x8 grid:
[41] get the offset of the red component (since it's a black and white image, all RGB components are equal, so I just picked the first one)
[42] get that byte from the imgData array
[43-61] if it's not empty:
[45-54] if a new particle needs to be created:
[47-54] randomize its coordinates around the target, set color to black and brightness increase to positive
[56-57] otherwise reuse an existing particle, set the brightness increase to positive
[59-60] set the target coords for the particle and increase the particles count
[64-65] if the number of particles in the new phrase is equal or greater than in the previous one, update the count
[66-72] otherwise set the brightness increase to negative for all redundant particles

[75-109] the animation function:
[77-93] if we're in phase 1, animate the particles:
[80-81] update their coordinates
[84-85] draw the particles.
[87] update their brightness (dimming if negative). The Red, Green and Blue components or the color are equal, resulting in shades of gray
[88-91] make sure that the color is in the 0-255 that can be handled by RGB
[95-96] if the counter reached a multiple of framesPerPhase, go to the next phase
[97-103] go back to phase 1 if phase 2 finished. Increase the phrase counter (and reset if last phrase)
[109-112] when the image is first loaded:
[110-111] draw the font bitmap on the invisible canvas and copy the pixel data to the imgData array
[112] start the animation!


Check out these programming tutorials:

JavaScript:

Particle constellations (42 lines)

3d stereogram (0 lines)

Optical illusion (18 lines)

Spinning squares - visual effect (25 lines)

Oldschool fire effect (20 lines)

Fireworks (60 lines)

Animated fractal (32 lines)

Minesweeper game (80 lines)

Physics engine for beginners

Physics engine - interactive sandbox

Physics engine - silly contraption

Starfield (21 lines)

Yin Yang with a twist (4 circles and 20 lines)

Tile map editor (70 lines)

Sine scroller (30 lines)

Turtle graphics

Interactive animated sprites

Image transition effect (16 lines)

Wholla lotta quadratic curves (50 lines)

Your first program in JavaScript: you need 5 minutes and a notepad


Fractals in Excel

Python in Blender 3d:

Domino effect (10 lines)


Wrecking ball effect (14 lines)

3d fractal in Blender Python