Code Walkthrough

The one you are looking for is you.

This pen was inspired by an image that popped up in my tumblr feed (always a great source of inspiration). I felt that it lended itself to a pen quite well, with the added animation and all.

Lets walk through the code!

The initial setup is fairly stock standard stuff for canvas demo's... getting the 2d context of the canvas, and defining a bunch of variables used to center the text since you don't get these completely for free when rendering text on canvas, compared to when rendering it in the dom.

  var canvas = document.querySelector('.the-canvas');
var context = canvas.getContext('2d');
var ratio = window.devicePixelRatio || 1;

var totalLineHeight = 680;
var totalLines = 4;
var totalDiff = totalLineHeight / totalLines;
var fontHeight = 60 * ratio - 50; // Small centering

var smallestWidth = 280; // width of smallest line;
var offsetX = 12;
var offsetY = 6;
var iterations;
var verticalAlign, line1Diff, line2Diff, line3Diff, line4Diff, iterations, iteration, animationFrame;

var startRGB = [255, 255, 255];
var endRGB   = [220, 165, 163];
var fullColorSet = [];

The init function does more of this setting up, preparing the colors, doing final context font setup, and finally triggering the draw function.

The logic to calculate the total times the text will be repeated, is based on the smallest line of text ("for you") in the demo, and how long it will take to have that text reach the edge of the screen, moving at the offset we do (offsetX set above)

  function init() {

  // Cancel any already running animations
  cancelAnimationFrame(animationFrame);

  // Set the canvas width and height
  canvas.width = window.innerWidth * ratio;
  canvas.height = window.innerHeight * ratio;

  // Set the canvas font properties
  context.font = '180px Montserrat';
  context.textAlign = 'center';
  context.fillStyle = '#fff';
  context.strokeStyle = "#F0A5A3";
  context.lineWidth = "3";
  context.textBaseline = "middle"; 

  // Centering of the text
  verticalAlign = (window.innerHeight / 2  * ratio) - totalLineHeight / 2;
  line1Diff = totalLineHeight + fontHeight - totalDiff;
  line2Diff = totalLineHeight + fontHeight - totalDiff * 2;
  line3Diff = totalLineHeight + fontHeight - totalDiff * 3;
  line4Diff = totalLineHeight + fontHeight - totalDiff * 4;

  // How many iterations will we go through?
  iterations = Math.floor(((window.innerWidth * ratio / 2) - (smallestWidth * ratio / 2)) / offsetX + 5);
  prepareColorSets(iterations);

  iteration = 0;

  animationFrame = requestAnimationFrame(draw);
}

One point of note is the prepareColorSets function, which is explained further down in the article. This is done to create a list of colors slowly iterated from the white, to the darker pink in the edges of the repeating text. By doing this initially, and saving it, we save having to calculate these values over and over on each redraw.

It also contains the cancelAnimationFrame function. This is because when the window is resized, this sizing logic will need to be re-ran before the animation is triggered again.

The draw function below, goes through each repeating text from the back to the front... this way, the front most rendered text will appear at the top level.

  function draw() {

  context.clearRect(0, 0, canvas.width, canvas.height);

  for( var i = iterations - 1; i > 0; i-- ) {
    context.fillStyle = 'rgb(' + fullColorSet[i][0] + ',' + fullColorSet[i][1] + ',' + fullColorSet[i][2] + ')';
    var x = window.innerWidth / 2 * ratio - i * offsetX;
    var y = verticalAlign + i * offsetY + (Math.sin(i + iteration) * 2);
    drawText( x, y );
  } 

  iteration += 0.1; 
  animationFrame = requestAnimationFrame(draw);
}

The small bit of trig here (Math.sin) is used to make the letters y position vary between iterations. You can see the counter adding .1 each time the rendering is done.

Here, the drawText function does the actual drawing of the text, it uses the line offsets definied initially. We also need to fill, and then stroke the text, so the strokes appear on the top most later.

  // Draw the single lines of text.
function drawText(x, y) {

  context.fillText("THE ONE", x, y + line4Diff);
  context.strokeText("THE ONE", x, y + line4Diff);

  context.fillText("YOU ARE", x, y + line3Diff);
  context.strokeText("YOU ARE", x, y + line3Diff);

  context.fillText("LOOKING FOR", x, y + line2Diff);
  context.strokeText("LOOKING FOR", x, y + line2Diff);

  context.fillText("IS YOU", x, y + line1Diff);
  context.strokeText("IS YOU", x, y + line1Diff);
}

Here is the function that does the calculation of the color gradients, usefully snagged from stack overflow. The colourGradienter function helps get the colors between two RGB values, by returning the numbers between them. For example... From rgb(100, 100, 100) to rgb(0, 0, 0) half way between these colors would be rgb(50, 50, 50).

  // We do this so we don't have to calculate these EVERY loop.
function prepareColorSets(iterations) {
  fullColorSet = [];
  for( var i = 0; i < iterations; i++ ) {
    fullColorSet.push(colourGradientor(1 - i / iterations, startRGB, endRGB));
  }
}

// THNX - http://stackoverflow.com/questions/14482226/how-can-i-get-the-color-halfway-between-two-colors
function colourGradientor(p, rgb_beginning, rgb_end){

  var w = p * 2 - 1;
  var w1 = (w + 1) / 2.0;
  var w2 = 1 - w1;
  var rgb = [parseInt(rgb_beginning[0] * w1 + rgb_end[0] * w2),
             parseInt(rgb_beginning[1] * w1 + rgb_end[1] * w2),
             parseInt(rgb_beginning[2] * w1 + rgb_end[2] * w2)];
  return rgb;
};

And ofcourse, a quick hard coded "calculate everything again if the screen is resized"

  init();
window.onresize = init;