In my last post, I gave you some example code for an extremely simple web app that draws some polygons on an HTML canvas element. Today I’m giving you the code for another extremely simple web app, but this one introduces a new concept: animation. If you’ve ever done any web development in JavaScript, you’ll notice that the process in Dart is pretty similar. That’s because Dart compiles down to JavaScript (with dart2js), so its API must be built on top of JavaScript’s. However, Dart does a good job of insulating programmers from most of JavaScript’s awkwardness. As you’ll see in this example, even the code for a one-off web example can be elegant in Dart.

Slithery Demo Animation

Of course this is just a choppy screen-capture of the program. To view the actual code in action, click here!

HTML

Like we did with the previous canvas-based web app, let’s start with the HTML. It’s basically the same: a slightly modified Stagehand template.

<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="scaffolded-by" content="https://github.com/google/stagehand">
    <title>slithery</title>
    <link rel="stylesheet" href="styles.css">
    <script defer src="main.dart.js"></script>
</head>

<body>

    <div id="canvas_container" style="width: 100%; text-align:center;">
        <p><a href="https://www.dartographer.com">Dartographer</a> © 2018 Aaron Barrett</p>
        <canvas id="canvas" width="100" height="100"></canvas>
    </div>

</body>
</html>

Once again, it’s nothing but a centered canvas for our app to interact with.

To the Dart code

This example is also simple enough that I could’ve gotten away with just one Dart file, but I decided to separate responsibilities among four different classes to make my code a bit more maintainable. That way if I decide to expand upon this codebase (as I very well may), I’ll have an easier time in the future.

Naturally, the program flow starts in main.dart.

import "dart:html";

import "canvas_tools.dart";
import "simulation.dart";

void main() {
  var canvas = CanvasTools.configureSquareCanvas();
  var context = canvas.getContext("2d") as CanvasRenderingContext2D;

  new Simulation(canvas, context);
}

CanvasTools hasn’t changed at all from the previous example, but this time I’m quickly passing the canvas into a Simulation class. The Simulation allows me to control the animation settings in one place.

Here’s simulation.dart.

import "dart:html";
import "snake.dart";

class Simulation {
  CanvasElement _canvas;
  CanvasRenderingContext2D _context;

  double _canvasSize;

  Snake _snake;

  Simulation(this._canvas, this._context) {
    _canvasSize = _canvas.width.toDouble();
    _snake = new Snake(_canvasSize);
    _startAnimating();
  }

  void _startAnimating() {
    window.requestAnimationFrame(_update);
  }

  void _update(num time) {
    _context.clearRect(0.0, 0.0, _canvasSize, _canvasSize);
    _snake.draw(_context, time);
    window.requestAnimationFrame(_update);
  }
}

Once again, JavaScript web programmers will find this construction pretty familiar. That’s because Dart’s web APIs must all be built on top of JavaScript’s. _startAnimating() sets off a never-ending cycle where the _update function clears the canvas, updates and draws the Snake, and schedules the next call to _update. This is about as simple as animations get on the web.

But let’s look at the Snake class to see how I’m doing the actual drawing. Here’s snake.dart.

import "dart:html";
import "dart:math";

import "snake_point.dart";

class Snake {
  static final Random randomGenerator = new Random();

  static const oscillationRate = const Point(0.0023, 0.0015);
  static const thicknessProportion = 0.022;
  static const randomizedTimeRange = 100000.0;
  static const slitherRadius = 0.35;
  static const pointTimeOffsets = [0, 120.0, 230.0, 300.0];

  double _canvasSize;
  double _thickness;

  List<SnakePoint> _points;
  SnakePoint _headPoint;
  SnakePoint _controlPointA;
  SnakePoint _controlPointB;
  SnakePoint _tailPoint;

  Snake(this._canvasSize) {
    _thickness = _canvasSize * thicknessProportion;

    var leadingTimeOffset = randomGenerator.nextDouble() * randomizedTimeRange;

    _points = pointTimeOffsets
        .map((pointOffset) => new SnakePoint(oscillationRate,
            leadingTimeOffset - pointOffset, _canvasSize, slitherRadius))
        .toList(growable: false);

    _headPoint = _points[0];
    _controlPointA = _points[1];
    _controlPointB = _points[2];
    _tailPoint = _points[3];
  }

  void draw(CanvasRenderingContext2D context, double time) {
    _points.forEach((point) => point.updateLocation(time));

    context.setStrokeColorRgb(0, 255, 120);
    context.lineWidth = _thickness;
    context.lineCap = "round";
    context.beginPath();
    context.moveTo(_headPoint.x, _headPoint.y);
    context.bezierCurveTo(_controlPointA.x, _controlPointA.y, _controlPointB.x,
        _controlPointB.y, _tailPoint.x, _tailPoint.y);
    context.stroke();
  }
}

The Snake is nothing more than a standard cubic Bezier curve whose points move in a pattern over time. In the constructor, I’m building up a List of four SnakePoints: one for the curve’s start point, two for its two control points, and one for its end point. The draw method updates the location of each SnakePoint based on the timestamp of the frame being rendered. Then it draws the Bezier through the four points on the canvas.

Finally, let’s look at SnakePoint to see how I calculated the locations of the Bezier points. Here’s snake_point.dart.

import "dart:math";

class SnakePoint {
  final Point<double> oscillationRate;
  final double timeOffset;
  final double canvasSize;
  final double slitherRadius;

  double x;
  double y;

  SnakePoint(this.oscillationRate, this.timeOffset, this.canvasSize,
      this.slitherRadius);

  void updateLocation(double time) {
    var adjustedTime = time + timeOffset;

    x = canvasSize *
        (0.5 + cos(oscillationRate.x * adjustedTime) * slitherRadius);
    y = canvasSize *
        (0.5 + sin(oscillationRate.y * adjustedTime) * slitherRadius);
  }
}

I’m using dart:math’s Point class for the oscillationRate here for simplicity’s sake. For more advanced math or more optimized applications, I’ve found the vector_math Pub package to be an excellent alternative.

Turbo-charged development

As you can see, this kind of web animation is a breeze in Dart. It’s certainly possible to build all the same functionality in pure JavaScript without too much trouble. Still, I’d challenge any traditional web developer to try writing just one simple web app in Dart. Once you’ve worked with Dart’s classes, data structures, and syntactic features in a full-featured Dart development environment, the prospect of turning back to JavaScript is pretty unappealing. Dart’s official language tour is a great place to start.

I hope you’ve enjoyed this simple Dart web programming example. The code is posted on GitHub, and you’re encouraged to share and modify it!