River flow with canvas and requestAnimationFrame

In this example below you will see how to do a River flow with canvas and requestAnimationFrame with some HTML / CSS and Javascript

Just starting to play with canvas. How can it be optimized?

Thumbnail
This awesome code was written by dandv, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright dandv ©
  • HTML
  • CSS
  • JavaScript
    The boats are randomly created, so they may overlap. This isn't a problem when the signup data from <a href="https://idorecall.com">IDR</a> is plugged in.
<canvas></canvas>
<button>Refresh</button>
<input type="checkbox" checked>Animate


/*Downloaded from https://www.codeseek.co/dandv/river-flow-with-canvas-and-requestanimationframe-aOqaMZ */
    body {
  margin: 0;
  width: 100%;
  height: 100vh;
  background: url(https://i.imgur.com/1iQw8zz.jpg) no-repeat center center;
}

canvas {
  width: 100%;
  height: 30vh;
  margin-top: 60vh;
  background-color: #2A3238;
}


/*Downloaded from https://www.codeseek.co/dandv/river-flow-with-canvas-and-requestanimationframe-aOqaMZ */
    // https://codepen.io/dandv/pen/aOqaMZ?editors=101

window.requestAnimationFrame = window.requestAnimationFrame || window.msRequestAnimationFrame;

var canvas, context,
  sizeX, sizeY,
  waves = [],
  flowSpeed = 0.1,

  boats = [],
  numBoats = 5,
  
  doScaleBoats = false,
  doScaleText = false,

  boatScaleMax = 1.05,
  boatScaleIncrement = (boatScaleMax - 1) / 60;  // how much to increase the scale per second

function Wave(x, y, length, velocity) {
  this.x = x;
  this.y = y;
  this.l = length;
  this.velocity = velocity;
  return this;
}

Wave.prototype.draw = function () {
 context.beginPath();
 context.moveTo(this.x, this.y);
 context.lineTo(this.x + this.l, this.y);
 context.strokeStyle = '#4F5E5B';
 context.stroke();
};

function createWaves() {
  // 1, 7, 15
  var numWaves = 150;
  var waveBaseLength = 5;
  var waveLengths = [waveBaseLength, waveBaseLength * 7, waveBaseLength * 15];  // initial length = 1 unit; replicate the proportions in the logo line

  for (var i = 1; i <= numWaves; i++) {
    var x = Math.random() * sizeX,
      y = sizeY / numWaves * i,
      velocity = 5;

    waves.push(new Wave(x, y, waveLengths[Math.floor(Math.random() * waveLengths.length)], velocity));
  }
}

function Boat(x, y, length, text) {
  this.x = x;
  this.y = y;
  this.length = length;
  this.text = text;
  this.color = '#4F5E5B';
  this.scale = 1 + Math.random() * (boatScaleMax - 1);
  this.scaleSign = 1;  // 1 = growing, -1 = shrinking
  return this;
}

Boat.prototype.draw = function () {
  // TODO oscillate with https://www.html5canvastutorials.com/advanced/html5-canvas-oscillation-animation/ instead
  if (doScaleBoats)
    context.setTransform(this.scale, 0, 0, this.scale, (this.x + this.length / 2) * (1 - this.scale), (this.y + 20) * (1 - this.scale));

  var boatPolygon = [[0,0], [this.length,0], [this.length+20,20], [this.length,40], [0,40]];
  // add a shadow to boats so they look more realistic, though in reality there wouldn't be much of a shadow

  context.shadowOffsetX = 2;
  context.shadowOffsetY = 2;
  context.shadowBlur = 5;
  context.shadowColor = 'rgba(0, 0, 0, 0.3)';

  context.beginPath();
  context.moveTo(this.x, this.y);
  context.lineTo(this.x + this.length, this.y);
  context.quadraticCurveTo(this.x + this.length + 15, this.y/* control point */, this.x + this.length + 40, this.y + 20/* bow */);
  context.quadraticCurveTo(this.x + this.length + 15, this.y + 40/* control point */, this.x + this.length, this.y + 40/* back from bow */);
  context.lineTo(this.x, this.y + 40);
  context.fillStyle = this.color;
  context.fill();
  // context.fillPolygon(boatPolygon, this.color, '#000', {x: this.x, y: this.y});

  // add the text, without shadows
  if (!doScaleText) context.setTransform(1, 0, 0, 1, 0, 0);
  context.font = '16pt Calibri';
  context.fillStyle = '#000';
  context.shadowOffsetX = context.shadowOffsetY = context.shadowBlur = 0;
  context.fillText(this.text, this.x + 10, this.y + 26);

  if (doScaleBoats) {
    this.scale += this.scaleSign * boatScaleIncrement;
    if (this.scale > boatScaleMax || this.scale < 1) this.scaleSign = -this.scaleSign;
  }  
};

function createBoats() {
  var minBoatLength = 140,  /* minimum boat length to accommodate the text */
      maxBoatLength = 200;
  for (var b = 0; b < numBoats; b++) {
    var boat = new Boat(
      Math.random() * (sizeX - maxBoatLength),
      15 + (sizeY - 40) / numBoats * b,
      minBoatLength + Math.random() * (maxBoatLength - minBoatLength),
      'Student from ...'
    );
    boats.push(boat);
  }
  // Make the first boat "you"
  boats[0].text = 'You are here';
  boats[0].color = '#A7C8B2';
}

function clearCanvas() {
  context.clearRect(0, 0, sizeX, sizeY);
}


document.addEventListener('DOMContentLoaded', function () {

  canvas = document.querySelector('canvas');
  context = canvas.getContext('2d');

  // animate them
  var runAnimationCheckbox = document.querySelector('input[type="checkbox"]');
  var loop = function loop(timestamp) {
    clearCanvas();

    for (var j = 0; j < waves.length; j++) {
      var wave = waves[j];

      if (wave.x - wave.velocity > 0) {
        // if in the canvas, draw it
        wave.draw();
        wave.x -= wave.velocity * flowSpeed;
      } else {
        // otherwise, recreate the wave off-screen to the right, at a different Y location to keep it interesting
        wave.x = sizeX + 20;
        wave.y = Math.random() * sizeY;
      }
    }

    // draw the boats over the waves
    for (j = 0; j < boats.length; j++) {
      boats[j].draw();
    }

    // keep animating
    if (runAnimationCheckbox.checked)
      window.requestAnimationFrame(loop);
  };

  if (runAnimationCheckbox.checked)
    window.requestAnimationFrame(loop);
  
  runAnimationCheckbox.addEventListener('change', function (event) {
    if (this.checked)
      window.requestAnimationFrame(loop);
  });

  // automatically resize the canvas to fill its container
  function resizeCanvas() {
    sizeX = canvas.width = canvas.scrollWidth;
    sizeY = canvas.height = canvas.scrollHeight;
  }

  resizeCanvas();

  createWaves();
  createBoats();

  window.addEventListener('resize', resizeCanvas, false);
  
  document.querySelector('button').addEventListener('click', function (event) {
    boats = [];
    createBoats();
  })

});

Comments