GeistHaus
log in · sign up

https://varun.ca/rss.xml

rss
94 posts
Polling state
Status active
Last polled May 19, 2026 10:57 UTC
Next poll May 20, 2026 07:39 UTC
Poll interval 86400s
ETag "028ad84be29d596d91215f8d27f1bd18-ssl-df"

Posts

Iridescent crystal with raymarching and signed distance fields
When building a 3D scene using libraries such as Three.js we generally use meshes. You define a geometry attach some material to it to…
Show full content

When building a 3D scene using libraries such as Three.js we generally use meshes. You define a geometry attach some material to it to create a mesh. Then add that mesh to the scene to render it. This is also how 3D modelling software like Blender and Cinema4D work. However, in the demoscene world—where the goal of is to create stuff using extremely small and self-contained computer programs—this approach didn’t work. They’d have to package a 3D library or engine along with the demo code which takes up a lot of memory. So, those folks came up with a pretty innovative approach. They used signed distance fields (SDFs) to define the geometry and then use raymarching to render the scene. The whole thing runs in a single shader program.

If you’ve ever come across demos on Shadertoy or GLSL Sandbox, you’ve seen this approach in action. While the initial goal was a small file size, it also allows you to create some really cool effects and use boolean operations to create complex shapes. In this article, I’ll show you how to create an iridescent crystal using raymarching and SDFs.

ℹ️ This post assumes foundational knowledge of shaders. If you're not familiar with shaders or the GLSL API, check out: Dan Hollick's twitter thread for a brief overview, kishimisu's intro tutorial that breaks down basic GLSL concepts, or Maxime Heckel's tutorial on shaders with React Three Fiber.Ray Marching

Ray Marching is a rendering technique that involves sending rays into a scene and checking for collisions with objects. Here’s how it works:

First, we select a position for the camera. Then, we send rays from the camera to each pixel in the output image. Along each ray, we step bit by bit, checking if there is a collision with an object in the scene. If a collision occurs, we’re done! If not, we continue stepping along the ray up to a maximum number of steps.

from "Ray tracing" on Wikipedia

The other important distinction is that we’re not using vertices & triangles to define the geometry. If you’ve done any kind of 3D work, you’re probably familiar with the idea of defining geometry using vertices. For example, a cube is defined by 8 vertices and 12 triangles. But with raymarching, we use something called “signed distance field” to represent the geometry.

Signed Distance Field (SDF)

While the term SDF may sound daunting, it’s just a function that calculates the shortest distance from any point in space to a shape’s surface. The distance is negative for points within the shape, positive for points outside the shape, and zero for points exactly on the surface of the shape.

For example, a circle can be defined by the following function:

float sdCircle(vec2 point, float radius) {
  return length(point) - radius;
}

You can find functions for various 2D and 3D SDFs on Inigo Quilez’s website. Or use the glsl-sdf-primitives library. I’ll explain how to use these functions later in the article.

Back to raymarching. When stepping along the ray, the obvious option is to take a tiny step at a time and check for collisions. But since SDF provides us with the distance to the surface, we know that we can step by that distance without going through the surface. Doing so both speeds up the process and improves accuracy.

🤔 Raytracing vs Raymarching
Raytracing is a very similar process to raymarching, the key difference is that geometry is typically defined as triangles, spheres, etc. To find the intersection between the view ray and the scene, we conduct a series of geometric intersection tests. For example, does the ray intersect with a triangle and, if so, which part of the triangle.Implementing a raymarched scene

Alright, onto the crystal. Let’s take the technique I shared above and implement it in GLSL. We’ll start with a basic shader scene, add raymarching to it, and then implement lighting and materials.

Basic shader scene

My goto tool for creative coding is canvas-sketch. It offers a utility function that creates a full-screen GLSL shader renderer using regl. You can pass in your shader code and uniforms and it takes care of the rest. Here’s an example of a shader that renders a gradient.

const canvasSketch = require('canvas-sketch');
const createShader = require('canvas-sketch-util/shader');
const glsl = require('glslify');

const settings = {
  dimensions: [1080, 1080],
  context: 'webgl',
  animate: true,
};

const frag = glsl(`
  precision highp float;

  uniform float time;
  varying vec2 vUv;

  void main () {
    vec3 col = 0.5 + 0.5 * cos(time + vUv.xyx + vec3(0,2,4));
    gl_FragColor = vec4(col, 1.0);
  }
`);

const sketch = ({ gl, canvas }) => {
  return createShader({
    gl,
    frag,
    uniforms: {
      resolution: ({ width, height }) => [width, height],
      time: ({ time }) => time,
      playhead: ({ playhead }) => playhead,
    },
  });
};

canvasSketch(sketch, settings);

Couple of things to note here. createShader bootstraps a default vertex shader (see below) that provides a varying vUv. This essentially maps the pixel coordinates to a value between 0 and 1. You can override this by specifying a custom vertex shader. But for most cases, this is sufficient.

vert.glsl
precision highp float;
attribute vec3 position;
varying vec2 vUv;

void main () {
  gl_Position = vec4(position.xyz, 1.0);
  vUv = gl_Position.xy * 0.5 + 0.5;
}

I’m also using a tool called glslify to wrap the shader code. This enables us to import GLSL modules into our shader. We’ll use it to import SDF functions and other raymarching utilities.

The Raymarching AlgorithmlensLength

0

Below is an implementation of the ray marching algorithm. The camera is positioned as the rayOrigin, and pointed towards the rayTarget—the center of the scene.

The rayDirection is a vector that points from the origin towards a a pixel on the screen, while accounting for the camera’s orientation and field of view. It requires a bit of fancy math to figure out this direction. We’ll be using the glsl-camera-ray module to run that calculation.

Ray starts at the camera, goes through the pixel on the screen and moves through the scene

Once we obtain the ray direction, we proceed along it, checking for collisions. If a collision is detected, the distance to the surface is returned. Otherwise, we return -1.0 to signify that no collision was found.

precision highp float;
varying vec2 vUv;
uniform float lensLength;

#pragma glslify: camera = require('glsl-camera-ray')

float sdSphere(vec3 point, float radius) {
  return length(point) - radius;
}

const int steps = 90;
const float maxdist = 20.0;
const float precis = 0.001;

float raymarch(vec3 rayOrigin, vec3 rayDir) {
  float latest = precis * 2.0;
  float dist = 0.0;
  float res = -1.0;

  // March along the ray
  for (int i = 0; i < steps; i++) {
    // Break if we're close enough or too far away
    if (latest < precis || dist > maxdist) break;
    // Get the SDF distance
    float latest = sdSphere(rayOrigin + rayDir * dist, 1.0);
    // Increment by the latest SDF distance
    dist += latest;
  }
  // if we're still within bounds,
  // set the result to the distance
  if (dist < maxdist) {
    res = dist;
  }

  return res;
}

void main() {
  vec3 color = vec3(0.0);

  // Bootstrap a raymarching scene
  vec3 rayOrigin = vec3(3.5, 0., 3.5);
  vec3 rayTarget = vec3(0, 0, 0);
  // map from 0 to 1 to -1. to 1.
  vec2 screenPos = vUv * 2.0 - 1.;
  vec3 rayDirection = camera(rayOrigin, rayTarget, screenPos, lensLength);

  float collision = raymarch(rayOrigin, rayDirection);

  // If the ray collides, draw the surface
  if (collision > -0.5) {
    color = vec3(0.678, 0.106, 0.176);
  }

  gl_FragColor = vec4(color, 1);
}

lensLength here determines the field of view. Try changing it to see how it affects the scene.

Using GLSL modules for raymarchinglensLength

0

Implementing your own raymarching function is cool. It’s especially useful when you want to tweak the inner workings to achieve a specific effect. However, in most cases, you can probably just use an off-the-shelf module.

Below, I’ve updated the sketch to use the glsl-raytrace module. Additionally, I’m using a glsl-sdf-primitives module to generate a torus and glsl-rotate to rotate it.

The mechanics remain largely similar. The key difference is that geometry is now defined within a function called doModel, and raymarch returns a vec2 containing the distance and material index. This is useful if you want to render multiple types of objects in a scene.

precision highp float;
varying vec2 vUv;
uniform float lensLength;
uniform float time;

vec2 doModel(vec3 p);

#pragma glslify: camera = require('glsl-camera-ray')
#pragma glslify: raymarch = require('glsl-raytrace', map = doModel, steps = 90)
#pragma glslify: sdTorus = require('glsl-sdf-primitives/sdTorus')
#pragma glslify: rotate = require('glsl-rotate/rotate')

vec2 doModel(vec3 p) {
  // Spin the shape
  p.xy = rotate(p.xy, time);
  p.yz = rotate(p.yz, time);
  // Calculate SDF distance
  float d = sdTorus(p, vec2(0.75, 0.35));
  return vec2(d, 0.0);
}

void main() {
  vec3 color = vec3(0.0);
  // Bootstrap a raytracing scene
  vec3 rayOrigin = vec3(3.5, 0, 3.5);
  vec3 rayTarget = vec3(0, 0, 0);
  // map from 0 to 1 to -1. to 1.
  vec2 screenPos = vUv * 2.0 - 1.;
  vec3 rayDirection = camera(rayOrigin, rayTarget, screenPos, lensLength);

  vec2 collision = raymarch(rayOrigin, rayDirection);

  // If the ray collides, draw the surface
  if (collision.x > -0.5) {
    color = vec3(0.678, 0.106, 0.176);
  }

  gl_FragColor = vec4(color, 1);
}

Check it out! We’ve got a spinning donut 🍩 But it looks kinda flat. Let’s add some depth to the scene.

Calculating normals

For the classic material and lighting combination, we need to calculate surface normals. That is, a vector that points away from the surface at a given point.

With SDFs, we calculate the normal by taking the gradient of the SDF function (f) at a specific point, denoted as ∇f. I don’t know about you, but the last time I took a gradient was in MEC E 537 - Aerodynamics. And that was a while ago 😅

Luckily for us, we can use the glsl-sdf-normal module to compute normals for us. The module uses the same doModel function that we defined for raymarching. If you’re curious about the underlying math, check out Jamie Wong’s explanation.

#pragma glslify: normal = require('glsl-sdf-normal', map = doModel)

// ...

if (collision.x > -0.5) {
  // Determine the point of collision
  vec3 pos = rayOrigin + rayDirection * collision.x;
  // Calculate the normal
  vec3 nor = normal(pos);
  // Convert the normal to a color
  color = nor * 0.5 + 0.5;
}

// ...
Phong lighting

My personal philosophy is very much:

Fuck around find out

It’s important to understand how things work, but I’m less focused on implementing everything from scratch and more intrigued by applying those concepts to create my own sketches and scenes. That’s why I was super excited to come across stack.gl/packages.

The stackgl ecosystem is full of little GLSL modules that you can glue these together to create all kinds of effects.

Interested in adding lighting to the scene? What type would you prefer? Lambert, Phong, Beckmann, or Specular? Just grab the associated module and plug it into the scene.

I chose glsl-specular-blinn-phong

#pragma glslify: blinnPhongSpec = require('glsl-specular-blinn-phong')

// ...

vec3 lightPos = vec3(1, 1, 1);
vec3 tint = vec3(0.05, 0.0, 0.97); // color of the shape

vec2 collision = raymarch(rayOrigin, rayDirection);

// If the ray collides, draw the surface
if (collision.x > -0.5) {
  // Determine the point of collision
  vec3 pos = rayOrigin + rayDirection * collision.x;
  // Calculate the normal
  vec3 nor = normal(pos);

  // Calculate light intensity
  vec3 eyeDirection = normalize(rayOrigin - pos);
  vec3 lightDirection = normalize(lightPos - pos);
  float power = blinnPhongSpec(lightDirection, eyeDirection, nor, 0.5);
  // light intensity * color of the shape
  color = power * tint;
}
Iridescent material

Stackgl isn’t the only place where you can find useful code. My other favourite option is Shadertoy. I’m not going to lie, most things on shadertoy were too daunting for me. I couldn’t even begin to figure out what the code was doing.

That is, until I discovered that most work on shadertoy uses a combo of raymarching + SDF. This was certainly a lightbulb moment for me. It’s like suddenly this cryptic code was deciphered and I could understand what it said.

I’ve been obsessed with iridescence and have been bookmarking cool shaders. Once I learnt the raymarching technique, that was it. I could revisit these shaders and try to understand how they work.

One such shader was Thomas Hooper’s Crystals. It’s way more complex than our scene but the general structure is the same. There’s a function for generating the geometry, there’s raymarching loop and after checking for collision is the bit where the iridescence effect is applied.

Let’s add that to our scene.

activeLayers

0

vec3 pal( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d ) {
  return a + b*cos( 6.28318*(c*t+d) );
}

vec3 spectrum(float n) {
  return pal( n, vec3(0.5,0.5,0.5),vec3(0.5,0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,0.33,0.67) );
}

const float GAMMA = 2.2;

vec3 gamma(vec3 color, float g) {
  return pow(color, vec3(g));
}

vec3 linearToScreen(vec3 linearRGB) {
  return gamma(linearRGB, 1.0 / GAMMA);
}

// ...

if (collision.x > -0.5) {
  // Determine the point of collision
  vec3 pos = rayOrigin + rayDirection * collision.x;
  vec3 nor = normal(pos);

  vec3 eyeDirection = normalize(rayOrigin - pos);
  vec3 lightDirection = normalize(lightPos - pos);

  // Iridescent lighting
  vec3 reflection = reflect(rayDirection, nor);
  vec3 dome = vec3(0, 1, 0);
  // base layer
  vec3 perturb = sin(pos * 10.);
  color = spectrum(dot(nor + perturb * .05, eyeDirection) * 2.);
  // specular
  float specular = clamp(dot(reflection, lightDirection), 0., 1.);
  specular = pow((sin(specular * 20. - 3.) * .5 + .5) + .1, 32.) * specular;
  specular *= .1;
  specular += pow(clamp(dot(reflection, lightDirection), 0., 1.) + .3, 8.) * .1;
  // shadow
  float shadow = pow(clamp(dot(nor, dome) * .5 + 1.2, 0., 1.), 3.);
  color = color * shadow + specular;

  // gamma correction
  color = linearToScreen(color);
}

There are three layers to the iridescent material: the base layer (the funky gradients), a little bit of shadow and specular (the concentric light bands). Try toggling them on and off with the slider see their effects.

Mix Phong and Iridescence

One last little tweak with the lighting. We can actually blend the phong and iridescence effects. Which enables you to have tinted iridescent objects.

There’s not a whole lot to it. Calculate the colors for the two effects and then blend them with the mix function.

mixBaseAndIridescent

0

if (collision.x > -0.5) {
  // ...
  // Basic blinn phong lighting
  float power = blinnPhongSpec(lightDirection, eyeDirection, nor, 0.5);
  vec3 baseColor = power * tint;

  // Iridescent lighting
  // ...
  color = color * shadow + specular;

  // mix blinn phong lighting and iridescent lighting
  color = mix(baseColor, color, mixBaseAndIridescent);
  // gamma correction
  color = linearToScreen(color);
}
Crystal geometry

We’ve nailed the look, but what about the crystal shape?

You can file this under “stuff I don’t quite understand, but that’s not going to stop me from using it.” The crystal geometry is a Rhombic Triacontahedron, which I discovered in a The Art Of Code tutorial.

constructionStep

0

This shape is created by folding a plane onto itself using some “magic numbers” and along a “magic direction.” We repeat the process a few times until we achieve the desired crystal shape.

Try using the slider to observe how the shape changes with each fold.

float sdCrystal(vec3 p) {
  float c = cos(3.1415/5.), s=sqrt(0.75-c*c); // magic numbers
  vec3 n = vec3(-0.5, -c, s); // magic direction

  // fold the space to add symmetry
  p = abs(p);
  // fold along the n direction
  p -= 2.*min(0., dot(p, n))*n;

  // fold the space again and along the n direction
  p.xy = abs(p.xy);
  p -= 2.*min(0., dot(p, n))*n;

  // repeat the process
  p.xy = abs(p.xy);
  p -= 2.*min(0., dot(p, n))*n;

  // distance to the surface
  float d = p.z - 1.;
  return d;
}
Basic shader scene

My goto tool for creative coding is canvas-sketch. It offers a utility function that creates a full-screen GLSL shader renderer using regl. You can pass in your shader code and uniforms and it takes care of the rest. Here’s an example of a shader that renders a gradient.

const canvasSketch = require('canvas-sketch');
const createShader = require('canvas-sketch-util/shader');
const glsl = require('glslify');

const settings = {
  dimensions: [1080, 1080],
  context: 'webgl',
  animate: true,
};

const frag = glsl(`
  precision highp float;

  uniform float time;
  varying vec2 vUv;

  void main () {
    vec3 col = 0.5 + 0.5 * cos(time + vUv.xyx + vec3(0,2,4));
    gl_FragColor = vec4(col, 1.0);
  }
`);

const sketch = ({ gl, canvas }) => {
  return createShader({
    gl,
    frag,
    uniforms: {
      resolution: ({ width, height }) => [width, height],
      time: ({ time }) => time,
      playhead: ({ playhead }) => playhead,
    },
  });
};

canvasSketch(sketch, settings);

Couple of things to note here. createShader bootstraps a default vertex shader (see below) that provides a varying vUv. This essentially maps the pixel coordinates to a value between 0 and 1. You can override this by specifying a custom vertex shader. But for most cases, this is sufficient.

vert.glsl
precision highp float;
attribute vec3 position;
varying vec2 vUv;

void main () {
  gl_Position = vec4(position.xyz, 1.0);
  vUv = gl_Position.xy * 0.5 + 0.5;
}

I’m also using a tool called glslify to wrap the shader code. This enables us to import GLSL modules into our shader. We’ll use it to import SDF functions and other raymarching utilities.

The Raymarching AlgorithmlensLength

0

Below is an implementation of the ray marching algorithm. The camera is positioned as the rayOrigin, and pointed towards the rayTarget—the center of the scene.

The rayDirection is a vector that points from the origin towards a a pixel on the screen, while accounting for the camera’s orientation and field of view. It requires a bit of fancy math to figure out this direction. We’ll be using the glsl-camera-ray module to run that calculation.

Ray starts at the camera, goes through the pixel on the screen and moves through the scene

Once we obtain the ray direction, we proceed along it, checking for collisions. If a collision is detected, the distance to the surface is returned. Otherwise, we return -1.0 to signify that no collision was found.

precision highp float;
varying vec2 vUv;
uniform float lensLength;

#pragma glslify: camera = require('glsl-camera-ray')

float sdSphere(vec3 point, float radius) {
  return length(point) - radius;
}

const int steps = 90;
const float maxdist = 20.0;
const float precis = 0.001;

float raymarch(vec3 rayOrigin, vec3 rayDir) {
  float latest = precis * 2.0;
  float dist = 0.0;
  float res = -1.0;

  // March along the ray
  for (int i = 0; i < steps; i++) {
    // Break if we're close enough or too far away
    if (latest < precis || dist > maxdist) break;
    // Get the SDF distance
    float latest = sdSphere(rayOrigin + rayDir * dist, 1.0);
    // Increment by the latest SDF distance
    dist += latest;
  }
  // if we're still within bounds,
  // set the result to the distance
  if (dist < maxdist) {
    res = dist;
  }

  return res;
}

void main() {
  vec3 color = vec3(0.0);

  // Bootstrap a raymarching scene
  vec3 rayOrigin = vec3(3.5, 0., 3.5);
  vec3 rayTarget = vec3(0, 0, 0);
  // map from 0 to 1 to -1. to 1.
  vec2 screenPos = vUv * 2.0 - 1.;
  vec3 rayDirection = camera(rayOrigin, rayTarget, screenPos, lensLength);

  float collision = raymarch(rayOrigin, rayDirection);

  // If the ray collides, draw the surface
  if (collision > -0.5) {
    color = vec3(0.678, 0.106, 0.176);
  }

  gl_FragColor = vec4(color, 1);
}

lensLength here determines the field of view. Try changing it to see how it affects the scene.

Using GLSL modules for raymarchinglensLength

0

Implementing your own raymarching function is cool. It’s especially useful when you want to tweak the inner workings to achieve a specific effect. However, in most cases, you can probably just use an off-the-shelf module.

Below, I’ve updated the sketch to use the glsl-raytrace module. Additionally, I’m using a glsl-sdf-primitives module to generate a torus and glsl-rotate to rotate it.

The mechanics remain largely similar. The key difference is that geometry is now defined within a function called doModel, and raymarch returns a vec2 containing the distance and material index. This is useful if you want to render multiple types of objects in a scene.

precision highp float;
varying vec2 vUv;
uniform float lensLength;
uniform float time;

vec2 doModel(vec3 p);

#pragma glslify: camera = require('glsl-camera-ray')
#pragma glslify: raymarch = require('glsl-raytrace', map = doModel, steps = 90)
#pragma glslify: sdTorus = require('glsl-sdf-primitives/sdTorus')
#pragma glslify: rotate = require('glsl-rotate/rotate')

vec2 doModel(vec3 p) {
  // Spin the shape
  p.xy = rotate(p.xy, time);
  p.yz = rotate(p.yz, time);
  // Calculate SDF distance
  float d = sdTorus(p, vec2(0.75, 0.35));
  return vec2(d, 0.0);
}

void main() {
  vec3 color = vec3(0.0);
  // Bootstrap a raytracing scene
  vec3 rayOrigin = vec3(3.5, 0, 3.5);
  vec3 rayTarget = vec3(0, 0, 0);
  // map from 0 to 1 to -1. to 1.
  vec2 screenPos = vUv * 2.0 - 1.;
  vec3 rayDirection = camera(rayOrigin, rayTarget, screenPos, lensLength);

  vec2 collision = raymarch(rayOrigin, rayDirection);

  // If the ray collides, draw the surface
  if (collision.x > -0.5) {
    color = vec3(0.678, 0.106, 0.176);
  }

  gl_FragColor = vec4(color, 1);
}

Check it out! We’ve got a spinning donut 🍩 But it looks kinda flat. Let’s add some depth to the scene.

Calculating normals

For the classic material and lighting combination, we need to calculate surface normals. That is, a vector that points away from the surface at a given point.

With SDFs, we calculate the normal by taking the gradient of the SDF function (f) at a specific point, denoted as ∇f. I don’t know about you, but the last time I took a gradient was in MEC E 537 - Aerodynamics. And that was a while ago 😅

Luckily for us, we can use the glsl-sdf-normal module to compute normals for us. The module uses the same doModel function that we defined for raymarching. If you’re curious about the underlying math, check out Jamie Wong’s explanation.

#pragma glslify: normal = require('glsl-sdf-normal', map = doModel)

// ...

if (collision.x > -0.5) {
  // Determine the point of collision
  vec3 pos = rayOrigin + rayDirection * collision.x;
  // Calculate the normal
  vec3 nor = normal(pos);
  // Convert the normal to a color
  color = nor * 0.5 + 0.5;
}

// ...
Phong lighting

My personal philosophy is very much:

Fuck around find out

It’s important to understand how things work, but I’m less focused on implementing everything from scratch and more intrigued by applying those concepts to create my own sketches and scenes. That’s why I was super excited to come across stack.gl/packages.

The stackgl ecosystem is full of little GLSL modules that you can glue these together to create all kinds of effects.

Interested in adding lighting to the scene? What type would you prefer? Lambert, Phong, Beckmann, or Specular? Just grab the associated module and plug it into the scene.

I chose glsl-specular-blinn-phong

#pragma glslify: blinnPhongSpec = require('glsl-specular-blinn-phong')

// ...

vec3 lightPos = vec3(1, 1, 1);
vec3 tint = vec3(0.05, 0.0, 0.97); // color of the shape

vec2 collision = raymarch(rayOrigin, rayDirection);

// If the ray collides, draw the surface
if (collision.x > -0.5) {
  // Determine the point of collision
  vec3 pos = rayOrigin + rayDirection * collision.x;
  // Calculate the normal
  vec3 nor = normal(pos);

  // Calculate light intensity
  vec3 eyeDirection = normalize(rayOrigin - pos);
  vec3 lightDirection = normalize(lightPos - pos);
  float power = blinnPhongSpec(lightDirection, eyeDirection, nor, 0.5);
  // light intensity * color of the shape
  color = power * tint;
}
Iridescent material

Stackgl isn’t the only place where you can find useful code. My other favourite option is Shadertoy. I’m not going to lie, most things on shadertoy were too daunting for me. I couldn’t even begin to figure out what the code was doing.

That is, until I discovered that most work on shadertoy uses a combo of raymarching + SDF. This was certainly a lightbulb moment for me. It’s like suddenly this cryptic code was deciphered and I could understand what it said.

I’ve been obsessed with iridescence and have been bookmarking cool shaders. Once I learnt the raymarching technique, that was it. I could revisit these shaders and try to understand how they work.

One such shader was Thomas Hooper’s Crystals. It’s way more complex than our scene but the general structure is the same. There’s a function for generating the geometry, there’s raymarching loop and after checking for collision is the bit where the iridescence effect is applied.

Let’s add that to our scene.

activeLayers

0

vec3 pal( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d ) {
  return a + b*cos( 6.28318*(c*t+d) );
}

vec3 spectrum(float n) {
  return pal( n, vec3(0.5,0.5,0.5),vec3(0.5,0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,0.33,0.67) );
}

const float GAMMA = 2.2;

vec3 gamma(vec3 color, float g) {
  return pow(color, vec3(g));
}

vec3 linearToScreen(vec3 linearRGB) {
  return gamma(linearRGB, 1.0 / GAMMA);
}

// ...

if (collision.x > -0.5) {
  // Determine the point of collision
  vec3 pos = rayOrigin + rayDirection * collision.x;
  vec3 nor = normal(pos);

  vec3 eyeDirection = normalize(rayOrigin - pos);
  vec3 lightDirection = normalize(lightPos - pos);

  // Iridescent lighting
  vec3 reflection = reflect(rayDirection, nor);
  vec3 dome = vec3(0, 1, 0);
  // base layer
  vec3 perturb = sin(pos * 10.);
  color = spectrum(dot(nor + perturb * .05, eyeDirection) * 2.);
  // specular
  float specular = clamp(dot(reflection, lightDirection), 0., 1.);
  specular = pow((sin(specular * 20. - 3.) * .5 + .5) + .1, 32.) * specular;
  specular *= .1;
  specular += pow(clamp(dot(reflection, lightDirection), 0., 1.) + .3, 8.) * .1;
  // shadow
  float shadow = pow(clamp(dot(nor, dome) * .5 + 1.2, 0., 1.), 3.);
  color = color * shadow + specular;

  // gamma correction
  color = linearToScreen(color);
}

There are three layers to the iridescent material: the base layer (the funky gradients), a little bit of shadow and specular (the concentric light bands). Try toggling them on and off with the slider see their effects.

Mix Phong and Iridescence

One last little tweak with the lighting. We can actually blend the phong and iridescence effects. Which enables you to have tinted iridescent objects.

There’s not a whole lot to it. Calculate the colors for the two effects and then blend them with the mix function.

mixBaseAndIridescent

0

if (collision.x > -0.5) {
  // ...
  // Basic blinn phong lighting
  float power = blinnPhongSpec(lightDirection, eyeDirection, nor, 0.5);
  vec3 baseColor = power * tint;

  // Iridescent lighting
  // ...
  color = color * shadow + specular;

  // mix blinn phong lighting and iridescent lighting
  color = mix(baseColor, color, mixBaseAndIridescent);
  // gamma correction
  color = linearToScreen(color);
}
Crystal geometry

We’ve nailed the look, but what about the crystal shape?

You can file this under “stuff I don’t quite understand, but that’s not going to stop me from using it.” The crystal geometry is a Rhombic Triacontahedron, which I discovered in a The Art Of Code tutorial.

constructionStep

0

This shape is created by folding a plane onto itself using some “magic numbers” and along a “magic direction.” We repeat the process a few times until we achieve the desired crystal shape.

Try using the slider to observe how the shape changes with each fold.

float sdCrystal(vec3 p) {
  float c = cos(3.1415/5.), s=sqrt(0.75-c*c); // magic numbers
  vec3 n = vec3(-0.5, -c, s); // magic direction

  // fold the space to add symmetry
  p = abs(p);
  // fold along the n direction
  p -= 2.*min(0., dot(p, n))*n;

  // fold the space again and along the n direction
  p.xy = abs(p.xy);
  p -= 2.*min(0., dot(p, n))*n;

  // repeat the process
  p.xy = abs(p.xy);
  p -= 2.*min(0., dot(p, n))*n;

  // distance to the surface
  float d = p.z - 1.;
  return d;
}
And that’s that!

Raymarching with SDF isn’t better than the conventional mesh based approach; it’s just different. However, it offers the ability to create unique and visually striking effects, making it a fantastic tool to have in your creative coding toolbox. Plus, with glslify and the stack.gl ecosystem you can use off-the-shelf modules to get going quickly.

Checkout the full source for the crystal sketch on Github. Want to take it a step further? I’ve expanded this sketch to combine refraction and iridescence to make a see through crystal.

Reference
https://varun.ca/ray-march-sdf/
How we built the Storybook Day 3D animation
Storybook just hit a major milestone: version 7.0! With a re-engineered codebase and bunch of new features, it's faster and more stable than…
Show full content

Storybook just hit a major milestone: version 7.0! With a re-engineered codebase and bunch of new features, it’s faster and more stable than ever before. To celebrate, the team hosted their first ever user conference—Storybook Day. And to make things even more special, we decided to add a visually stunning 3D element to the event landing page.

We used React Three Fiber (R3F) to build an eye-catching 3D illustration, inspired by Storybook’s Tetris blocks branding. In this article, we’ll dive into the nitty-gritty of shipping a 3D scene. Here’s what I’ll cover:

  • 🏗️ Avoid object overlap with sphere packing
  • 🧱 Model Tetris blocks with extrusion
  • 🎥 Enhance visuals with effects like depth of field and shadows
  • 🏎️ Optimize performance by reducing material count
ℹ️ This post assumes foundational knowledge of React Three Fiber. If you're new to 3D or not familiar with the R3F API, check out my intro post for a primer.Our strategy

We built the event site using NextJS and @react-three/fiber, with a little help from @react-three/drei. The animation features floating Tetris blocks surrounding “7.0” extruded text.

Here’s an MVP in code.

Loading...The Details

On to the fun stuff. Notice how the blocks are randomly spread across the scene and sometimes overlap with the text or each other. It would be aesthetically more pleasing if the blocks had no overlap. That’s one example of little adjustments we made to take this scene from MVP to production ready. Let’s dive into these techniques.

Sphere packing to place blocks

The pack-sphere library enabled us to evenly distribute the blocks and prevent any potential overlapping issues. This library employs a brute force approach to arrange spheres of varying radii within a cube.

const spheres = pack({
  maxCount: 40,
  minRadius: 0.125,
  maxRadius: 0.25,
});

We then scaled the spheres to fit our scene space and stretched them horizontally along the x-axis. Lastly, we placed a block at the center of each sphere, scaled to the sphere’s radius.

That gave us a nice spread of blocks, with a satisfying mix of size and placement.

To fix the overlap between the “7.0” text and the blocks, a different approach was needed. Initially, we thought of using pack-sphere to detect collisions between the spheres and the text geometry. However, we ended up opting for a simpler solution: slightly shifting the spheres along the z-axis.

The text essentially parts the sea of blocks.

Here’s the entire process, in code:

// Sphere packing
const spheres = pack({
  maxCount: 40,
  minRadius: 0.125,
  maxRadius: 0.25,
}).map((sphere) => {
  const inFront = sphere.position[2] >= 0;
  return {
    ...sphere,
    position: [
      sphere.position[0],
      sphere.position[1],
      // offset to avoid overlapping with the 7.0 text
      inFront ? sphere.position[2] + 0.6 : sphere.position[2] - 0.6,
    ],
  };
});

const size = 5.5;
// stretch horizontally
const scale = [size * 6, size, size];

const blocks = spheres.map((sphere, index) => ({
  ...sphere,
  id: index,
  // scale position to scene space
  position: sphere.position.map((v, idx) => v * scale[idx]),
  // scale radius to scene space
  size: sphere.radius * size * 1.5,
  color: Random.pick(colors),
  type: Random.pick(blockTypes),
  rotation: new THREE.Quaternion(...Random.quaternion()),
}));
Extrusion to model tetris blocks

You may have noticed that we’ve only used primitive blocks so far. Hey, no hating on primitives, but Storybook branding uses tetris-style blocks, so we had to add those into the mix.

The concept of ExtrudeGeometry in Three.js is quite interesting. You can supply it with a 2D shape using a syntax similar to SVG path or CSS shapes, and it’ll extrude it along the z-axis. This feature is perfect for creating Tetris blocks.

Drei’s Extrude utility offers a relatively straightforward syntax for creating such shapes. Here’s an example of how we generated the “T” block:

import { useMemo } from 'react';
import * as THREE from 'three';
import { Extrude } from '@react-three/drei';

export const EXTRUDE_SETTINGS = {
  steps: 2,
  depth: 0.5625,
  bevelEnabled: false,
};

export const TBlock = ({ type, color, ...props }) => {
  const shape = useMemo(() => {
    const _shape = new THREE.Shape();
    _shape.moveTo(0, 0);
    _shape.lineTo(SIDE, 0);
    _shape.lineTo(SIDE, SIDE * 3);
    _shape.lineTo(0, SIDE * 3);
    _shape.lineTo(0, SIDE * 2);
    _shape.lineTo(-SIDE, SIDE * 2);
    _shape.lineTo(-SIDE, SIDE);
    _shape.lineTo(0, SIDE);
    return _shape;
  }, []);

  return (
    <Extrude args={[shape, EXTRUDE_SETTINGS]} {...props}>
      <meshPhongMaterial color={color} />
    </Extrude>
  );
};

Next, we layered on shadows and depth of field effect to add some oomph.

Shadows

Shadows bring scenes to life by adding depth and realism. You can configure lights and meshes within the scene to cast shadows using the castShadow prop. However, we were looking for a softer look, so once again we reached for Drei, which offers a convenient contact shadows component.

Contact shadows are a “fake shadow” effect. They’re generated by filming the scene from below and rendering the shadow onto a catcher plane. The shadow is accumulated over several frames, making it softer and more realistic.

To use it, add the ContactShadows component to the scene. Then customize the look by adjusting the resolution, opacity, blur, color, and other properties.

Loading...Depth of field effect

At this stage, every object in the scene is rendered with the same sharpness, making the scene appear somewhat flat. Photographers, often use wide apertures and shallow depth of field to create a pleasing blurred aesthetic. We mimicked this effect by applying post-processing effects—using @react-three/postprocessing—to our scene, giving it a more cinematic feel.

The EffectComposer manages and runs post-processing passes. It begins by rendering the scene to a buffer and then applies filters and effects one at a time before rendering the final image onto the screen.

Picking a focus distance

With the DepthOfField effect, you can pinpoint a specific distance (focusDistance) within your scene and make everything else beautifully blurry. But how do you define the focus distance? Is it measured in world units or something else?

import { Canvas } from '@react-three/fiber';
import { EffectComposer, DepthOfField } from '@react-three/postprocessing';

export const Scene = () => (
  <Canvas>
    {/* Rest of Our scene */}
    <EffectComposer multisampling={8}>
      <DepthOfField focusDistance={0.5} bokehScale={7} focalLength={0.2} />
    </EffectComposer>
  </Canvas>
);

The camera’s view is defined by a pyramid-shaped volume called the “view frustum.” Objects within the minimum (near plane) and maximum (far plane) distances from the camera will be rendered.

View Frustum this is a pyramid which has 2 clipping planes. Objects or parts of objects found outside the frustum, won't be rendered
From: Intro to 3D Programming - Perspective Projections

The focusDistance parameter determines the distance from the camera at which objects are considered in focus. Its value is normalized between 0 and 1, where 0 represents the near plane of the camera and 1 represents the far plane.

We set the focus distance to 0.5, which is where the text is located. Objects closer to that threshold will be in focus, while those farther away will be blurred. Try adjusting the focusDistance slider in the sandbox below to see how it affects the scene.

You can also further customize this effect by adjusting the bokehScale and focalLength props.

Loading...Using a material store for performance optimization

Although shadows and depth of field are cool visual effects, they can be quite expensive to render and have a significant impact on performance. Seeking some advice on this topic, I reached out to Paul Henschel aka @0xca0a. Among several perf optimizations, they suggested using a material store to avoid creating a new material instance for each block. Let’s take a closer look at how this works.

The Block component uses the color prop to create a unique material for every instance. For example, every orange coloured block will have its own material instance. Pretty wasteful, right?

export const Block = ({ type, color }: BlockProps) => {
  const Component = BLOCK_TYPES[type].shape
  return (
    <Component args={BLOCK_TYPES[type].args as any} castShadow>
      <meshPhongMaterial color={color} />
    </Component>
  )
}

By using a material store, we could reuse the same material across multiple block instances. This improved performance by reducing the number of materials that need to be created and rendered.

src/components/BlocksScene/store.tsx
import * as THREE from 'three';
THREE.ColorManagement.legacyMode = false;

const colors: string[] = [
  '#FC521F',
  '#CA90FF',
  '#1EA7FD',
  '#FFAE00',
  '#37D5D3',
  '#FC521F',
  '#66BF3C',
  '#0AB94F'
];

interface Materials {
  [color: string]: THREE.MeshPhongMaterial;
}

const materials: Materials = colors.reduce(
  (acc, color) => ({ ...acc, [color]: new THREE.MeshPhongMaterial({ color }) }),
  {}
);

export { colors, materials };

The store generates a material for every possible block color and stores it in a plain object. Now, instead of creating a material for each instance, the block component simply references it from the material store. We went from 40 materials to 8.

export const Block = ({ type, color }: BlockProps) => {
  const Component = BLOCK_TYPES[type].shape;
  return (
    <Component
      args={OTHER_TYPES[type as OtherBlockType].args as any}
      material={materials[color]}
    />
  );
}
Wrap up

3D is now part of the web’s grain, and R3F is a fantastic tool for intertwining HTML and WebGL. The R3F ecosystem is rich, and libraries like drei and postprocessing simplify complex 3D tasks. The Storybook Day 3D scene perfectly showcases the platform’s possibilities. We used sphere packing, extrusion, shadows, depth of field, and a material store to create an memorable event landing page.

In deconstructing this scene, I aimed to shed light on the final 20% of work that elevates a scene from “done” to “ready to ship”. If you have any questions or feedback, feel free to reach out via twitter.

Want to dive in deeper? The code is open-source and each aspect of the scene is broken down in the deployed Storybook.

https://varun.ca/storybook-day/
Three ways to create 3D particle effects
Particles will never not be cool! I've been obsessed with them since the day I was introduced to generative art. They're super versatile…
Show full content
Loading...

Particles will never not be cool! I’ve been obsessed with them since the day I was introduced to generative art. They’re super versatile. You can use them for all kinds of things—flock of birds, snow, fire, fireworks, stars, sparks and so on. I’ve shown you how to build a confetti particle system in the past. This time we’re entering the third dimension.

I’ll break down the demo you see above and share three techniques for creating particle systems:

  1. Using instanced meshes and animating their transforms.
  2. Using dashed lines with an animated offset.
  3. By drawing a line and advancing it step by step.
⚠️ This post assumes foundational knowledge of React Three Fiber. If you're new to 3D or not familiar with the R3F API, check out my intro post for a primer.What are we building?

particles building

The demo above consists of three particle systems. The space dust moving around in the background. Sparks gently floating around the planet. And the spark storm that engulfs the planet.

Let’s tackle them one by one.

Instanced meshes

First up is the Space Dust system. It’s just a bunch of dodecahedrons oscillating around a point. The classical approach is to have one object per particle. But you’re usually dealing with hundreds of thousands of particles, and it’s hard to render them all in a performant way. That’s where instanced meshes come in.

every white dot is a particle

The Space Dust system consists of ten thousand particles. That would mean ten thousand draw calls. With InstancedMesh, we can cut that down to a single draw call. It accomplishes this by reusing the geometry but applying a unique transformation for each instance.

We start by creating a bunch of particles with a random position, speed and timing.

const particles = useMemo(() => {
  const temp = [];
  for (let i = 0; i < count; i++) {
    const time = Random.range(0, 100);
    const factor = Random.range(20, 120);
    const speed = Random.range(0.01, 0.015) / 2;
    const x = Random.range(-50, 50);
    const y = Random.range(-50, 50);
    const z = Random.range(-50, 50);

    temp.push({ time, factor, speed, x, y, z });
  }
  return temp;
}, [count]);

Then render them all using a single instancedMesh.

<>
  <pointLight ref={light} distance={40} intensity={8} color="lightblue" />
  <instancedMesh ref={mesh} args={[null, null, count]}>
    <dodecahedronBufferGeometry args={[0.2, 0]} />
    <meshPhongMaterial color="#050505" />
  </instancedMesh>
</>

You can see that they start off distributed all across the screen.

Loading...

We can then update the particle position, scale, and rotation on every frame to create the oscillating motion. For this, we use a dummy object to calculate the updated transformation matrix and then apply that to the particle instance using the setMatrixAt call.

Now go ahead and toggle on the animations (in the demo above) to see how the movement builds up.

const dummy = useMemo(() => new THREE.Object3D(), []);

useFrame(() => {
  // Run through the list of particles calculate some movement
  particles.forEach((particle, index) => {
    let { factor, speed, x, y, z } = particle;

    // Update the particle time
    const t = (particle.time += speed);

    // Update the particle position based on the time
    dummy.position.set(
      x + Math.cos((t / 10) * factor) + (Math.sin(t * 1) * factor) / 10,
      y + Math.sin((t / 10) * factor) + (Math.cos(t * 2) * factor) / 10,
      z + Math.cos((t / 10) * factor) + (Math.sin(t * 3) * factor) / 10
    );

    // Derive an oscillating value for size and rotation
    const s = Math.cos(t);
    dummy.scale.set(s, s, s);
    dummy.rotation.set(s * 5, s * 5, s * 5);
    dummy.updateMatrix();

    // And apply the matrix to the instanced item
    mesh.current.setMatrixAt(index, dummy.matrix);
  });
  mesh.current.instanceMatrix.needsUpdate = true;
});

The output for sin and cos functions yo-yos between -1 and 1, making them perfect for generating an oscillating motion. You can even combine these trigonometric functions (by adding or multiplying them) to create more interesting repeating patterns. That’s what I’ve done for the position animation.

Dashed line with animated offsetLoading...

Moving on to the floating sparks. The second technique is a bit of a sleight of hand. Instead of creating particle objects, we’ll draw lines and animate the dash offset to make it appear like particles. Kind of like those SVG line animations but in 3D. This is trick works exceptionally well when you want particles to move along a path.

So, how does it work? It’s a three step process:

  1. Generate a bunch of points
  2. Use THREE.CatmullRomCurve3 to convert them into a curve
  3. Draw the curve using THREE.MeshLine and animate the dashOffset

Repeat this process for each line.

Loading...

You can play around with the dash ratio, line count and thickness to create different effects.

How you generate the points totally changes the effect. You can use a mathematical equation like polynomials or bezier curves. In my case, I decided to go with a polar coordinates approach.

We’ll increment the angle step-by-step from 0 to 2 PI radians and use it to calculate the (x,y) coordinates and keep z as 0. Most importantly, adding a bit of variance to the radius for a more organic look.

const radiusVariance = () => Random.range(0.2, 1);

const lines = useMemo(
  () =>
    new Array(lineCount).fill().map((_, index) => {
      // starting position
      const pos = new THREE.Vector3(
        Math.sin(0) * radius * radiusVariance(),
        Math.cos(0) * radius * radiusVariance(),
        0
      );
      // Increment the angle to create the points      const points = new Array(30).fill().map((_, index) => {        const angle = (index / 20) * Math.PI * 2;        return pos          .add(            new THREE.Vector3(              Math.sin(angle) * radius * radiusVariance(),              Math.cos(angle) * radius * radiusVariance(),              Math.sin(angle) * Math.cos(angle) * radius * radiusVariance()            )          )          .clone();      });
      // convert points into a curve
      const curve = new THREE.CatmullRomCurve3(points).getPoints(1000);

      return {
        color: colors[parseInt(colors.length * Math.random(), 10)],
        width: Math.max(0.1, (0.2 * index) / 10),
        speed: Math.max(0.001, 0.004 * Math.random()),
        curve,
      };
    }),
  [count, colors, radius]
);

Lastly, we can draw each line and animate the dashOffset value using the useFrame hook.

function SparkLine({ curve, width, color, speed }) {
  const material = useRef();

  useFrame(() => {    material.current.uniforms.dashOffset.value -= speed;  });
  return (
    <mesh>
      <meshLine attach="geometry" points={curve} />
      <meshLineMaterial
        ref={material}
        transparent
        depthTest={false}
        lineWidth={width}
        color={color}
        dashArray={0.1}
        dashRatio={0.95}
      />
    </mesh>
  );
}

My go-to option for drawing 3D lines is THREE.MeshLine. It’s a fantastic feature-rich library. However, it doesn’t yet support Three.js version 128 and up. But, worry not, I’ve included a patched up version in my demo repo that you can use instead: 3d-particle-effects-demo/tree/main/src/MeshLine

Advance (move) a lineLoading...

Lastly, we have the Spark Storm. With particles buzzing around the planet in a peculiar motion. This requires a technique related to the previous one. However, instead of relying on a dash offset, we’ll draw a line and then animate it using a mathematical system. This is a bit more involved, but a whole lot more fun!

You can use all kinds of mathematical models to create the movement. I decided to go with Attractors mapped to the surface of a sphere. You’ve likely seen these attractors before. The Lorenz attractor creates this famous butterfly-like curve.

const newPosition = attractor(currentPosition, timeStep);

We’re not going to get into the math behind attractors. For our purposes, an attractor is simply a function that uses the current position and a time step value to calculate the next location for a point.

Notice how that ball moves along the attractor. We’ll use this effect to power our particle system by tracking multiple points on the attractor. Then connect those points to draw a line.

Loading...

We start with an initial, randomly generated, list of positions. Then use the attractor to calculate a new position on each frame. We then drop the last position in the list to advance the line and push this new position in. THREE.MeshLine has a handy advance function built in that does this for you.

function StormLine({ radius, simulation, width, color }) {
  const line = useRef();

  // Create the points
  const [positions, currentPosition] = useMemo(() => createAttractor(5), []);

  // Move the points
  useFrame(() => {
    if (line.current) {
      const nextPosition = updateAttractor(
        currentPosition,
        radius,
        simulation,
        0.005
      );

      line.current.advance(nextPosition);
    }
  });

  // Draw the line
  return (
    <mesh>
      <meshLine ref={line} attach="geometry" points={positions} />
      <meshLineMaterial transparent lineWidth={width} color={color} />
    </mesh>
  );
}

There are many different attractors, and each produces a different kind of curve. The system randomly picks an attractor for each line of the Spark Storm, which adds a good amount of variation to the animation and makes for a pretty cool effect!

The other significant bit here is to map the positions to the surface of a sphere. With vectors, that’s as simple as normalizing and scaling the vector. Without this, the particles will fly all over the place, and I wanted to make it look like they were engulfing a planet.

const normalizedPosition = currentPosition
  .clone()
  .normalize()
  .multiplyScalar(scale);

This is probably my favourite technique because you can change the mathematical model to create totally different effects. For example, you could use a flocking algorithm or noise instead of attractors.

Wrapping up

Thanks for following along. I hope breaking down these techniques will encourage you to make your own awesome particle systems. Do share them with me on Twitter @winkerVSbecks — I love seeing what you all make!

The complete source code is available at github.com/winkerVSbecks/3d-particle-effects-demo.

Want to dive deeper? Several particle effect examples are available on the Three.js and React Three Fiber sites. Also, Codrops has many excellent tutorials on this topic.

https://varun.ca/three-js-particles/
How to build stunning 3D scenes with React Three Fiber
WebGL is the magic sauce behind Solar Storm , an audio-reactive music video that renders live in the browser. After fumbling around with…
Show full content

WebGL is the magic sauce behind Solar Storm , an audio-reactive music video that renders live in the browser. After fumbling around with Three.js for many years, WebGL finally clicked for me thanks to React Three Fiber. That’s because I could use the familiar concepts—components, props, hooks and state—and transfer my app development skills to 3D graphics.

This article shows you how to create breathtaking 3D animations using React Three Fiber (R3F). We’ll walk through setting up a stage, creating geometry, adding lighting and enabling post-processing effects. What’s more, you’ll get to learn by recreating the animation below from scratch.

Declarative and Componentized WebGL

Before we begin, it’s worth covering why building with components offers more benefits than just familiarity.

Breaking up the UI into components makes it easier to reason through your code and build complex interfaces. The structure, styling and associated logic get encapsulated into one reusable module. You can test all its variants and work through its edge cases. The same is true for WebGL.

With Three.js, the code for creating the geometry & material, combining it into a mesh and animating is split across multiple parts of the file. R3F enables you to package all of that into a single component. That way, you can start by building basic meshes, then progressively combining them to create scenes and finally, layer on post-processing effects.

Build it up part by part

Storybook is my go-to tool for building meshes in isolation. That allows you to finesse them without worrying about other scene elements. No constant repositioning or toggling global lighting & effects. You can focus on getting the look and feel just right.

To get started, you need a minimal scene that’ll wrap each story using a decorator.

import { StoryStage } from '../../.storybook/StoryStage';
import { TetrisBlock } from './TetrisBlock';

export default {
  title: 'Meshes/TetrisBlock',
  component: TetrisBlock,
  decorators: [(storyFn) => <StoryStage>{storyFn()}</StoryStage>],
};
Your first scene

Web UIs are built using HTML & CSS. 3D scenes are similar, but the building blocks are geometry, material and lights. You start with a Geometry that describes the shape of an object (think HTML). You can apply a Material to this geometry that controls the look and feel (think CSS). Combined together, you get an Mesh. You can place this mesh in a 3D space. Then add in some Lights to be able to see your object. Finally, a Renderer renders the whole thing as though you were looking at it through a Camera.

Loading...

The above is a demo of an introductory 3D scene—a rotating box. Notice how everything is a component. The canvas, lights, geometry, material and mesh. The entire Three.js API is automatically available as components in R3F.

Decomposing & building the scene

Now that we have the basics down, let’s go back to the visualization we’re building. The high-level elements of this scene are:

  • Components: these have Tetris block shapes with translucent material.
  • Data & State: these are flat planes with funky custom textures.
  • Label: this is just a text overlay.

The Scene consists of Label, State, Data and Components

Components (Tetris blocks)

The components in this visualization are represented by Tetris blocks. These shapes are constructed by drawing a 2D path and then extruding it along the Z-axis. We’ll use the Extrude utility from Drei, which offers a shortcut syntax to create such shapes.

Drei is a helper library that offers utilities for common Three.js tasks, e.g., adding audio or Orbit controls or creating a custom shader material.Loading...

The syntax for defining the shape is quite similar to SVG Paths. You can control the extrude depth and bevelled edges using the extrudeSettings.

That’s that for the geometry, but remember, a mesh consists of a geometry plus a material. We’ll use MeshPhysicalMaterial here. It allows you to replicate physical properties such as transparency, roughness and clearcoat. In this case, we’ve used it for a Glassmorphism effect, which is a super popular aesthetic right now.

Data & State

Now that we know how to create meshes let’s focus on making custom materials. At its core, a material is a shader, and a shader is a program that takes a set of inputs and produces a texture. For Data and State, we’ll build custom shader materials using the shaderMaterial utility from Drei.

Loading...

shaderMaterial takes in a set of uniforms plus the vertex and fragment shaders, and returns a material. We then use extend to add it to the R3F namespace and finally apply it to the Plane.

Not sure how to get started with shaders? Check out the Poimandres marketplace for a bunch of ready-to-use materials.

The shader in this example is using noise and is animated using the time uniform. You can use the useFrame hook to access the Three.js clock and update the time value.

Label

Rendering text in WebGL is complicated. That complexity only ramps up if you want to use a custom font. That’s why if possible, use HTML and layer the text on top of your scene. That’s exactly what we’re doing here. The Label is absolutely positioned on top of the canvas.

import React from 'react';
import styled from 'styled-components';

const Container = styled.div`
  pointer-events: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 144px;
`;

const Box = styled.div`...`;
const Text = styled.div`...`;

export const Label = () => {
  return (
    <Container>
      <Box>
        <Text>Atomic components</Text>
      </Box>
    </Container>
  );
};

For more complex applications, I recommend the Html component, which controls size and positioning of your HTML content based on its location in the 3D space.

Compose the scene and animate!

We have all the bits now; the next step is to animate them using React Spring. It uses a spring-physics-based model where the animated props are generated using the useSpring hook and applied to an animated component instance.

Loading...

The visualization has four states and the position, scale and colour of the meshes animate between each step. We can track all that info in an array and use component state for cycling between each step.

import React from 'react';

const STEP_COUNT = 4;

export function useCDDState() {
  const [step, setStep] = React.useState(0);

  React.useEffect(() => {
    const t = setInterval(
      () => setStep((state) => (state + 1) % STEP_COUNT),
      3000
    );
    return () => clearTimeout(t);
  }, []);

  return step;
}

// Then in the component:
//
// const step = useCDDState();
// const spring = useSpring(STATES[step]);

The embed below is interactive. Zoom out and rotate the scene to see how the animation is executed. Or check out the source to see how each mesh is wired up to a spring.

React Spring works with both Three.js objects and HTML, so we can also apply the same principles to animate the label. All animations are driven by the same state and therefore stay in sync.

Post processing effects

Lastly, we’re going to add a glow effect to the scene. Post-processing effects are also shaders but applied to the entire rendered output instead of individual meshes.

This whole process is often referred to as a render pipeline. You start by rendering the scene as usual and pass that as the input into the first effect shader. Then give that output to the following effect and so on. The output of the final effect is rendered onto the screen.

import * as THREE from 'three';
import React, { useMemo } from 'react';
import { Effects as EffectsComposer } from '@react-three/drei';
import { extend, useThree } from '@react-three/fiber';
import { UnrealBloomPass } from 'three-stdlib';

extend({ UnrealBloomPass });

export const Effects = () => {
  const { size, scene, camera } = useThree();
  const aspect = useMemo(
    () => new THREE.Vector2(size.width, size.height),
    [size]
  );

  return (
    <EffectsComposer
      multisamping={8}
      renderIndex={1}
      disableGamma
      disableRenderPass
    >
      <renderPass attachArray="passes" scene={scene} camera={camera} />
      <unrealBloomPass attachArray="passes" args={[aspect, 0.4, 1, 0]} />
    </EffectsComposer>
  );
};

In our case, we have just one effect, the unrealBloomPass, which adds that nice sci-fi glow.

Wrapping up

Success! We used React Three Fiber to create an animated 3D visualization. The whole thing is simply a component that you can drop into any React app, and it’ll work.

Solarstorm was built mainly using these techniques. The significant difference was that instead of animating with springs, I used JavaScript to analyze the audio and use that output to drive the animations. Now that you’re familiar with these concepts, jump into the source code or use that as a starting point for your own audio reactive scene.

https://varun.ca/modular-webgl/
Noise in Creative Coding
Noise is an indispensable tool for creative coding. We use it to generate all kinds of organic effects like clouds, landscapes and contours…
Show full content

Noise is an indispensable tool for creative coding. We use it to generate all kinds of organic effects like clouds, landscapes and contours. Or to move and distort objects with a more lifelike behaviour.

On the surface, noise appears to be simple to use but, there are so many layers to it. This post takes a deep dive into what noise is, its variants, how to use it on the web and its applications. And lot’s of examples. So many examples!

What is noise?

Imagine you want to move an object around the screen. Animators will use keyframes and tweens to describe the exact motion. Generative artists instead rely on algorithms.

So what, something like math.random()?

Not exactly. Randomness is just too unnatural. Look at that pink ball, bouncing all over the place. It’s nauseating 🥴

What we need is a smoother, more organic randomness. That is what the noise function generates (the yellow ball). Much more aesthetically pleasing!

Loading...

This idea of organic randomness appears again and again in creative coding. For example, when generating textures, moving objects or distorting them. We often reach for the noise function.

Generating noise on the web

There are two flavours of noise—Perlin and Simplex.

Ken Perlin developed the first while working on Tron in the early 1980s and won an Oscar 🏆 for those special effects. He then improved on it with Simplex noise and made it a bit faster. I’ll focus on the latter and use the simplex-noise library for my examples.

The noise function takes in a couple of inputs (which drive the output) and returns a value between -1 and 1. The output is a mix of Math.sin() and Math.random(). A random wave is how I would describe it.

import SimplexNoise from 'simplex-noise';

const simplex = new SimplexNoise();
const y = simplex.noise2D(x * frequency, 0) * amplitude;

The underlying mechanics are similar to how waves work. You can control how quickly or how much it oscillates by adjusting frequency and amplitude.

Loading...

Umm, noise… 2D, what’s happening here?

Noise Dimensions

The noise algorithm can be implemented for multiple dimensions. Think of these as the number of inputs into the noise generator—two for 2D, three for 3D and so on.

Which dimension you pick depends on what variables you want to drive the generator with. Let’s look at a few examples.

💡 Starting here, each example is embedded in a source card. With links to the actual source code and, in some cases, variables you can tweak.Noise 1D — Wave Motion

Technically there is no noise1D. You can get 1D noise by passing in zero as the second argument to simplex.noise2D. Let’s say you want to move a ball with an organic-looking oscillating motion. Increment its x coordinate and use it to generate the y value.

x += 0.1;
y = simplex.noise2D(x * frequency, 0) * amplitude;
Loading...Noise2D — Terrain Generator

We can turn a flat 2D plane into hilly terrain by moving its vertices in the z-direction. Use the x and y coordinates to generate the z location.

And just like that, we have a terrain generator 🏔️

z = simplex.noise2D(x * frequency, y * frequency) * amplitude;
Loading...Noise3D — Vertex Displacement

You’ve probably seen these distorted spheres in the wild. They are created by displacing the vertices of a sphere. Use the vertex coordinate (x, y, z) to generate the distortion amount. Then displace the vertex by it radially.

const distortion =
  simplex.noise3D(x * frequency, y * frequency, z * frequency) * amplitude;

newPosition = position.normalize().multiplyScalar(distortion);
Loading...Noise4D — Animated Distortion

We can animate the distorted sphere by using 4D noise. The inputs will be the vertex coordinate (x, y, z) and time. This technique is used to create fireballs, amongst other things.

const distortion =
  simplex.noise4D(
    x * frequency,
    y * frequency,
    z * frequency,
    time * frequency
  ) * amplitude;

newPosition = position.normalize().multiplyScalar(distortion);
Loading...

Notice our use of amplitude in the above examples. It’s a handy way to scale the noise output to your application. You could also use interpolation to map the noise output to a specified range of your choice.

mapping noise

Now that we have the basics down let’s look at a few more applications of noise.

Textures

The noise output for a 2D grid, with (x,y) coordinates, looks something like this:

raw noise output

Generated using:

for (let y = 0; y < gridSize[1]; y++) {
  for (let x = 0; x < gridSize[0]; x++) {
    const n = simplex.noise2D(
      x / (gridSize[0] * 0.75),
      y / (gridSize[1] * 0.75)
    );
  }
}

This is going to be our starting point.

We can use the Marching Squares algorithm to turn that 2D noise data into contours. Using Canavas, SVG, WebGL or whatever else you prefer. I recently used this technique with SVG to create generative profile cards. For a full tutorial on that, check out creating a generative image service 📸

noise contours

The cool thing about noise is that you can go up one dimension, layer in time and animate your image.

simplex.noise3D(x / (gridSize[0] * 0.75), y / (gridSize[1] * 0.75), time);
Fields

Honestly, this 2D grid of noise data is super utilitarian. It’s my go-to for all kinds of stuff. One of which is noise fields—a particular favourite of mine.

Let’s go back to that initial grayscale output. Map that to a more interesting colour scale, and you get plasma 🤩

Or, start with a grid of rectangles. Then turn it into a vector field by using noise to control the colour and angle of rotation.

Noisy Particles

Vector fields are cool, but flow fields are an even more exciting visualization. Here’s a plot I made last year.

Notice the path that the pen traces. Each one of those strokes is generated by dropping a particle onto the vector field. And then tracing its path. 💫

Alright, let’s break down this process.

Creating a Flow Field

Step 1, create a vector field. Same as before. But this time, we’re not going to animate the vector field.

Step 2, drop a bunch of particles onto the canvas. Their direction of movement is based on the underlying vector field. Take a step forward, get the new direction and repeat.

function moveParticle(particle) {
  // Calculate direction from noise
  const angle =
    simplex.noise2D(particle.x * FREQUENCY, particle.y * FREQUENCY) * AMPLITUDE;

  // Update the velocity of the particle
  // based on the direction
  particle.vx += Math.cos(angle) * STEP;
  particle.vy += Math.sin(angle) * STEP;

  // Move the particle
  particle.x += particle.vx;
  particle.y += particle.vy;

  // Use damping to slow down the particle (think friction)
  particle.vx *= DAMPING;
  particle.vy *= DAMPING;

  particle.line.push([particle.x, particle.y]);
}

Step 3, track the location of the particle to trace its path.

Add in more particles and colour, and we get our flow field.

Particle systems

Speaking of particles, noise shows up in particle systems too. For effects like rain, snow or confetti, you’re looking to simulate a natural-looking motion. Imagine a confetti particle floating down to earth. The particles don’t move in straight lines. They float and wiggle due to air resistance. Noise is a really great tool for adding in that organic variability.

const wiggle = {
  x: simplex.noise2D(particle.x * frequency, time) * amplitude,
  y: simplex.noise2D(particle.y * frequency, time) * amplitude,
};
particle.x += wiggle.x;
particle.y += wiggle.y;

For a deeper dive, check my post on building a confetti cannon 🎉

Shaders

Let’s circle all the way back to those animated blobs. Manipulating and animating 3D geometries is a lot more efficient with shaders. But shaders are a whole different world unto themselves. For starters, there is a special WebGL version of noise, glsl-noise. We’ll need to import that into our shader using glslify.

We’ll take it slow, starting with 2D first.

The animation above is quite similar to the contours we looked at earlier. It’s implemented as a fragment shader. Which means we run the same program for each pixel of the canvas.

Notice the pragma line? That’s us importing webgl-noise. Then use it to generate noise data for each pixel position vUv.

This part should look quite familiar by now.

fragment.glsl
precision highp float;
uniform float time;
uniform float density;
varying vec2 vUv;

#define PI 3.141592653589793
#pragma glslify: noise = require(glsl-noise/simplex/3d);

float patternZebra(float v){
  float d = 1.0 / density;
  float s = -cos(v / d * PI * 2.);
  return smoothstep(.0, .1 * d, .1 * s / fwidth(s));
}

void main() {
  // Generate noise data  float amplitude = 1.0;  float frequency = 1.5;  float noiseValue = noise(vec3(vUv * frequency, time)) * amplitude;  // Convert noise data to rings
  float t = patternZebra(noiseValue);
  vec3 color = mix(vec3(1.,0.4,0.369), vec3(0.824,0.318,0.369), t);
  // Clip the rings to a circle
  float dist = length(vUv - vec2(0.5, 0.5));
  float alpha = smoothstep(0.250, 0.2482, dist);

  gl_FragColor = vec4(color, alpha);
}

Fragment shaders have this concept of distance functions used for drawing shapes. patternZebra is one such example that converts noise data into rings. Essentially it returns either 0 or 1. Which we then use to pick a colour using mix. And finally, use another distance function to clip a circular boundary.

Seems quite basic?

Well, the amazing thing about shaders is that you can turn them into a material and apply it to more complex geometry.

Noise Material

We can take the same 2D mars shader from above. Convert it into a ThreeJS shader material. Then use the vertex position vPosition to generate noise instead of vUv.

And voilà, we have a 3D Mars!

const material = new THREE.ShaderMaterial({
  extensions: {
    derivatives: true,
  },
  uniforms: {
    time: { value: 0 },
    density: { value: 1.0 },
  },
  vertexShader: /*glsl*/ `
    varying vec3 vPosition;
    void main () {
      vPosition = position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
    `,
  fragmentShader: glslify(/* glsl */ `
    precision highp float;
    varying vec3 vPosition;
    uniform float time;
    uniform float density;
    #pragma glslify: noise = require(glsl-noise/simplex/4d);
    #define PI 3.141592653589793
    float patternZebra(float v){
      float d = 1.0 / density;
      float s = -cos(v / d * PI * 2.);
      return smoothstep(.0, .1 * d, .1 * s / fwidth(s));
    }
    void main () {
      float frequency = .6;
      float amplitude = 1.5;
      float v = noise(vec4(vPosition * frequency, sin(PI * time))) * amplitude;
      float t = patternZebra(v);
      vec3 fragColor = mix(vec3(1.,0.4,0.369), vec3(0.824,0.318,0.369), t);
      gl_FragColor = vec4(fragColor, 1.);
    }
    `),
});

This is honestly just a start. Shaders open up so many possibilities. Like the example below 👻

Not only is noise mapping to colours but, also alpha.

Glossy Blobs

The idea behind vertex displacement is quite similar. Instead of a fragment shader, we write a vertex shader that transforms each vertex of the sphere. Again, applied using a shader material.

vertex.glsl
precision highp float;
varying vec3 vNormal;

#pragma glslify: snoise4 = require(glsl-noise/simplex/4d)

uniform float u_time;
uniform float u_amplitude;
uniform float u_frequency;

void main () {
  vNormal = normalMatrix * normalize(normal);  float distortion = snoise4(vec4(normal * u_frequency, u_time)) * u_amplitude;  vec3 newPosition = position + (normal * distortion);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}

Oh, and throw on some matcap, and you’ve got some glossy animated blobs.

Loading...Your turn to make some noise

That was quite a journey. Motion, textures, fields, particle systems and displacement—noise can do it all. It truly is the workhorse of the creative coding world.

So obviously, you want to play with noise 😁

I’ve put together a little starter CodePen for you. It spins up a grid of noise and maps it to an array of glyphs. Just start by modifying the array and see how far you can take it.

const glyphs = ['¤', '✳', '●', '◔', '○', '◕', '◐', '◑', '◒'];

Or if you’re looking for something more advanced, check out noisedeck.app. It’s like a synthesizer but for visuals.

Either way, don’t forget to share your creations with me on Twitter.

https://varun.ca/noise/
Let's Build a Confetti Cannon
Confetti cannons are fun! Both to play with and to build. Let's learn to make one. Along the way, we'll cover particle systems and a bit of…
Show full content

Confetti cannons are fun! Both to play with and to build. Let’s learn to make one. Along the way, we’ll cover particle systems and a bit of high school physics. I’ll also show you how to integrate a Canvas based animation into a larger application.

The Confetti System 🎉

Confetti is all about a quick pop, followed by a slow, wobbly tumble to the ground. In graphical terms, it can be modelled as a particle system.

“A particle system is a collection of many many minute particles that together represent a fuzzy object. Over a period of time, particles are generated into a system, move and change from within the system, and die from the system.”

— William Reeves, “Particle Systems—A Technique for Modeling a Class of Fuzzy Objects”

You’ve seen these before. They are used to model all sorts of natural phenomena: fire, smoke, waterfall, fog, grass, bubbles, a flock of birds, and so on.

We’re not just building any old particle system. We’re building one inspired by Laura Belém’s Carnival Nights.

But why a particle system?

Confetti is made up of many small pieces of paper. Each piece of paper follows the same rules of physics. A particle allows us to encapsulate this behaviour in code. The system manages a collection of these particles. We don’t want to control each particle on its own. Instead, we give the system a few high-level parameters and let it drive the simulation.

The confetti particle

Each particle has a few attributes that define its state:

  • Start position
  • Angle: the direction of movement
  • Velocity: how fast it’s moving

All combined, these tell us where the particle is, which direction it’s moving in, and how fast.

Let’s move it

We’ll use the velocity components to move the particle.

particle.x += Math.cos(particle.angle) * particle.velocity;
particle.y += Math.sin(particle.angle) * particle.velocity;

This essentially describes the motion in terms of vectors.

New position = Current Position + Velocity Vector

Often graphics & physics libraries use an actual vector object. I’m keeping things simple and breaking up the motion into x and y directions.

This is the foundational movement we’ll build on top of. Right now, it lacks any natural characteristics. It is the equivalent of confetti being fired in space. On Earth, its motion is impacted by external forces: friction, air resistance and gravity.

Decaydecay

0

Friction is the first of those forces. We model that as a decay multiplier.

The decay should be less than 1 to slow things down. In each frame, we multiply the velocity by decay. The smaller the value, the quicker it’ll slow down.

particle.x += Math.cos(particle.angle) * particle.velocity;
particle.y += Math.sin(particle.angle) * particle.velocity;
particle.velocity *= particle.decay;
Gravity

What goes up must come down, with a bit of gravity.

gravity

0

The confetti’s net motion is a combination of three forces: the initial launch, the decay and the gravity.

It starts by launching upwards. The decay then starts slowing it down. Then eventually, the gravity overpowers it and starts pulling it downwards in the y-direction.

particle.x += Math.cos(particle.angle) * particle.velocity;
particle.y +=  Math.sin(particle.angle) * particle.velocity + particle.gravity;particle.velocity *= particle.decay;

Too little gravity, and it’ll look like the confetti was fired on the Moon. And too much gravity will stop it from getting very far. You can tweak the value to find the right balance.

Tilt

The confetti also tilts as it moves. Sometimes rotating just one way and sometimes back and forth.

Each particle will have a starting tilt angle. Chosen randomly at launch. Which is then animated using noise. Which gives it a more natural tumbling look.

const tiltOffset = Random.noise2D(
  particle.x / particle.random,
  particle.y / particle.random,
  1,
  Math.PI / 16
);
particle.tiltAngle = particle.tiltAngle + tiltOffset;

Noise is a perfect tool for simulating organic motion. Think of it as a smoother form of randomness.

🚂 Not familiar with noise? Checkout this Coding Train episode for a primer: Introduction - Perlin Noise and p5.js TutorialWobble

In nature, things rarely move in straight lines. We’re going to add a bit of wobble. This will simulate the effect of confetti wafting through the air.

We’ll once again use noise. This time, however, it will add an offset to the x-position of the particle.

particle.x += Math.cos(particle.angle) * particle.velocity;

const xOffset = Random.noise2D(
  particle.x / particle.random,
  particle.y / particle.random
);

// Add wobble using 2D noise
particle.x = particle.x + xOffset;

That’s much better. The movement feels a lot more natural now.

The particle system

Moving on to the particle system. Each particle is an object. This object tracks all its visual characteristics and those related to motion.

We also need to track the lifetime of each particle. Notice how they fade out after a while? That’s their lifetime. We do this to track when the animation is complete and trigger another one. I chose to track the lifetime as ticks or frame count.

It will be responsible for four aspects:

  • Creating the particles and seeding their initial attributes. For example, launch velocity, tilt and colour.
  • Updating their motion at each tick or frame. And marking them as dead once their allotted tick count has been reached.
  • Drawing the particle
  • Resetting the particles once all of them are dead and triggering another pop

Now, some attributes are defined at the system level and passed down. For example, start position, gravity, decay and the size of the particle. While others, such as colour, are set for each particle.

// System Level Attributes
{
  particleCount: 90,
  radiusRatio: 0.02,
  animDelay: 600,
  noInteractionWait: 5000,
  velocityFactor: 0.15,
  decay: 0.94,
  gravity: 3,
  x: 0,
  y: 0,
  colors,
}

Some attributes are set at the system level but inform individual particles—for example, direction and velocity. We pick a direction of launch. Then each particle is launched in that direction ± a slight variance. The larger the variance, the wider the confetti will spread.

An even spread

At this point, we have a confetti system. The fun thing about particle systems is that you can tweak their behaviour in all kinds of exciting ways. I wanted to recreate an effect similar to the original image. Make the confetti pop from one location, but then get it to spread somewhat evenly across the canvas.

Instead of cascading the launch angle down to the particle, we’ll try something different. We start by picking a random start position. Based on the bounds, we calculate a random end position—a different one for each particle. Now we have a start point and an endpoint. With a bit of trigonometry, we can calculate the angle between them. And then calculate a launch velocity as a function of the distance between the two points.

function setEndLocation({ width, height }, x, y) {
  const xBounds = [-x, width - x];
  const yBounds = [-y, height - y];

  return [x + Random.range(...xBounds), y + Random.range(...yBounds)];
}

function launchAngle([x1, y1], [x2, y2]) {
  return Math.atan2(y2 - y1, x2 - x1);
}

function launchVelocity(maxDist, startPos, endPos, startVelocity) {
  const d = dist(startPos, endPos);
  return mapRange(d, 0, maxDist, startVelocity * 0.1, 1 * startVelocity);
}
Integrating it into an app

I originally built this as a digital greeting card. It sits inside a larger Svelte app.

Canvas is quite portable. You can render the DOM node using vanilla HTML or a SPA framework like React or Svelte. And once the DOM node mounts, initialize the animation. The animation then runs in its own loop. You can leave it unattached or trigger a refresh based on props.

Canvas.svelte
<script>
  import { onMount } from 'svelte';
  import initConfettiSystem from './confetti-system';
  let canvasEl;
  onMount(() => {
    setTimeout(() => {
      initConfettiSystem(canvasEl);
    }, 1000);
  });
</script>

<canvas class="carnival-nights" bind:this="{canvasEl}"></canvas>
The confetti particle

Each particle has a few attributes that define its state:

  • Start position
  • Angle: the direction of movement
  • Velocity: how fast it’s moving

All combined, these tell us where the particle is, which direction it’s moving in, and how fast.

Let’s move it

We’ll use the velocity components to move the particle.

particle.x += Math.cos(particle.angle) * particle.velocity;
particle.y += Math.sin(particle.angle) * particle.velocity;

This essentially describes the motion in terms of vectors.

New position = Current Position + Velocity Vector

Often graphics & physics libraries use an actual vector object. I’m keeping things simple and breaking up the motion into x and y directions.

This is the foundational movement we’ll build on top of. Right now, it lacks any natural characteristics. It is the equivalent of confetti being fired in space. On Earth, its motion is impacted by external forces: friction, air resistance and gravity.

Decaydecay

0

Friction is the first of those forces. We model that as a decay multiplier.

The decay should be less than 1 to slow things down. In each frame, we multiply the velocity by decay. The smaller the value, the quicker it’ll slow down.

particle.x += Math.cos(particle.angle) * particle.velocity;
particle.y += Math.sin(particle.angle) * particle.velocity;
particle.velocity *= particle.decay;
Gravity

What goes up must come down, with a bit of gravity.

gravity

0

The confetti’s net motion is a combination of three forces: the initial launch, the decay and the gravity.

It starts by launching upwards. The decay then starts slowing it down. Then eventually, the gravity overpowers it and starts pulling it downwards in the y-direction.

particle.x += Math.cos(particle.angle) * particle.velocity;
particle.y +=  Math.sin(particle.angle) * particle.velocity + particle.gravity;particle.velocity *= particle.decay;

Too little gravity, and it’ll look like the confetti was fired on the Moon. And too much gravity will stop it from getting very far. You can tweak the value to find the right balance.

Tilt

The confetti also tilts as it moves. Sometimes rotating just one way and sometimes back and forth.

Each particle will have a starting tilt angle. Chosen randomly at launch. Which is then animated using noise. Which gives it a more natural tumbling look.

const tiltOffset = Random.noise2D(
  particle.x / particle.random,
  particle.y / particle.random,
  1,
  Math.PI / 16
);
particle.tiltAngle = particle.tiltAngle + tiltOffset;

Noise is a perfect tool for simulating organic motion. Think of it as a smoother form of randomness.

🚂 Not familiar with noise? Checkout this Coding Train episode for a primer: Introduction - Perlin Noise and p5.js TutorialWobble

In nature, things rarely move in straight lines. We’re going to add a bit of wobble. This will simulate the effect of confetti wafting through the air.

We’ll once again use noise. This time, however, it will add an offset to the x-position of the particle.

particle.x += Math.cos(particle.angle) * particle.velocity;

const xOffset = Random.noise2D(
  particle.x / particle.random,
  particle.y / particle.random
);

// Add wobble using 2D noise
particle.x = particle.x + xOffset;

That’s much better. The movement feels a lot more natural now.

The particle system

Moving on to the particle system. Each particle is an object. This object tracks all its visual characteristics and those related to motion.

We also need to track the lifetime of each particle. Notice how they fade out after a while? That’s their lifetime. We do this to track when the animation is complete and trigger another one. I chose to track the lifetime as ticks or frame count.

It will be responsible for four aspects:

  • Creating the particles and seeding their initial attributes. For example, launch velocity, tilt and colour.
  • Updating their motion at each tick or frame. And marking them as dead once their allotted tick count has been reached.
  • Drawing the particle
  • Resetting the particles once all of them are dead and triggering another pop

Now, some attributes are defined at the system level and passed down. For example, start position, gravity, decay and the size of the particle. While others, such as colour, are set for each particle.

// System Level Attributes
{
  particleCount: 90,
  radiusRatio: 0.02,
  animDelay: 600,
  noInteractionWait: 5000,
  velocityFactor: 0.15,
  decay: 0.94,
  gravity: 3,
  x: 0,
  y: 0,
  colors,
}

Some attributes are set at the system level but inform individual particles—for example, direction and velocity. We pick a direction of launch. Then each particle is launched in that direction ± a slight variance. The larger the variance, the wider the confetti will spread.

An even spread

At this point, we have a confetti system. The fun thing about particle systems is that you can tweak their behaviour in all kinds of exciting ways. I wanted to recreate an effect similar to the original image. Make the confetti pop from one location, but then get it to spread somewhat evenly across the canvas.

Instead of cascading the launch angle down to the particle, we’ll try something different. We start by picking a random start position. Based on the bounds, we calculate a random end position—a different one for each particle. Now we have a start point and an endpoint. With a bit of trigonometry, we can calculate the angle between them. And then calculate a launch velocity as a function of the distance between the two points.

function setEndLocation({ width, height }, x, y) {
  const xBounds = [-x, width - x];
  const yBounds = [-y, height - y];

  return [x + Random.range(...xBounds), y + Random.range(...yBounds)];
}

function launchAngle([x1, y1], [x2, y2]) {
  return Math.atan2(y2 - y1, x2 - x1);
}

function launchVelocity(maxDist, startPos, endPos, startVelocity) {
  const d = dist(startPos, endPos);
  return mapRange(d, 0, maxDist, startVelocity * 0.1, 1 * startVelocity);
}
Integrating it into an app

I originally built this as a digital greeting card. It sits inside a larger Svelte app.

Canvas is quite portable. You can render the DOM node using vanilla HTML or a SPA framework like React or Svelte. And once the DOM node mounts, initialize the animation. The animation then runs in its own loop. You can leave it unattached or trigger a refresh based on props.

Canvas.svelte
<script>
  import { onMount } from 'svelte';
  import initConfettiSystem from './confetti-system';
  let canvasEl;
  onMount(() => {
    setTimeout(() => {
      initConfettiSystem(canvasEl);
    }, 1000);
  });
</script>

<canvas class="carnival-nights" bind:this="{canvasEl}"></canvas>

You now have a foundational particle system. You can simulate other effects just by tweaking the particle’s attributes and behaviours. Create snow, rain or fireworks. Canvas-confetti is another really great example for reference. Or check out the source code for my greeting card to see how I added interactivity.

Flocking simulationTaking It to the Next Level

With confetti, each particle follows the same rules but runs independently. There are systems where particles interact with each other and influence behaviour. Flocking is an excellent example of this. In my next post, I’ll cover one such system. Sign up for my newsletter to get an update.

https://varun.ca/confetti/
Scrollytelling with React
Scrollytelling is a visual and interactive form of storytelling. It consists of a logical sequence of visualizations. They accompany a…
Show full content

Scrollytelling is a visual and interactive form of storytelling. It consists of a logical sequence of visualizations. They accompany a narrative and are driven by the user’s scrolling. In my last post, I used this technique to break down an animation. There are plenty of off-the-shelf libraries that one can use. I wanted something minimal that fit my existing publishing setup and didn’t impact content portability.

Let’s talk about how I built my React and Intersection Observer based solution.

What Are We Building?

The setup is largely inspired by Generative Artistry. It is incredibly powerful to put words and updating visuals side by side. This approach works particularly well for incremental tutorials and code walkthroughs.

What’s involved?
  1. The actual page layout
  2. A sticky container for the visualization
  3. Tracking the user’s scrolling and updating the visuals accordingly
Sectioning Content

The post is broken into discrete steps and each step has a visualization associated with it. But how do you define those steps?

One option would be to define them as an array. That’s not a great authoring experience though. You have to breakdown freeform text into JavaScript. Makes writing and editing super annoying.

Instead, I decided to use <h3> elements (in markdown) to signify a step.

post.mdx
## Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae. Ut venenatis tellus in metus. Nulla posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus.

## Mattis aliquam faucibus purus in

Metus aliquam eleifend mi in nulla posuere. Nunc id cursus metus aliquam eleifend mi. Venenatis cras sed felis eget velit aliquet. Sit amet cursus sit amet dictum sit amet. Orci eu lobortis elementum nibh tellus molestie nunc non blandit. Iaculis nunc sed augue lacus. Quis varius quam quisque id diam. Viverra ipsum nunc aliquet bibendum enim facilisis gravida.

## Nunc sed blandit libero volutpat sed cras ornare arcu dui

Id aliquet risus feugiat in ante. Mi eget mauris pharetra et ultrices. Sed arcu non odio euismod. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Auctor urna nunc id cursus metus aliquam eleifend. Condimentum mattis pellentesque id nibh tortor.

## Et netus et malesuada fames ac

In est ante in nibh mauris. Aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc. Id nibh tortor id aliquet lectus proin nibh nisl. Adipiscing elit duis tristique sollicitudin nibh sit amet commodo. Massa sapien faucibus et molestie ac feugiat sed lectus. Ipsum a arcu cursus vitae congue. Sociis natoque penatibus et magnis dis parturient montes. Interdum velit laoreet id donec ultrices.

Cool. So, each of those ## headings mark the start of a section.

How do you attach visuals to this?

I write my posts in MDX. Which means we can use React components within the content. I created a Scroller component which can wrap all or a part of the post. And we can pass in a list of figures to this component.

It’s also going to be responsible for the layout and scroll tracking.

post.mdx
export const figures = [  <img alt="image for section one" src="./img-1" />,  <img alt="image for section two" src="./img-2" />,  <img alt="image for section three" src="./img-3" />,];
## Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pulvinar elementum integer enim neque volutpat ac tincidunt vitae. Ut venenatis tellus in metus. Nulla posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus.

<Scroller width={300} figures={figures}>## Mattis aliquam faucibus purus in

Metus aliquam eleifend mi in nulla posuere. Nunc id cursus metus aliquam eleifend mi. Venenatis cras sed felis eget velit aliquet. Sit amet cursus sit amet dictum sit amet. Orci eu lobortis elementum nibh tellus molestie nunc non blandit. Iaculis nunc sed augue lacus. Quis varius quam quisque id diam. Viverra ipsum nunc aliquet bibendum enim facilisis gravida.

## Nunc sed blandit libero volutpat sed cras ornare arcu dui

Id aliquet risus feugiat in ante. Mi eget mauris pharetra et ultrices. Sed arcu non odio euismod. Nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Auctor urna nunc id cursus metus aliquam eleifend. Condimentum mattis pellentesque id nibh tortor.

## Et netus et malesuada fames ac

In est ante in nibh mauris. Aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc. Id nibh tortor id aliquet lectus proin nibh nisl. Adipiscing elit duis tristique sollicitudin nibh sit amet commodo. Massa sapien faucibus et molestie ac feugiat sed lectus. Ipsum a arcu cursus vitae congue. Sociis natoque penatibus et magnis dis parturient montes. Interdum velit laoreet id donec ultrices.
</Scroller>
Layout

The layout is relatively straightforward. A flex container that puts the text and visuals side-by-side. The visuals container has a fixed width while the text expands to fill the remaining space.

+----------------------------------------------------------+ | Container | | | | display: flex; | | | | align-items: flex-start; | | | | | | +---------------+ +------------------------------------+ | | | visuals | | | | | | | | Text Content | | | | flex:none | | | | | +---------------+ | flex: 1 1 auto; | | | | | | | | | | | | | | | +------------------------------------+ | +----------------------------------------------------------+Flex containerSticky Visuals

There are no restrictions to what the visuals can be: image, multiple images, video, code blocks, etc. Therefore, they are wrapped in a <figure> element. Which in turn uses position: sticky for its sticky behaviour.

flex: none;
position: sticky;
top: 64px;
margin-left: 0;
margin-top: 0;
Track scrolling and update visuals

Each section starts with an <h3>. We can track to see which of those headings is near the top of the viewport and display the visual for that section.

Intersection Observer is an efficient tool for this job. I wrote a custom hook that tracks all elements of a specific type, within a particular parent element. And then executes a callback when one of them enters the target area.

useIntersection.js
import { useEffect } from 'react';

export const useIntersection = (ref, selector, handler, options) => {
  useEffect(() => {
    const observers = [];

    if (ref.current && typeof IntersectionObserver === 'function') {
      const handleIntersect = idx => entries => {
        handler(entries[0], idx);
      };

      ref.current.querySelectorAll(selector).forEach((node, idx) => {
        const observer = new IntersectionObserver(
          handleIntersect(idx),
          options
        );
        observer.observe(node);
        observers.push(observer);
      });

      return () => {
        observers.forEach(observer => {
          observer.disconnect();
        });
      };
    }
    return () => {};
  }, [ref.current, options.threshold, options.rootMargin]);
};

We’re going to use this hook in the Scroller component. It’s set up to track all <h3> elements within the flex container. Notice the rootMargin, that’s how I limit the target area to roughly the top of the viewport.

scroller.js
export const Scroller = ({ figures = [], width, children }) => {
  const ScrollerContainerRef = useRef(null);
  const [activeFigure, setActiveFigure] = useState(0);

  useIntersection(    ScrollerContainerRef,    'h3',    (entry, idx) => {      if (entry.intersectionRatio === 1) {        setActiveFigure(idx);      }    },    { threshold: 1, rootMargin: '32px 0px -80% 0px' }  );
  const figure = figures[activeFigure];

  return (
    <Flex ref={ScrollerContainerRef} alignItems="flex-start">
      <StickyFigure width={width} mr={[3, 4]}>
        {figure}
      </StickyFigure>
      <Box maxWidth={7}>{children}</Box>
    </Flex>
  );
};

Now as the user scrolls, the IntersectionObserver tracks which heading is near the top. We then update activeFigure to that index. Which in turn is used to pick which figure to display.

That’s all there is to it!

Where can you take this next?

One obvious scenario I ran into was mobile. There just isn’t enough space to show text and images side-by-side. So, I split behaviour based on viewport size:

  • Small viewports: hide the sticky visual container and display the visuals inline instead
  • Large viewports: hide the inline visuals and make the sticky visual container visible

The inline visuals are placed by the author in the main content body. Check out the source for this responsive version.

This was just a start. The basic concept is to leverage visuals to explain ideas. And drive those visuals via scrolling. You can use it to modify or highlight code snippets. Add transitions between the visuals. Or drive an animation timeline. The possibilities are endless.

https://varun.ca/scrollytelling/
Making things move
One of my favourite forms of creative coding is to add motion to static images. The process is always the same: pick an image, recreate it…
Show full content
torsions

One of my favourite forms of creative coding is to add motion to static images. The process is always the same: pick an image, recreate it with Canvas, SVG or WebGL and animate aspects of its geometry. The image above is a good example. It’s based on Walter Leblanc’s print Torsions. In this post I’m going to break down the process of creating this animation. And share 2D techniques that I often use in my work.

Torsions

My go-to tool for creative coding these days is canvas-sketch. It is both a dev enviornment and a framework for creating generative art. By default, it allows you to create pieces using the Canvas API. However, you can also pair it with other graphics libraries like ThreeJS, p5.js, d3, etc. And most importantly it allows you to export high-resolution PNGs, animated GIFs or even render videos.

I’ll do a detailed dive into canvas-sketch in a future post. However, for now all you need to know is overall structure of the program. The settings object controls the dimensions and other characteristics of the animation. The sketch function recieves the canvas context and other props such as canvas width, height, playhead, etc. This is where you also define the actual draw loop. Most of your code will live inside that.

hello.js
// Import the library
const canvasSketch = require('canvas-sketch');

// Specify some output parameters
const settings = {
  // The [ width, height ] of the artwork in pixels
  dimensions: [ 256, 256 ],
  // Is it animated and the duration of the animation
  animate: true,
  duration: 6,
};

// Start the sketch
const sketch = () => {
  // The draw loop
  return (props) => {
    // Destructure what we need from props
    const { context, width, height } = props;

    // Now draw a white rectangle in the center
    context.strokeStyle = 'white';
    context.lineWidth = 4;
    context.strokeRect(width / 4, height / 4, width / 2, height / 2);
  };
};

// Start the sketch with parameters
canvasSketch(sketch, settings);
Torsions by Walter Leblanc
1. Getting Started

Our starting point in the original image. This is an ideal scenario. The image essentially gives you keyframes for what the animation should look like. Usually, we’re not that lucky. To figure out what to animate requires a bit more imagination.

Torsions by Walter Leblanc

So, what exactly is going on here? I see a block of some thickness. The top edge remains fixed while the bottom twists back and forth. As the block twists, the edge warps and creates the tapered look you see on the far right.

All in all, I see three parts: the beige front face, the blue back face and the front-facing edge.

2. Simplified Case — No Thickness

Let’s reduce this down to the simplest scenario, a flat 2D rectangle that is twisting around. As with hand-drawing, learning to draw with code requires you to observe an object and describe it in terms of lines and curves.

Edge curve animated

I see a rectangle with the bottom two vertices oscillating left and right. And the two vertical edges go from a straight line to a curve and then back again.

What is that curve?

3. Edge Curve

To me this looks a like a cosine wave. There is no cosine path API for Canvas, however, you can draw it using the bezier path API.

Edge curve

A bezier curve requires four points: start, end and two control points. This should be quite familiar if you’ve ever worked with vector paths in Illustrator, Sketch or Figma. Now, we know the start and endpoints. They are two vertices of the rectangle.

So, what about the control points? A quick google took me to this Stackoverflow answer. Here’s how you can calculate the two control points:

// Where (x1, y1) is the start and (x2, y2) the end points
const K = 0.37;

const cp1 = [x1, y1 + K * (y2 - y1)];
const cp2 = [x2, y2 - K * (y2 - y1)];

I’ve actually used this technique in a previous post, chillwave. Check it out for a full deep dive.

4. Straight Edge to Curve and Back AgainEdge curve control pointEdge curve control point

The edge oscillates between a straight line and a curve. This requires two movements:

  1. Horizontal movement of the bottom vertices
  2. Vertical movement of the top vertex and the bottom control point

The first movement controls the twist i.e., moves the bottom right vertex to the left and vice versa. The second collapses the curve into a straight line.

5. Combining the Movements

By synchronizing the two movements we achieve the desired result. The curve appears as the bar twists, and straightens as the bar untwists.

Edge curve combined motion

And then run the whole thing in reverse.

Here’s a handy little trick to get the animation to alternate its direction.

const sketch = () => {
  // The draw loop
  return (props) => {
    const { context, width, height, playhead } = props;

    const pingPongPlayhead = Math.sin(playhead * Math.PI);
    // ...
  };
};

Canvas-sketch gives you a playhead property. It goes from 0 to 1. By passing it through the sine function, you can make it oscillate from 0 to 1 and back to 0 in the same time duration. Use this pingPongPlayhead value to drive the two movements above.

BTW I’m only describing the geometry of the left edge here. You can draw the right edge as a mirror image.

6. Adding a Thick EdgeDouble curveThe double curves join to create a thick edge

Let’s go beyond that flat 2D rectangle. You can add the 3D perspective by making the edge appear thicker. To do so, draw it twice with a slight offset. The start point is same for both the curves. It’s only the end point that is different. Therefore, when you join the two curves you get this triangular-ish strip. The larger the offset the thicker the edge will appear.

7. Combine the Edge and FacesFace and edgeThe double curves join to create a thick edge

I broke down the animation into two layers: the flat faces and the edge. By combining the two we get this perception of a block of some thickness twisting around.

To be honest, I thought I was done, but there’s an issue. Observe the edge. At first, you don’t see the edge, then slowly, the edge appears and then it disappears again as the block twists entirely. That isn’t correct. The edge should remain visible in that fully twisted state. However, it does being to taper. Let’s fix that.

8. Correcting PerspectiveThe double curves join to create a thick edge

Let’s go back to that control point calculation. Notice the lerpFrames here. I added that back in step 4, to animate the bottom control point.

function edgeCurve([x1, y1], [x2, y2], playhead) {
  const K1 = 0.37;
  const K2 = lerpFrames([0, 0, 0.37], playhead);

  const cp1 = [x1, y1 + K1 * (y2 - y1)];
  const cp2 = [x2, y2 - K2 * (y2 - y1)];

  return [...cp1, ...cp2, x2, y2];
}

Next, we’ll add a variation to this. When the perspective flag is enabled the control point will extend a bit further. Draw one curve with this variation and one without to get that tapered look.

function edgeCurve([x1, y1], [x2, y2], playhead, perspective) {
  const K1 = 0.37;
  const K2 = perspective
    ? lerpFrames([0, 0, 0.6], playhead)
    : lerpFrames([0, 0, 0.37], playhead);

  const cp1 = [x1, y1 + K1 * (y2 - y1)];
  const cp2 = [x2, y2 - K2 * (y2 - y1)];

  return [...cp1, ...cp2, x2, y2];
}
9. Colouring FacesIntersectionsEdge curve split

Almost there. So far we’ve drawn the twisty rectangle and added an edge to it. The last bit is to render different colours for the front and back faces. Beidge for the front and blue for the back.

As the rectangle twists, the two sides intersect. The shape formed by the top two vertices and the point of intersection is the front-face. And the rest is the backface. First up, we need to find the point of intersection.

Remember the two sides are bezier curves. I used Pomax’s fantastic bezierjs library. It has an .intersects function built in. If the curves aren’t intersecting then simply draw the front face. Otherwise, render the two parts with the appropriate colour.

10. Putting It All Together

In total there are three parts to the final image:

  1. The twisty rectangle
  2. Which gets replaced by the split faces
  3. And finally the edge that layers on top at all times
Edge curve split

Combine them, and success!

I love creating these 2D drawings of 3D shapes. With 3D engines you define 3D geometry. The 2D approach is a lot like sketching. You create curves and then morph them as the image animates.

Checkout the full source on Github.

1. Getting Started

Our starting point in the original image. This is an ideal scenario. The image essentially gives you keyframes for what the animation should look like. Usually, we’re not that lucky. To figure out what to animate requires a bit more imagination.

Torsions by Walter Leblanc

So, what exactly is going on here? I see a block of some thickness. The top edge remains fixed while the bottom twists back and forth. As the block twists, the edge warps and creates the tapered look you see on the far right.

All in all, I see three parts: the beige front face, the blue back face and the front-facing edge.

2. Simplified Case — No Thickness

Let’s reduce this down to the simplest scenario, a flat 2D rectangle that is twisting around. As with hand-drawing, learning to draw with code requires you to observe an object and describe it in terms of lines and curves.

Edge curve animated

I see a rectangle with the bottom two vertices oscillating left and right. And the two vertical edges go from a straight line to a curve and then back again.

What is that curve?

3. Edge Curve

To me this looks a like a cosine wave. There is no cosine path API for Canvas, however, you can draw it using the bezier path API.

Edge curve

A bezier curve requires four points: start, end and two control points. This should be quite familiar if you’ve ever worked with vector paths in Illustrator, Sketch or Figma. Now, we know the start and endpoints. They are two vertices of the rectangle.

So, what about the control points? A quick google took me to this Stackoverflow answer. Here’s how you can calculate the two control points:

// Where (x1, y1) is the start and (x2, y2) the end points
const K = 0.37;

const cp1 = [x1, y1 + K * (y2 - y1)];
const cp2 = [x2, y2 - K * (y2 - y1)];

I’ve actually used this technique in a previous post, chillwave. Check it out for a full deep dive.

4. Straight Edge to Curve and Back AgainEdge curve control pointEdge curve control point

The edge oscillates between a straight line and a curve. This requires two movements:

  1. Horizontal movement of the bottom vertices
  2. Vertical movement of the top vertex and the bottom control point

The first movement controls the twist i.e., moves the bottom right vertex to the left and vice versa. The second collapses the curve into a straight line.

5. Combining the Movements

By synchronizing the two movements we achieve the desired result. The curve appears as the bar twists, and straightens as the bar untwists.

Edge curve combined motion

And then run the whole thing in reverse.

Here’s a handy little trick to get the animation to alternate its direction.

const sketch = () => {
  // The draw loop
  return (props) => {
    const { context, width, height, playhead } = props;

    const pingPongPlayhead = Math.sin(playhead * Math.PI);
    // ...
  };
};

Canvas-sketch gives you a playhead property. It goes from 0 to 1. By passing it through the sine function, you can make it oscillate from 0 to 1 and back to 0 in the same time duration. Use this pingPongPlayhead value to drive the two movements above.

BTW I’m only describing the geometry of the left edge here. You can draw the right edge as a mirror image.

6. Adding a Thick EdgeDouble curveThe double curves join to create a thick edge

Let’s go beyond that flat 2D rectangle. You can add the 3D perspective by making the edge appear thicker. To do so, draw it twice with a slight offset. The start point is same for both the curves. It’s only the end point that is different. Therefore, when you join the two curves you get this triangular-ish strip. The larger the offset the thicker the edge will appear.

7. Combine the Edge and FacesFace and edgeThe double curves join to create a thick edge

I broke down the animation into two layers: the flat faces and the edge. By combining the two we get this perception of a block of some thickness twisting around.

To be honest, I thought I was done, but there’s an issue. Observe the edge. At first, you don’t see the edge, then slowly, the edge appears and then it disappears again as the block twists entirely. That isn’t correct. The edge should remain visible in that fully twisted state. However, it does being to taper. Let’s fix that.

8. Correcting PerspectiveThe double curves join to create a thick edge

Let’s go back to that control point calculation. Notice the lerpFrames here. I added that back in step 4, to animate the bottom control point.

function edgeCurve([x1, y1], [x2, y2], playhead) {
  const K1 = 0.37;
  const K2 = lerpFrames([0, 0, 0.37], playhead);

  const cp1 = [x1, y1 + K1 * (y2 - y1)];
  const cp2 = [x2, y2 - K2 * (y2 - y1)];

  return [...cp1, ...cp2, x2, y2];
}

Next, we’ll add a variation to this. When the perspective flag is enabled the control point will extend a bit further. Draw one curve with this variation and one without to get that tapered look.

function edgeCurve([x1, y1], [x2, y2], playhead, perspective) {
  const K1 = 0.37;
  const K2 = perspective
    ? lerpFrames([0, 0, 0.6], playhead)
    : lerpFrames([0, 0, 0.37], playhead);

  const cp1 = [x1, y1 + K1 * (y2 - y1)];
  const cp2 = [x2, y2 - K2 * (y2 - y1)];

  return [...cp1, ...cp2, x2, y2];
}
9. Colouring FacesIntersectionsEdge curve split

Almost there. So far we’ve drawn the twisty rectangle and added an edge to it. The last bit is to render different colours for the front and back faces. Beidge for the front and blue for the back.

As the rectangle twists, the two sides intersect. The shape formed by the top two vertices and the point of intersection is the front-face. And the rest is the backface. First up, we need to find the point of intersection.

Remember the two sides are bezier curves. I used Pomax’s fantastic bezierjs library. It has an .intersects function built in. If the curves aren’t intersecting then simply draw the front face. Otherwise, render the two parts with the appropriate colour.

10. Putting It All Together

In total there are three parts to the final image:

  1. The twisty rectangle
  2. Which gets replaced by the split faces
  3. And finally the edge that layers on top at all times
Edge curve split

Combine them, and success!

I love creating these 2D drawings of 3D shapes. With 3D engines you define 3D geometry. The 2D approach is a lot like sketching. You create curves and then morph them as the image animates.

Checkout the full source on Github.

Now, your turn

Find an image and create an animation for yourself. Looking for inspiration? Pinterest, Tumblr, Dribbble and Artsy are my go to places.

Also, I would highly recommend this MDN guide on paths. Paths are going to be the foundation of any 2D piece. While this guide focuses on SVGs, the Canvas API is nearly identical.

And if you create something, I would love to see it. Give me a shout on Twitter.

https://varun.ca/torsions/
React Icon System
Icons play a crucial role in interface design. They can certainly be used as visual embellishments, but they are quite often able to convey…
Show full content

Icons play a crucial role in interface design. They can certainly be used as visual embellishments, but they are quite often able to convey their meaning without additional text, making them a handy tool for designers & developers. There are many different ways to build icon systems. In the past, I have written about a sprite based technique. Since then, tooling has matured and there are better approaches. This article will show you how to set up an icon system using SVGR — a tool for transforming SVGs into React components.

Prepare the SVG Files

Our starting point will be SVG files – one per icon. You will likely use design tools like Figma, Illustrator or Sketch to create these.

When designing these icons, consider using a consistent artboard size. This ensures that all icons follow the same layout rules and can be used interchangeably. You should also consider adding a bit of padding to your artboard to keep the icon content visually centred.

Artboard size, live area and padding
Generating the Icon Components

SVGR converts SVG files into React components. It is available as a Node library, a CLI tool and a webpack plugin.

Create React App comes pre-configured with SVGR. You can import an SVG file and use it as a component. This is a great start. It reduces the effort required to use SVGs with React.

import { ReactComponent as Logo } from './logo.svg';
function App() {
  return (
    <div>
      {/* Logo is an actual React component */}
      <Logo />
    </div>
  );
}

By using the SVGR CLI, you can customize the component generation and improve your workflow further. You can provide a custom template for component generation and even transform the SVG itself. To start, install the CLI using:

$ npm install @svgr/cli --save-dev

To create an icon, run:

$ npx svgr --icon --replace-attr-values "#000=currentColor" my-icon.svg

Notice, the --icon flag. It performs a couple of important tasks for us:

  1. It sets the width and height values to 1em to make the SVG scale with the inherited font-size.
  2. It preserves viewBox to ensure that the SVG scales with the correct aspect ratio.

The --replace-attr-values "#000=currentColor" flag replaces the chosen color with currentColor, allowing you to control the icon color using the font-color CSS property.

Behind the scenes, SVGR also uses SVGO to optimize the SVG file before converting it into a component. This is a sample of what you can expect the output to look like:

MyIcon.js
import * as React from 'react';

function SvgMyIcon(props) {
  return (
    <svg
      width="1em"
      height="1em"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
    </svg>
  );
}

export default SvgMyIcon;

To transform an entire directory of icons, use:

$ npx svgr --icon --replace-attr-values "#000=currentColor" -d icons icons

I generally treat these generated icon components as build artifacts. All the SVG files live in an icon directory, and the .js files within that directory are ignored by git. Then use an npm task to generate the icon components at build time.

package.json
{
  ...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "prebuild": "npm run icons",    "test": "react-scripts test",
    "eject": "react-scripts eject"
    "icons": "svgr --icon --replace-attr-values '#000=currentColor' -d src/icons src/icons"  }
}
Customizing the Icon Component

You can provide a custom template to modify the generated component code. The template below creates an icon component that leverages styled-components to control its styling.

icon-template.js
function template(
  { template },
  opts,
  { imports, componentName, props, jsx, exports }
) {
  const styledComponentName = 'Styled' + componentName;

  return template.ast`
    ${imports}
    import styled from 'styled-components';

    const SVG = (${props}) => ${jsx};

    const ${componentName} = styled(SVG)\`
      display: \${(props) => (props.block ? 'block' : 'inline-block')};
      font-size: \${(props) => (props.fontSize ? props.fontSize : '16px')};
      color: \${(props) => (props.color ? props.color : '#ccc')};
      vertical-align: middle;
      shape-rendering: inherit;
      transform: translate3d(0, 0, 0);
    \`;

    export default ${componentName};
  `;
}

module.exports = template;

For the Radius design system, we are using TypeScript, styled-components and styled-system. Our custom template generated icons that are correctly typed and appropriately connected to our design tokens.

Compound Icons Component

We can further simplify the icons’ usage by combining all the generated icons into one compound Icons component.

import { Icons } from 'ds';

export const App = () => (
  <>
    <Icons.Airplay aria-title="airplay the video" />
    <Icons.AlertCircle aria-title="error" />
  </>
);

SVGR allows us to specify a custom index template. This template is used to generate the index.js file when transforming a directory of SVGs. The following template generates a compound component.

icon-index-template.js
const path = require('path');

function indexTemplate(files) {
  const compoundExportEntries = [];

  const importEntries = files.map(file => {
    const componentName = path.basename(file, path.extname(file));
    compoundExportEntries.push(componentName);

    return `import { default as ${componentName} } from './${componentName}';`;
  });

  return `${importEntries.join('\n')}

    export const Icons = {
      ${compoundExportEntries.join(',\n  ')}
    };
  `;
}

module.exports = indexTemplate;

It adds an import statement for all the components, generates a name for the component based on the file name and finally combines them all into the Icons object.

src/icons/index.js
import { default as Activity } from './Activity';
import { default as Airplay } from './Airplay';
import { default as AlertCircle } from './AlertCircle';
import { default as AlertOctagon } from './AlertOctagon';

export const Icons = {
  Activity,
  Airplay,
  AlertCircle,
  AlertOctagon,
};

And here is the final version of the npm task:

package.json
{
  "scripts": {
    ...
    "icons": "svgr --icon --replace-attr-values '#000=currentColor' --template icon-template.js --index-template ./icon-index-template.js -d src/icons src/icons"
  }
}

SVGR is a fantastic tool. You can use it out of the box with Create React App. You can also customize it to better fit your workflow. The full code for this example is available here. For a more complex example, I recommend checking out the Radius source-code.

https://varun.ca/react-icon-system/
Figma Plugins
Figma plugins extend the core product's functionality and allow designers to enhance and automate their workflow. Building a plugin is…
Show full content

Figma plugins extend the core product’s functionality and allow designers to enhance and automate their workflow. Building a plugin is relatively approachable. The shape of a plugin is quite similar to that of a website. However, it is a significant jump to go from making your first plugin to writing one that tackles a more sophisticated user problem. In this post, I’m going to break down the architecture of Figma plugins and describe how to use figplug to reduce some of the tooling complexity.

If you can code a webpage with basic HTML and JavaScript, you can build a Figma plugin.

That’s the motto that the Figma team adopted when designing their plugin API. I’m focusing on designers here. Instead of relying on engineers, I want you to be able to build plugins for yourself. I’m going to assume familiarity with HTML, CSS and JavaScript. However, I will try my best to explain the modern JavaScript ecosystem and the various tools you are likely to encounter when writing plugins.

If you have never created a plugin, start with this tutorial first.

What’s in a Plugin?

A website is an HTML page that runs in the browser. You can use CSS to control the visual styling of the page and JavaScript to handle user interactions.

Similarly, a Figma plugin is a little website that runs inside Figma… kind of. The plugin has two parts:

  • Main Plugin Code — JavaScript code that can interact with the Figma file, modify its contents or generate new content.
  • Optional UI — an iframe that renders the UI associated with your plugin, inputs, sliders, buttons, etc.

The diagram below visualizes a Figma plugin, click to step through the explanation.

The Shape of a Plugin

Plugins fall into two categories:

  • Run Once — doesn’t require the user to interact with the plugin after it runs once.
  • With UI & browser APIs — allows users to interact with your plugin and provides access to the full set of browser APIs like <canvas> and network.

Run once plugins require a manifest and plugin file, while With UI plugins require an additional ui.html file

Where:

  • manifest.json — metadata that describes your plugin. Its name, version of the plugin API you want to use, what file has the main plugin code and what file has the UI code.
  • plugin.js — the main Javascript code of your plugin.
  • ui.html (optional) — the UI associated with your plugin. You can write inline CSS and JavaScript or load it from other .css and .js files.
Example

sample output of the confetting plugin

Throughout this post, I’ll build out the Confetti plugin. It draws a bunch of confetti in a selected frame. The manifest file looks something like this:

manifest.json
{
  "name": "Confetti",
  "id": "737805260747778092",
  "api": "1.0.0",
  "main": "plugin.js"
}

The ID property will be provided to you by Figma, through the Create new Plugin feature.

plugin.js
// Draw the confetti
createConfetti();

// Make sure to close the plugin when you're done. Otherwise the plugin will
// keep running, which shows the cancel button at the bottom of the screen.
figma.closePlugin();

function createConfetti() {
  // Make sure the user has only selected one node
  if (figma.currentPage.selection.length !== 1) {
    console.error('error: select a frame to render into');
  }

  const frameNode = figma.currentPage.selection[0];

  // Ensure that the selected node is a frame
  if (frameNode.type !== 'FRAME') {
    console.error('error: select a frame to render into');
  }

  const colors = [
    { r: 0.094, g: 0.627, b: 0.984 },
    { r: 0.482, g: 0.38, b: 1 },
    { r: 1, g: 0, b: 1 },
    { r: 0.105, g: 0.768, b: 0.49 },
    { r: 0.949, g: 0.282, b: 0.133 },
    { r: 1, g: 0.921, b: 0 },
  ];

  const width = frameNode.width;
  const height = frameNode.height;

  const randomRange = (low, high) => Math.floor(Math.random() * high) + low;
  const randomColor = () => colors[Math.floor(Math.random() * colors.length)];

  for (let index = 0; index < 50; index++) {
    // Create a rectangle
    const rect = figma.createRectangle();

    // Assign a random width and height to the rectangle
    const w = randomRange(width * 0.01, width * 0.05);
    const h = randomRange(height * 0.01, height * 0.05);
    rect.resize(w, h);

    // Randomly position the rectangle within the frame
    rect.x = randomRange(0, width);
    rect.y = randomRange(0, height);

    // Set a random color
    rect.fills = [
      {
        type: 'SOLID',
        color: randomColor(),
      },
    ];

    // Add the rectangle to the frame
    frameNode.appendChild(rect);
  }
}

The above plugin allows the user to select a frame and render 50 rectangles within it, with random size, location and colour. It picks from a list of six predefined colours. What if you wanted to allow the user to define a colour palette or control the number of rectangles? For that, we would need to add in the plugin UI.

Creating a User Interface

Create an HTML file containing the markup for your UI and then modify the manifest file to add the UI property. Notice the postMessage here. When the user clicks on the button, we grab the rectangle count and send a message to the main plugin code.

ui.html
<p>
  <label>Number of rectangles:</label>
  <input id="number" value="50" />
</p>

<p>
  <button id="draw">Draw Confetti</button>
</p>

<script>
  document.querySelector('#draw').onclick = () => {
    const count = document.querySelector('#number').value;

    parent.postMessage(      { pluginMessage: { type: 'create-confetti', count: Number(count) } },      '*'    );  };
</script>
manifest.json
{
  "name": "Confetti",
  "id": "737805260747778092",
  "api": "1.0.0",
  "main": "plugin.js",
  "ui": "ui.html"}

We need to make a few modifications to the plugin code too. We start by telling Figma to show the UI. Then we register a listener for messages. This listener is going to receive all messages from the UI. If the right type of message comes along, then we draw the confetti and close the plugin.

plugin.js
figma.showUI(__html__);
figma.ui.onmessage = msg => {  if (msg.type === 'create-confetti' && typeof msg.count == 'number') {
    createConfetti(msg.count);  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  figma.closePlugin();};

function createConfetti(count) {
  // Make sure the user has only selected one node
  if (figma.currentPage.selection.length !== 1) {
    console.error('error: select a frame to render into');
  }

  const frameNode = figma.currentPage.selection[0];

  // Ensure that the selected node is a frame
  if (frameNode.type !== 'FRAME') {
    console.error('error: select a frame to render into');
  }

  const colors = [
    { r: 0.094, g: 0.627, b: 0.984 },
    { r: 0.482, g: 0.38, b: 1 },
    { r: 1, g: 0, b: 1 },
    { r: 0.105, g: 0.768, b: 0.49 },
    { r: 0.949, g: 0.282, b: 0.133 },
    { r: 1, g: 0.921, b: 0 },
  ];

  const width = frameNode.width;
  const height = frameNode.height;

  const randomRange = (low, high) => Math.floor(Math.random() * high) + low;
  const randomColor = () => colors[Math.floor(Math.random() * colors.length)];

  for (let index = 0; index < count; index++) {
    // Create a rectangle
    const rect = figma.createRectangle();

    // Assign a random width and height to the rectangle
    const w = randomRange(width * 0.01, width * 0.05);
    const h = randomRange(height * 0.01, height * 0.05);
    rect.resize(w, h);

    // Randomly position the rectangle within the frame
    rect.x = randomRange(0, width);
    rect.y = randomRange(0, height);

    // Set a random color
    rect.fills = [
      {
        type: 'SOLID',
        color: randomColor(),
      },
    ];

    // Add the rectangle to the frame
    frameNode.appendChild(rect);
  }
}
Displaying Errors

So far, we have seen how to send messages from the iframe to the sandbox. This allows us to get information from the user or other sources (via network requests) and send that to our plugin. There are also going to be scenarios where you want to send information from the sandbox to the iframe. For example, to display errors encountered by the plugin.

In the plugin code, replace console.error with postMessage to dispatch errors to the UI.

plugin.js
figma.showUI(__html__);

figma.ui.onmessage = msg => {
  let status;

  if (msg.type === 'create-confetti' && typeof msg.count == 'number') {
    status = createConfetti(msg.count);
  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  if (status !== 'error') {
    figma.closePlugin();
  }
};

function createConfetti(count) {
  // Make sure the user has only selected one node
  if (figma.currentPage.selection.length !== 1) {
    figma.ui.postMessage({      type: 'error',      value: 'select a frame to render into',    });
    return 'error';
  }

  const frameNode = figma.currentPage.selection[0];

  // Ensure that the selected node is a frame
  if (frameNode.type !== 'FRAME') {
    figma.ui.postMessage({      type: 'error',      value: 'select a frame to render into',    });
    return 'error';
  }

  const colors = [
    { r: 0.094, g: 0.627, b: 0.984 },
    { r: 0.482, g: 0.38, b: 1 },
    { r: 1, g: 0, b: 1 },
    { r: 0.105, g: 0.768, b: 0.49 },
    { r: 0.949, g: 0.282, b: 0.133 },
    { r: 1, g: 0.921, b: 0 },
  ];

  const width = frameNode.width;
  const height = frameNode.height;

  const randomRange = (low, high) => Math.floor(Math.random() * high) + low;
  const randomColor = () => colors[Math.floor(Math.random() * colors.length)];

  for (let index = 0; index < count; index++) {
    // Create a rectangle
    const rect = figma.createRectangle();

    // Assign a random width and height to the rectangle
    const w = randomRange(width * 0.01, width * 0.05);
    const h = randomRange(height * 0.01, height * 0.05);
    rect.resize(w, h);

    // Randomly position the rectangle within the frame
    rect.x = randomRange(0, width);
    rect.y = randomRange(0, height);

    // Set a random color
    rect.fills = [
      {
        type: 'SOLID',
        color: randomColor(),
      },
    ];

    // Add the rectangle to the frame
    frameNode.appendChild(rect);
  }

  return 'success';
}

In the UI code, add a listener for the messages. The showError function then displays the error to the user.

ui.html
<p>
  <label>Number of rectangles:</label>
  <input id="number" value="50" />
</p>

<b id="errors"></b>

<p>
  <button id="draw">Draw Confetti</button>
</p>

<script>
  document.querySelector('#draw').onclick = () => {
    const count = document.querySelector('#number').value;

    parent.postMessage(
      { pluginMessage: { type: 'create-confetti', count: Number(count) } },
      '*'
    );
  };

  function showError(errorMessage) {    document.querySelector('#errors').textContent = 'error:' + errorMessage;  }  window.onmessage = event => {    const message = event.data.pluginMessage;    if (message.type === 'error') {      showError(message.value);    }  };</script>
Accelerate the Development Process

At this point, the UI looks pretty raw, just default browser styling. Unfortunately, Figma doesn’t offer any out of the box solution. There is no official component library to make your plugin UI match that of Figma. There are however, some great open-source options like figma-plugin-ds, figma-plugin-ds-svelte, figma-ui-components and create-figma-plugin.

the plugin ui has default browser styling

As soon as you introduce UI, the complexity level of building a plugin ramps up pretty quickly — forms, error handling and styling the UI. You could stick with vanilla HTML, CSS and JS. A lot of people prefer to use frameworks such as React, Vue or Svelte to make their life easier. For example, you probably don’t want to implement your own colour picker. You could use the colour picker built into the browser or something fancier like react-color.

Figma also recommends using TypeScript to write your plugin. This is not mandatory. You can use vanilla JavaScript or any other language which can be translated to JavaScript. Using TypeScript does give you a better editing experience.

The modern front-end development ecosystem is built to handle such complexity. We use NPM to install code written by others, code like chroma.js, Vue, figma-ui-components, react-color, etc. Tools like Webpack or Rollup package up your code and other’s code into one file. Even translate TypeScript code into JavaScript. It can be quite overwhelming to set up all these tools so, most frameworks offer tools to create a new application. You might have come across Create React App or the Vue CLI. They generate boilerplate code for you. That way, you only need to focus on your code and the boilerplate handles the rest.

figplug

For Figma plugins, I like to use figplug. It handles TypeScript, React/JSX, asset bundling, plugin manifest generation, etc. There are some other really great alternatives too. Fire up your terminal and use the following commands to install figplug and initialize your first plugin.

# Install figplug
npm install -g figplug
# create a plugin
figplug init -ui confetti
# build a plugin
figplug build -w confetti
# Your plugin is now available in "confetti/build".
# -w makes figbuild watch your source files for changes
# and rebuild your plugin automatically.

Here’s the full source-code for the confetti plugin, using figplug. You’ll notice a few modifications to account for TypeScript. Also, the UI code is split into an HTML file and a TS file. Figplug builds and packages it all up for you.

To use React, initialize the plugin with -react flag. Here’s the full source-code for the React & figma-plugin-ds version of the plugin. If you are new to React, I highly recommend React for Web Designers. The ID property will be assigned to you by Figma.

# Install figplug
npm install -g figplug
# create a plugin
figplug init -react confetti# build a plugin
figplug build -w confetti
Multi-Command Plugins

The context menu displays a list of all plugins, and their sub-commands as a nested menu

You can break up various features of your plugin into multiple commands. These commands are available to the user via the context menu and defined in the manifest file.

manifest.json
{
  "api": "1.0.0",
  "name": "Pack",
  "id": "843186203578433973",
  "main": "plugin.ts",
  "menu": [    {      "name": "Circle",      "command": "circle"    },    {      "name": "Square",      "command": "square"    }  ]}

Let’s look at an example, Pack. It is a plugin that fills a frame using the Circle packing effect. The user can choose between two different fill shapes.

Circles and squares packed into a rectangle

In the plugin code, use the figma.command property to determine which command the user selected. Check out the full source code on github.

plugin.ts
import pack from 'pack-spheres';

drawPack();
figma.closePlugin();

function drawPack() {
  ...

  const shapes = {
    circle: drawCircle,
    square: drawSquare,
  };

  for (let circle of circles) {
    const node = shapes[figma.command](circle);    frameNode.appendChild(node);
  }
}

function drawCircle(circle) {
  const node = figma.createEllipse();

  node.x = circle.position[0] - circle.radius;
  node.y = circle.position[1] - circle.radius;
  node.resize(circle.radius * 2, circle.radius * 2);

  node.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.2, b: 0.2 } }];

  return node;
}

function drawSquare(circle) {
  const node = figma.createRectangle();

  const radius = circle.radius * (2 ** 0.5) / 2;

  node.x = circle.position[0] - radius;
  node.y = circle.position[1] - radius;
  node.resize(radius * 2, radius * 2);

  node.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.2, b: 0.2 } }];

  return node;
}
Relaunch Buttons

Let’s say the user is not happy with the result of the packing effect, and they want to regenerate it. They could delete the content of the frame and execute the plugin again. Alternatively, we can give them a shortcut using the Relaunch Button feature. You can specify these shortcut commands under the relaunchButtons property of the manifest.

manifest.json
{
  "api": "1.0.0",
  "name": "Pack",
  "id": "843186203578433973",
  "main": "plugin.ts",
  "menu": [
    {
      "name": "Circle",
      "command": "circle"
    },
    {
      "name": "Square",
      "command": "square"
    }
  ],
  "relaunchButtons": [    {      "command": "regenerate",      "name": "Regenerate"    }  ]}

In the plugin code, you can attach the command to any node of your document. For example, the following will attach the regenerate command to the selected frame. This relaunch command is available to the user under the plugins sections of the right sidebar.

frameNode.setRelaunchData({ regenerate: 'regenerate the packing effect' });

The frame has a regenerate command attached to it

The execution is handled much like other commands. You can check the value of figma.command and perform the associated operation.

One of my biggest challenges with learning a new skill is not having a mental model of how it works. It gets incredibly frustrating because you are not even sure what to search for. I know this was a lot of information. Hopefully, it has helped you develop your mental model of how Figma plugins work and what to search for when you are stuck.

https://varun.ca/figma-plugins/
Styled System Revisited
Much has changed in the world of Styled System since my last post . There are fewer packages to deal with; it is much more performant and…
Show full content

Much has changed in the world of Styled System since my last post. There are fewer packages to deal with; it is much more performant and has a more straightforward and powerful API. I recently helped build Rangle’s open-source design system kit, Radius. We used Styled System, and it was a great experience. It remains my preferred tool for building design systems. However, I did have to update my understanding of the tool and adopt newer architectural patterns.

The Design Graph

The foundation of the Styled System API is the Design Graph — a constraint-based system for organizing styles in UI design. It consists of four parts: scales, theme, variants and components.

Scales

Design Tokens is a familiar term by now. They define the visual characteristics of a brand or a product such as typography, colours, icons, and spacing. They make it easier to maintain a scalable, consistent system for UI development.

Design tokens can have a broad set of responsibilities. They span both design tools & code and can support multiple render targets such as the web, iOS, Android and even embedded devices. You can think of scales as a subset of design tokens that map to specific CSS style properties. For example, typography tokens can map to font-size, font-weight and line-height scales.

Theme

The theme is a collection of all the scales and is maintained in a theme.ts file. The shape of this theme object conforms to the System UI Theme Specification.

You might have multiple versions of the theme. For example, to switch between different brands or perhaps colour modes (dark & light). Radius is excellent real-world example.

Components

Components allow you to split the UI into independent and reusable pieces. Styled System provides functions to connect component styles to the scales.

// styled-components is a CSS-in-JS Library
import styled from 'styled-components';

// styled-system provides functions that add props to React components
// which allow you to control styles based on design tokens.
import { space, layout, color } from 'styled-system';

// The Box component is now connected to Space and Color tokens
export const Box = styled.div(space, color);

Now, this component will have style props available to set foreground color, background color, margin and padding values.

/**
 * Color maps to theme.colors.textEditorial
 * background color maps to theme.colors.background[1]
 * Padding maps to theme.space[3]
 */
<Box color="textEditorial" bg="bg.primary" p={3}>
  Tomato
</Box>
Variants

There are situations where you might want to support slight stylistic variations of components. For example, a button might have primary, secondary and transparent variants.

Styled System variants allow you to apply a set of visual styles to a component using a single prop.

Let’s consider typography variants. In Figma, you define reusable typography as text styles. A text style has predefined values for font-family, size, weight and line-height.

An example of text styles defined in Figma

In code, use the variant function to define typography variants.

text.ts
const textVariants = variant({
  variants: {
    body: {
      fontFamily: 'body',
      fontWeight: 'regular',
      lineHeight: 'copy',
      fontSize: 2,
    },
    caption: {
      fontFamily: 'body',
      fontWeight: 'medium',
      lineHeight: 'copy',
      fontSize: 2,
    },
    label: {
      fontFamily: 'heading',
      fontWeight: 'regular',
      lineHeight: 'solid',
      fontSize: 1,
    },
  },
});

export const Text = styled.p`
  ${compose(
    space,
    color,
    typography
  )}
  ${textVariants}
`;

These variants map back to the scales.

theme.ts
{
  fonts: {
    body: '"Helvetica Neue", sans-serif',
    heading: '"Roboto", sans-serif',
  },
  fontWeights: {
    regular: 400,
    medium: 500,
    bold: 700,
  },
  lineHeights: {
    solid: 1,
    title: 1.25,
    copy: 1.5,
  },
  fontSizes: [12, 14, 16, 20, 24, 32, 48]
}

The Text component can now use the variant prop to switch between lead, body and label styles.

<Text variant="body" />
<Text variant="caption" />
<Text variant="label" as="span" />
Example

Let’s look at a complete example. The Field component here is a composition of the Input, Label and Text components, which, in turn, use a composition of scales and variants. The Hint and Error texts are the Text component with the variant set to hint and different colour values.

export const Field = () => (
  <Box>
    <Label htmlFor="my-input" required>
      Label text
    </Label>
    <Input
      placeholder="Placeholder Text"
      id="my-input"
      aria-describedby="helper-text error-text"
    />
    <Text variant="hint" color="text.secondary" id="helper-text">
      Hint text
    </Text>
    <Text variant="hint" color="text.error" id="error-text">
      Error text
    </Text>
  </Box>
);
Component Hierarchy

Not all components are built the same way. A component might be created using styled-components or a combination of styled-components and Styled System or as a composition of other components. I recommend using the following hierarchy to reason as to what technique to use for creating components.

  • Elements are basic reusable building blocks of the system.
  • Patterns are reusable building blocks made up of elements or other patterns.
  • Features are a set of patterns, elements, & styles that come together to support a specific user task. Sometimes referred to as container components.
  • Layouts are how features come together to form a page.

The design system would generally be limited to Elements and Patterns. Ideally, you should define Features and Layouts in the application. There are some scenarios in which you might want to include recipes to demonstrate how one might build a particular Feature — for example, the form control recipes in Radius.

Note, this is a framework for guiding architectural choices. I do not recommend categorizing components in code. A component might start as an Element and evolve into a Pattern. Category based folder structure leads to unnecessary overhead and can hinder this natural evolution.

Forwarding Refs

Each component should return a single HTML element that accepts all HTML props, including className, style and accessibility attributes. Which means you need to consider ref forwarding.

Encapsulation is desirable for application-level components (Features), it can be inconvenient for highly reusable “leaf” components like FancyButton or MyTextInput. These components tend to be used throughout the application in a similar manner as a regular DOM button and input, and accessing their DOM nodes may be unavoidable for managing focus, selection, or animations.

Ref forwarding is an opt-in feature that lets some components take a ref they receive, and pass it further down (in other words, “forward” it) to a child.

Forwarding Refs

Elements

An element is a styled-component that uses functions from Styled System. Styled-components handles ref forwarding here. These are polymorphic components, i.e., keeping the styling same they allow you to render a different HTML tag or a different custom component.

const textVariants = variant({
  variants: {
    big: {
      fontSize: 4,
      lineHeight: 'heading',
    },
    small: {
      fontSize: 1,
      lineHeight: 'body',
    },
  },
});

export const Text = styled.p(
  textVariants,
  compose(
    space,
    color,
    layout,
    flexbox,
    border,
    position,
    typography
  )
);
Patterns

Patterns are comprised of elements. Therefore, we have to pick which component ref to forward.

import { StyledComponentProps } from 'styled-components';

type AspectRatioProps = StyledComponentProps<
  'div',
  any,
  {
    /** The aspect ratio to apply */
    ratio?: number;
  } & BoxProps,
  never
>;

export const AspectRatio = forwardRef<HTMLDivElement, AspectRatioProps>(
  ({ ratio = 16 / 9, children, ...props }, ref) => (
    <Box ref={ref} position="relative" overflow="hidden">
      <Box width="100%" height={0} pb={100 / ratio + '%'} />
      <Box {...props} position="absolute" top={0} right={0} bottom={0} left={0}>
        {children}
      </Box>
    </Box>
  )
);

Here StyledComponentProps is a utility to create types for a styled component. It merges the typings for style function props and the typings of the HTML element being forwarded.

Constraint-based Style Props has been a revolutionary idea. It allows us to truly reason about our UI as a system. For more real-world examples of components, check out the Radius source code.

https://varun.ca/styled-system-revisited/
Side-Loading a Design System
As you go from a smaller to a medium-sized team, the need to share knowledge becomes crucial. It is the perfect time to invest in a design…
Show full content

side loading design system

As you go from a smaller to a medium-sized team, the need to share knowledge becomes crucial. It is the perfect time to invest in a design system. However, you are also likely under delivery pressure. You are hoping to meet deadlines and roll out new features to ensure that your product is successful. In this blog post, I will share a few practices that will allow you to establish a design system that facilitates knowledge sharing without sacrificing delivery speed.

Small Team

Small teams tend to be highly aligned. They work together on a day-to-day basis. They have a clear sense of the product’s surface area. Everyone is aware of the architecture, what components are available, where to find them, and how to use them. Because the team works so closely, knowledge sharing happens organically.

Mid-sized Team

As the team grows, it becomes challenging to maintain a shared context. Domains begin to develop within the product and the codebase. Not everyone is aware of all the UI components or architectural decisions or even who made them. It leads to sub-teams branching off and deviating from brand standards or the established interaction model. The code complexity ramps up, and ultimately it introduces too much entropy into your development process.

Organically Introducing a Design System

Design systems are an excellent tool to scale shared understanding and control those deviations from occurring. The challenge is that you likely don’t have the time and resources to stop and build out a design system.

The chances are that you are using a JavaScript UI framework such as React, Angular or Vue to build your product. These frameworks predominantly use a component-based architecture, which means that you already design and build UI as isolated extensible slices. That is one major step towards having your design system. There are a few more steps you can take, which leverages this component-based approach to creating a design system organically:

  1. Storybook driven development
  2. Implement Design Tokens
  3. Start formalizing this knowledge with light-weight documentation
Storybook Driven Development

Storybook is a tool for developing self-contained components and document use cases as stories. Stories are a combination of visual, interaction and structural testing. It supports most JS frameworks and integrates well with existing codebases.

By enabling Storybook, your team gets a quick way to surface all the components within their codebase. Storybook becomes the place where they go to find components and figure out how to use them. It also makes it easier for them to see the impact of their changes. You can also integrate it into your automated QA pipeline.

Design Tokens

Design tokens capture low-level visual characteristics of your product. As projects scale, you find that teams start hard-coding values directly into CSS — hex codes or pixel values. By using design tokens, you can move those values up into a centralized theme. Making it easier to maintain a scalable and consistent visual system.

Most teams have a theming infrastructure already set up, using Sass or CSS custom properties or CSS-in-JS solutions. Design tokens leverage that theming support to connect low-level design decisions to components. Colours, typography and spacing are must-haves for design tokens. You can extend them to all kinds of visual attributes such as widths, heights, border styles, elevations and even structural and motion attributes.

Example of a set of design tokens for a component
Read more about Design Tokens beyond colors, typography, and spacing.
Light-Weight Documentation

The most significant impact of a design system is that it allows your team to focus on solving differentiating challenges instead of continually reinventing the wheel. To be able to figure out what problems you have solved already, you need to start documenting those challenges and solutions.

Design systems you see in the wild tend to have polished websites with thorough documentation. That can feel quite intimidating. It would be best if you recognized that that is the end state after many months and years of iteration. Light-weight tools make it easy for you to start documenting this information. Tools such as InVision DSM and Zero Height provide a highly approachable authoring experience. They support version control and allow you to plug in Storybook for live code examples. In effect, seeding your DesignOps practice and slowly implementing a governance model.

The net impact of adopting these practices is that your teams have visibility to design decisions and how they flow into the code. They have an easily searchable directory of components and a development environment where they can test them in isolation. And finally, a platform to connect to designers and developers and build shared understanding asynchronously.

https://varun.ca/side-loading-design-system/
Dark Mode
After several years of using Jekyll, I switched over my website to Gatsby. Jekyll worked great for most things. The part that I struggled…
Show full content

After several years of using Jekyll, I switched over my website to Gatsby. Jekyll worked great for most things. The part that I struggled with most was adding interactivity. The best I could do was embed CodePen blocks in markdown. Gatsby gives you all the benefits of static rendering and craft amazing interactive experiences with React. Josh Comeau’s Dynamic Bézier Curves post or Rodrigo Pombo’s scrollycoding are good examples of this.

There is much out there about migrating to Gatsby, the benefits and the trade-offs. I want to focus on one particular challenge of my migration — dark mode. Dark themes are certainly a bit of a trend, but it also happens to be a feature many users rely on on a day-to-day basis. Most devices support it at the OS level. The user preference even cascades down to the browser and can be captured in CSS via the prefers-color-scheme media query.

Colour Palette

The first step was to pick a colour palette. Inverting shades using filter: invert(100%) is a quick and somewhat hacky way to get to dark mode. You can follow Daniel Eden’s advice and pair it with hue-rotate(180deg) for a better result. It compensates for the hue inversion. However, there is a lot more to it. Inverting hues can lead to unintended consequences such as poor contrast or shifting visual importance of UI elements. We need to be a lot more intentional about the colour palette.

Call-out, unintentionally, ends up with higher visual importance in dark mode

Teresa Man shared extremely valuable advice in her post, How to design delightful dark themes. Here are some of the key learnings that I applied to my website:

  1. Avoid pure black or white. It can lead to eye fatigue and causes black smearing.
  2. Darken distant surfaces.
  3. Reduce large blocks of bright colour.

There are two categories in the colour palette: neutral scale, used for most things such as text and surface colours, and brand colours, used only used for highlights and accents.

neutral.0 - rgb(217, 215, 224)

neutral.1 - #ccc

neutral.2 - #aaa

neutral.3 - #777

neutral.4 - #555

neutral.5 - #333

neutral.6 - #1b191f

neutral.7 - rgb(19, 18, 23)

brand.main

brand.bright

brand.faded

Taking Teresa’s advice, the extreme ends of the neutral scale are a blue-ish black and a light gray. The background is set to neutral.7, and other surfaces such as code-blocks all are set to a lighter neutral.6.

Implementation

I was going for an end experience where my site supports two modes — dark and light. On the first load it defaults to the light mode. It then checks to see if the user prefers dark mode using prefers-color-scheme: dark media query. If yes, then switch to dark mode. There is also a button that the user can click to toggle modes. On subsequent loads the site needs to track their choice, default to it.

On the surface this seems like a failry straightforward feature set. However, I soon discovered that there was a lot of hidden complexity I wasn’t accounting for. I’ll go through those challenges in a bit but first, I want to give a major shout out to the Theme UI team. My solution is largely based on how Theme UI handles colour mode switching. This blog post could easily have been titled “How Theme UI handles colour mode switching with Gatsby”.

Theme Variants

I’m using Styled System paired with styled-components for the UI. styled-components provides theming support through its <ThemeProvider> component. I created the dark and light variants and choose the appropriate theme based on the active mode.

user toggling the colour modeTracking User choice

The useColorMode hook allows us to track the active colour mode. On the first load it checks to see if the user’s choice is stored in local storage or if the user has enabled dark mode at their OS level. When updating the selected mode there a couple of side-effects that need to happen:

  1. Set a class on the body element reflecting the active mode, for example, varun-ca-dark
  2. Save the active mode in local-storage
  3. Update state
use-color-mode.js
export const useColorMode = () => {
  const [mode, setMode] = useState('light');

  useEffect(() => {
    const stored = storage.get();
    const dark = getMediaQuery();
    if (!stored && dark) return setModeWithSideEffects('dark');
    if (!stored || stored === mode) return;
    setModeWithSideEffects(stored);
  }, []);

  const setModeWithSideEffects = () => {
    setMode(state => {
      const nextMode = state === 'light' ? 'dark' : 'light';
      document.body.classList.remove('varun-ca-' + state);

      document.body.classList.add('varun-ca-' + nextMode);
      storage.set(nextMode);

      return nextMode;
    });
  };

  return [mode, setModeWithSideEffects];
};

Check out use-color-mode.js for more context.

My site has a base layout that I use for all pages, the layout.js file. It essentially acts at the root of the application and the themeing set up is done there. The active mode is used to generate the appropriate theme object and passed into the ThemeProvider.

To allow the user to select the colour-mode manually, we need to connect the mode state and updater to the toggle button. React’s context API works perfectly for this. The ColorModeContext provides { mode, setColorMode } which is then used by the CycleMode button.

layout.js
const Layout = ({ title, description, image, children }) => {
  const [mode, setColorMode] = useColorMode();

  return (
    <ThemeProvider theme={{ ...theme, ...createColorStyles(mode) }}>
      <ColorModeContext.Provider value={{ mode, setColorMode }}>
        <>
          <SEO title={title} description={description} image={image} />
          <InitializeColorMode />
          <GlobalStyle />
          <MDXProvider components={dsToMdx}>{children}</MDXProvider>
        </>
      </ColorModeContext.Provider>
    </ThemeProvider>
  );
};
Avoiding Flash

Gatsby, at build time, uses a React server-side rendering to compile the site into files that can be delivered to a web browser. The output is HTML and a JavaScript runtime that takes over in the browser once the initial HTML has loaded.

the site colours flash once the JavaScript is loaded

The generated HTML and CSS use the default colour mode. When the user loads the page, it renders with that default colour mode. Once the JS loads, it updates the CSS to match the user’s preferred choice. This switch causes the page to flash between the two styles. Not a great user experience!

The best way to avoid the flash is to rely on CSS rather than JavaScript. The challenge here is that styled-components is a CSS-in-JS library and relies on the JS bundle to load and parse before the CSS can be appropriately updated. The Theme UI team came up with an innovative solution.

First, convert the colour modes from hard-coded hex values to CSS Custom Properties. Then generate global CSS that controls the value of these CSS Custom Properties using scoped class names, for example: .varun-ca-dark and .varun-ca-light (color-mode-styles.js).

{
  light: {
    brand: {
      main: '#4e4bec',
      bright: '#dedefb',
      faded: '#ececfd',
      selection: '#ececfd',
    },
    neutral: {
      0: '#333',
      1: '#555',
      ...
    },
  }
  dark: { ... },
}
{
  light: {
    brand: {
      main: "var(--varun-ca-colors-brand-main, #4e4bec)",
      bright: "var(--varun-ca-colors-brand-bright, #dedefb)",
      faded: "var(--varun-ca-colors-brand-faded, #ececfd)",
      selection: "var(--varun-ca-colors-brand-selection, #ececfd)"
    },
    neutral: {
      0: "var(--varun-ca-colors-neutral-0, #333)",
      1: "var(--varun-ca-colors-neutral-1, #555)",
      ...
    },
  }
  dark: { ... },
}
{
  "&.varun-ca-light": {
    "--varun-ca-colors-brand-main": "#4e4bec",
    "--varun-ca-colors-brand-bright": "#dedefb",
    "--varun-ca-colors-brand-faded": "#ececfd",
    "--varun-ca-colors-brand-selection": "#ececfd",
    "--varun-ca-colors-neutral-0": "#333",
    "--varun-ca-colors-neutral-1": "#555",
    ...
  }
  "&.varun-ca-dark": { ... },
}
Theme object is modified to use CSS custom properties and a set of global CSS is extracted to control the value of those custom properties

Lastly, inject a tiny script in your HTML to check local-storage for the user’s preference and set the appropriate class name to the body tag (use-color-mode.js).

use-color-mode.js
const STORAGE_KEY = 'varun-ca-color-mode';

export const InitializeColorMode = () => (
  <script
    key="varun-ca-no-flash"
    dangerouslySetInnerHTML={{
      __html: `(function() { try {
        var mode = localStorage.getItem('${STORAGE_KEY}');
        if (!mode) return
        document.body.classList.add('varun-ca-' + mode);
      } catch (e) {} })();`,
    }}
  />
);

We are still using Styled-Components for theming. However, the values point to CSS custom properties, and we can switch the theme by changing the class on the body element. Since all the variable values are in global CSS, and the above script runs as soon as the page loads, the user no longer sees a flash. The only time the user sees a flash is the first time they visit your site, and if they have enabled dark mode on their OS.

Gatsby does a tremendous amount of work to ensure that your site is performant. One of those things is to use React SSR to generate a static website. It, unfortunately, means that we have to do a bit more work to implement dark mode, but the trade-off was worth it, and I got to learn a lot. That said, if you use Theme UI, then you get all of this for free.

The one key takeaway from this whole process is that implementing dark-mode means that you are creating more work for your future self. Each feature you add to your site will have to support it.

https://varun.ca/dark-mode/
Why Not Both?
The web is defined by the interplay of various components: back-end technologies such as servers and databases to support the website itself…
Show full content

The web is defined by the interplay of various components: back-end technologies such as servers and databases to support the website itself, client-side technologies to present content and provide interactivity, and the network to deliver the website to the user.

While there are a lot of layers to this medium I want to focus on the presentation layer — HTML, CSS & JS. In this article I am going to be using the shorthand web to refer to this presentational layer. (I realize that is inaccurate but, I don’t have a better word for it.)

The Medium of the Web

Bret Victor in his talk Stop Drawing Dead Fish differentiates computers from other mediums such as drawing, photography or animation. In his opinion, what makes computers different from all these other mediums is the fact that you can create simulations and add interactivity. It is not just a drawing of a fish or an animated fish but, an interactive simulation.

Frank Chimero explored the medium of the web in his article The Web’s Grain. What makes the web truly different from anything else. What are its intrinsic properties? He summarizes it as “an edgeless surface of unknown proportions, comprised of small, individual, and variable elements from multiple vantages assembled into a readable whole that documents a moment.”

For me, the intrinsic qualities of the web are fluidity, responsiveness and dynamism. Or as Jen Simmons puts it much more eloquently:

  1. Mix fluid with fixed
  2. Four stages of squishiness
  3. Truly two-dimensional layouts
  4. Nested contexts
  5. Ways to contract and expand content
  6. Media queries, as needed
Design in the Medium

In the past couple of years, we’ve gone from being excited about CSS Grid Layout to using it in production. However, it seems that there is a bit of inertia when it comes to designers adopting it.

Jen Simmons often speaks about how everything about web design just changed. We are no longer limited to fixed or fluid column-based layouts. Instead, we can design truly two-dimensional layouts. But, do we actually have any tools that allow us to design that way?

When we design in Sketch or Figma, we are designing a snapshot in time and space. You have to create multiple snapshots to describe the responsive behaviour of one screen. Now imagine designing responsive two-dimensional layouts with multiple states and variants: quite tedious.

So what is the answer? Design in the medium. You need to be able to code to design in the medium. Arguably, this barrier to entry is too high. What if you could design in the browser without needing to code?

Tools like Framer, Modulz, Alva and Playroom are bridging this divide. Framer in particular is quite exciting. It allows you to import code components and mix them with design components. You can extend design components with code or even build a custom UI to interact with them.

Designers are equipped with tools that focus on communicating design aesthetic, and developer tools are more geared toward controlling behaviour and functionality.

— Alan B Smith, Component-Based Design

Fundamentally these are still design tools. They are visual code editors with the purpose of building living prototypes not the actual application itself. They don’t allow you to define behaviour and functionality. They are not meant to replace any development tools.

Why not both?

My hope is that we will get something like Unity for the web. Something that both developers and designers can use. A tool that allows us to define both aesthetic and functionality with an intuitive interface. That interface is going to be some combination of GUI and code editor.

Generative Design

The convergence of design and development tools will allow us to discover new and more innovative ways of working. Code is not the most expressive or performative of platforms. In my opinion, describing visual characteristics in code is tedious and not-intuitive. For example, designing an animation with the After Effects timeline panel is a much better experience than doing the same with CSS or a JS library. You can visualize the timeline, adjust curves and iterate much faster.

The Compositor team has been working on some truly interesting tools. Modifying the UI directly or dragging components to create layouts is again a much better developer experience. It also allows you to explore and iterate with very little friction.

🙀 pic.twitter.com/q0BqSsTRHi

— Compositor (@getcompositor) November 17, 2017

Making progress pic.twitter.com/IGrZWzqqbT

— Compositor (@getcompositor) November 12, 2017

Project Phoebe was a brief attempt at using mutative design for UI and Adobe seems to be exploring this space too. But, we are just scratching the surface. What would the UI design equivalent of Real Time Topology Optimization look like? Can we define a set of constraints, allow the UI to morph around it and iterate?

With generative design you can start leveraging the power of the medium. You don’t have to manually create all the possible layouts. You have the ingredients. You can define a recipe and explore all the possible permutations.

Generative design mimics nature’s evolutionary approach to design. Designers or engineers input design goals into generative design software, along with parameters such as materials, manufacturing methods, and cost constraints. The software explores all the possible permutations of a solution, quickly generating design alternatives. It tests and learns from each iteration what works and what doesn’t.

Autodesk Generative Design

Let’s focus on one category — layout. Much of web design is stuck in the mindset of fixed or fluid columns where content shifts around at breakpoints, almost completely ignoring the two-dimensional nature with the possibility of overlapping content.

Frank describes web design as “creating assemblages of elements, then associating them with the appropriate space”. I did an experiment very much inspired by that statement. Start by defining a high level fluid grid, say 6x4. Take a document and slice up the different sections into components, give them random grid sizing and drop them into the layout. The user can then lock items and continue regenerating the grid until they find a satisfying layout. You can try it yourself in this CodeSandbox.

Playing with the idea of procedurally generated layout grids.https://t.co/yAsPbkpDZJ pic.twitter.com/bHQIvlF9nC

— Varun Vachhar (@winkerVSbecks) February 13, 2019

Continuing with generative grid experiments. Took one of the @tachyons_css examples, sliced up the different sections into components and randomly sizing them in the container. You can then lock items and continue regenerating the grid until you find an interesting layout. pic.twitter.com/1cUSnz9VRN

— Varun Vachhar (@winkerVSbecks) March 1, 2019

The results were a lot better than I expected. It has been hard to break out of the twelve column mindset for me. This helped.

These are just a first steps. We could get people to rate these layouts on a scale of 1-10 and use that data to train a machine learning model which could then in-turn generate layouts for us. There is a lot more to explore in the world of generative design. I’m excited to see what else the web design and development community comes up with. Perhaps generative design will be the bridge between design and code?

https://varun.ca/why-not-both/
What is a Design System?
Defining what a Design System is and what it means for an organization can be tricky. In this post, we'll take the conversation of Design…
Show full content
This post was co-authored with Catherine Maritan and originally published on the Rangle.io Blog.

Defining what a Design System is and what it means for an organization can be tricky. In this post, we’ll take the conversation of Design Systems past style guides and component libraries and get into breaking down silos between development and design.

The Hand-Off Problem

From our experience, most organizations treat websites as a collection of pages. Pages provide a holistic view of the design problem — what content are we displaying, what is its purpose, how are we going to display this content and how is the user going to interact with it. This ideology is what drives the design and development process. Designers create designs for each page, followed by a hand-off to developers who then work with a product manager to divide the mock-ups into tickets for developers.

A lot of chaos is introduced when changes are made during the design & development process

The page-based approach, while popular, poses a lot of challenges. Applications are highly dynamic. The interface has to account for many different application states and responsive variants. It’s tedious to describe all of these states in static mock-ups. Also, as the development process begins the requirements often change. You discover new states which were previously not considered or there might be implementation challenges. Meanwhile, the designs continue to iterate. Due to the siloed nature of this approach, it makes it hard to resolve pressures coming from either side. Over time, the team starts to experience a number of symptoms:

Developers Become Blocked By Designers

As requirements change and we uncover new design considerations, designers will inevitably need to update the pages that they had previously designed. This leads to two scenarios: the developers are constantly blocked having to wait for design iterations or the development team forges ahead without designs and delivers inconsistent or off-brand user experiences.

Poor Product Decisions Are Made

Traditionally, designers have only been responsible for thinking about the holistic experience, leaving developers to break down that design into small, implementable chunks.

How the developer breaks-down that design has implications for how the design can be changed at a later date, or how new types of content can fit into what has already been built. A developer may or may not have the context necessary to make informed decisions about this structure, and the resulting product implementation may not be flexible enough for all business scenarios.

Similarly, if a designer is not aware of how a developer has implemented a design, they may make design decisions that cause huge headaches for developers, potentially causing a lot of unnecessary rework.

The Design Of The Site Becomes Inconsistent

We’ve all seen those websites that include 36 different button styles, and asked ourselves “how could anyone let this happen?”

It can happen quicker than you may think. When we are creating and iterating on products one page at the time, it is likely that the same UI element will be designed and implemented more than once. Not only does this mean that there might be inconsistencies from the get-go, but it also increases the chances that an instance of a button is missed when updates are made in the future.

There is a lot of wasted time re-solving the same problems Whenever there is a new request, a designer (oftentimes) will open a new file and starts the design process from scratch. For all the time solving the holistic problem at-hand, there is also just as much time defining all the nitty-gritty design details. What does this dropdown look like on hover? How about this button? These are time-consuming tasks.

Now, these detailed-design nitty-gritty questions are important but aren’t unique problems. Most likely, another designer on the team has already thought about it, or maybe it’s something that has already been designed on another page. Once that dropdown hover state has been defined once, the designer shouldn’t have to think about it or document it again.

Maintenance Becomes A Nightmare

Documenting a product one page-at-a-time isn’t exactly maintainable. This approach doesn’t allow designers to easily revisit previous design decisions. Suddenly, even the smallest change in design direction requires a designer to update in 12 design files, 6 hand-off documents, and 22 places in code. Inevitably, someone will forget to update a document, and these inconsistencies will leave developers scratching their head and asking for the “source of truth”.

As time goes on, and a product (and its documentation!) gets larger, the cost for feature changes gets larger, too. The design and tech debt accrue. Fast forward 3 years down the road, and now nobody wants to touch the product.

Component-based Design & Development

There was clearly the need for a better approach. Both designers and developers were looking for techniques to improve and optimize their workflow.

As a result, designers started adopting style guides — a set of standards for designing the product often. These would include typographic scales, color palette, and UI patterns, and was published as a static document. Design tools evolved into a symbol based model. This made it easier to maintain states and compose more complex interfaces.

Meanwhile, JavaScript UI frameworks adopted a component-based architecture. This allowed developers create component libraries — a collection of reusable UI elements that can be used to build the application.

This was exciting. For the first time, both designers and developers were working off the same mental model. User interfaces are both designed and built as isolated extensible slices that are composed to create entire pages. Brad Frost’s Atomic Design pattern provided a good model for how to approach component-based design and extend those practices to development.

By focusing on isolated, extensible and reusable components we can surface implementation challenges much sooner in the process. It allowed for greater collaboration between designers and developers, and previously-siloed teams began to speak the same language. Design or development iterations could be more targeted and isolated. It was easier to see what UI components already exist which reduced a lot of duplicated effort.

What is a Design System?

The component-based model was a massive step in the right direction. There was a reduced need for hand-off, however, there were now multiple sources of truth — in design tools, in code and sometimes in documentation. There were reusable components, but usually no governance model of how to iterate. Which again lead to inconsistent UI or a need for constant design oversight.

There’s a common misconception that a Design System is just a Style Guide or a Component Library, terms people often use interchangeably. However, in our opinion, those are both sub-components of a Design System.

A design system includes design language, design kit, component library, developer sandbox and a documentation site

A Design System is a systematic approach to product development — complete with guidelines, principles, philosophies, and code. It shines a spotlight on how a team designs, making it a fantastic tool for scaling your design practice, reducing the need for hand-off and promoting more collaboration. As Nathan Curtis says, a “Design System isn’t a project, it’s a product serving products”. Our approach to a Design System includes:

  1. Design Language: The overall visual design of a digital product. This foundation defines characteristics such as typography, colors, icons, spacing and information architecture. The essence of your brand in a digital context. It is maintained as Design Tokens in code.

  2. Design Kit: A library of shared styles, symbols or components that can be used by product teams to design or prototype new experiences. These symbols mirror the JavaScript components from the component library and are updated to be always kept in sync.

  3. Component Library: A set of JavaScript components that are version controlled and are composed to build one or more products.

  4. Sandbox: A tool for developing components in isolation, document use cases and write structural or visual tests. This is for the Design System developers.

  5. Documentation: Tying everything together it has guidelines on how to consume the Design System, design and dev considerations and detailed documentation for each component. The documentation site often includes a live playground which is aimed at the consumers to try out the Design System in the browser.

  6. A Governance model for how we can continue to evolve the Design System and how others can contribute to it. A Design System enables your product teams to share learnings across silos, avoid duplicate work and focus more on user experience problems rather than UI problems.

A Design System enables your product teams to share learnings across silos, avoid duplicate work and focus more on user experience problems rather than UI problems.

What does a Design System Look like in Practice?

Though we believe that each Design System should include the six pillars described above, the exact details or structure of every Design System will be slightly different. What is the structure of your team or organization? Will your Design System service multiple brands, products, or regions? Will there be a dedicated team available to govern the system? All of these questions will inform how a Design System will best support your goals.

Regardless of the exact implementation, we’ve seen that the impact of a Design System is relatively universal.

Team Members From Different Disciplines Establish A Shared Understanding

In a Design System, the same set of components is shared by both designers and developers. The governance process ensures that both disciplines agree upon the exact requirements of a component and ensure it meets the needs of all parties, without those assumptions it can lead to painful rework later on. The capabilities and usage guidelines of these components are then documented in a centralized place so that everyone has a shared source of truth.

Suddenly, everyone is speaking the same language. When a designer is creating a page, they can now make use of these existing building blocks by grabbing from the UI kit, and can confidently point to those existing building blocks when implementation begins. There are no lengthy hand-off documents needed. The developer can then grab those same components from the component library. No more surprises or broken telephone.

Teams Can Iterate Faster

By implementing a Design System, you unlock the ability to release new designs in smaller chunks and shorter feedback loops, with fewer resources. It’s like if you already have the lego blocks manufactured for you, it takes a lot less time to build that castle, compared to if you had to spec and build blocks from scratch.

Multiple Teams Can Start Leveraging Each Others’ Work

Because Design Systems can be shared across multiple teams, efficiencies can be extended between multiple teams or multiple products. When one team has spent time and effort to solve a design problem, then why shouldn’t another team in your company also benefit from this work? Multiple teams, locales, or even separate brands can now make use of each other’s innovations and avoid reinventing the wheel.

The Role Of A Designer Changes

When a Design System is built, a designer’s role will change in one of two ways:

  1. For designers building or contributing to the Design System, designing UI becomes a more technical job. It is their responsibility to ensure that they are designing things in a reusable and systematic way to ensure that their designs mimic the world of development. Components and design decisions (like spacing, colors, typography) don’t happen in a vacuum, and every decision has cascading effects. Because of this, designers are required to have an extra level of discipline to build and adhere to the system.

  2. Designers using the system, however, are alleviated from thinking about these nitty-gritty UI interactions and instead get to focus on the holistic experience, the user flows, and the content. Because the nuances are already defined by the Design System, the creativity shifts from “How should I design this button” to “How do I create the most impactful experience?“. Now, the real innovation can begin.

Evolution of the Design System

It’s important to keep in mind that a Design System is never really “done”; it will grow and evolve just like any other product. As a website or product changes over time, there will be needs for new UI patterns to be created. By ensuring that the right governance processes are in place, teams can understand when and how to best introduce new elements into the system.

https://varun.ca/what-is-a-design-system/
Reuleaux Polygons
I have always been fascinated by the Reuleaux triangle. It is a shape formed by the intersection of three circles. Sort of like a ballooned…
Show full content
Reuleaux triangle, pentagon and hexagon.

I have always been fascinated by the Reuleaux triangle. It is a shape formed by the intersection of three circles. Sort of like a ballooned up equilateral triangle.

Reuleaux polygons are a generalization of the Reuleaux triangle. They are a curvilinear polygons i.e., made up of circular arcs and have an odd number of sides.

Construction

The Reuleaux triangle is constructed by drawing circles from each vertex of an equilateral triangle. Where the radius of these circles equals the side length of the triangle.

Reuleaux polygons are constructed using a similar process. Group three consecutive vertices to form a triangle. Then draw a circle from each vertex. The radius of this circle is the distance between the first and the third vertex. Then move onto the next three vertices and so on. Try changing the side count slider, in the demo above, to see this visualized.

SVG Arc Command

To draw these circular arcs in SVG we can use the arc command.

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

Where rx is the x-radius of the ellipse and ry is the y-radius. Keep rx equal to ry to create circular arcs.

Given these radii, there are two ellipses that can connect any two points. On those ellipses there are two possible paths to connect the points. In total, there are four possible arcs connecting any two points.

The large-arc-flag allows you to pick an arc greater than or less than 180 degrees. The sweep-flag controls if the arc should move clockwise or anti-clockwise.

Reuleaux Polygons With SVG

The first step is to get the location of each vertex of the polygon. In a previous blog post, I explained how you can use polar coordinates to generate the vertices of a regular polygon. You can follow the same process to get the vertices of a Reuleaux polygon. The only difference is that instead of connecting those vertices with a line you need to connect them with arcs.

  1. Compute the vertices of the polygon using the polygon generator.
    const pts = polygon([cx, cy], sideCount, radius);
  2. The radius of the arcs will be the distance between the first and the third vertex.
    const r = dist(pts[0], pts[2]);
  3. Draw the path. For Reuleaux polygons we need the arc to be less than 180 degrees and the sweep-flag direction should match the direction you define your points in (clockwise or anti-clockwise).
function reuleauxPolygonConstruction([cx, cy], radius, sideCount) {

const pts = polygon([cx, cy], sideCount, radius);
const r = dist(pts[0], pts[2]);
const [head, ...tail] = pts;

return [
  // move to the first vertex
  'M', head[0], head[1],
  // Connect adjacent vertices with an arc
  ...tail.map(p => `A ${r} ${r} 0 0 1 ${p[0]} ${p[1]}`),
  // Connect the last and the first vertices with an arc
  `A ${r} ${r} 0 0 1 ${head[0]} ${head[1]}`,
  // Close the shape
  'Z',
].join(' ');
Curve Of Constant Width

Although it is possible to construct curvilinear polygons with even number of sides. Reuleaux polygons always have an odd number of sides because they are constant-width shapes. Therefore, every vertex must be able to associate an opposite arc. This is possible only if we have an odd number of arcs.

A curve of constant width is a convex planar shape whose width (defined as the perpendicular distance between two distinct parallel lines each having at least one point in common with the shape’s boundary but none with the shape’s interior) is the same regardless of the orientation of the curve.

wikipedia

Any curve of constant width can rotate within a square while staying within it at all times. Notice, how as it rotates, its axis does not stay fixed at a single point. It follows a curve around the center of the square.

This property of being able to rotate within a square is leveraged to create bits that drill a nearly square hole. The drill bit itself is a Reuleaux triangle and is mounted in a chuck that allows for the bit to rotate without having a fixed centre of rotation.

Morphing

Lastly, just for fun, here are some polygons morphing between their regular form and Reuleaux form using flubber.

References
https://varun.ca/reuleaux-polygons/
Component Based Design System With Styled-System
Component-based design system is the practice of splitting the UI into small, isolated and more manageable parts; backed by a set of design…
Show full content

Component-based design system is the practice of splitting the UI into small, isolated and more manageable parts; backed by a set of design constraints. It builds upon ideas such as Atomic Design, Style Guides and Component-Based Architecture. In my previous, post I introduced some foundational concepts of component-based design systems and shared my approach to building one with Tachyons and React. I now want to focus on a different tool called Styled-System which uses a Tachyons like API while giving you a lot more control over the design system implementation.

Styled System

Styled-System provides you with a set of utilities that map props to your design system. It uses the concept of style functions. Each style function exposes its own set of props that style elements based on values defined in your design system theme. It has a rich API with functions for most CSS properties.

You need to pair the style functions with a CSS-in-JS library to build a component. It works with most CSS-in-JS libraries and even supports Vue. I will be using styled-components for all the examples in this blog post.

import { space, width, fontSize, color } from 'styled-system';
import styled, { ThemeProvider } from 'styled-components';
import theme from './theme';

const Box = styled.div`
  ${space}
  ${width}
  ${fontSize}
  ${color}
`;

render(
  <ThemeProvider theme={theme}>
    <Box p={3} bg="whites.10" color="orange">
      This is a Box
    </Box>
  </ThemeProvider>
);
Design Constraints Theme

You define your design system as a theme object (example below) and then provide it using the ThemeProvider component.

export const theme = {
  breakpoints: [32, 48, 64],
  space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
  fontSizes: [12, 14, 16, 20, 24, 36, 48, 80, 96],
  fontWeights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
  lineHeights: {
    solid: 1,
    title: 1.25,
    copy: 1.5,
  },
  letterSpacings: {
    normal: 'normal',
    tracked: '0.1em',
    tight: '-0.05em',
    mega: '0.25em',
  },
  fonts: {
    serif: 'athelas, georgia, times, serif',
    sansSerif:
      '-apple-system, BlinkMacSystemFont, "avenir next", avenir, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", arial, sans-serif',
  },
  borders: [
    0,
    '1px solid',
    '2px solid',
    '4px solid',
    '8px solid',
    '16px solid',
    '32px solid',
  ],
  radii: [0, 2, 4, 16, 9999, '100%'],
  width: [16, 32, 64, 128, 256],
  heights: [16, 32, 64, 128, 256],
  maxWidths: [16, 32, 64, 128, 256, 512, 768, 1024, 1536],
  colors: {
    black: '#000',
    'near-black': '#111',
    'dark-gray': '#333',
    'mid-gray': '#555',
    gray: ' #777',
    silver: '#999',
    'light-silver': '#aaa',
    'moon-gray': '#ccc',
    'light-gray': '#eee',
    'near-white': '#f4f4f4',
    white: '#fff',
    transparent: 'transparent',
    blacks: [
      'rgba(0,0,0,.0125)',
      'rgba(0,0,0,.025)',
      'rgba(0,0,0,.05)',
      'rgba(0,0,0,.1)',
      'rgba(0,0,0,.2)',
      'rgba(0,0,0,.3)',
      'rgba(0,0,0,.4)',
      'rgba(0,0,0,.5)',
      'rgba(0,0,0,.6)',
      'rgba(0,0,0,.7)',
      'rgba(0,0,0,.8)',
      'rgba(0,0,0,.9)',
    ],
    whites: [
      'rgba(255,255,255,.0125)',
      'rgba(255,255,255,.025)',
      'rgba(255,255,255,.05)',
      'rgba(255,255,255,.1)',
      'rgba(255,255,255,.2)',
      'rgba(255,255,255,.3)',
      'rgba(255,255,255,.4)',
      'rgba(255,255,255,.5)',
      'rgba(255,255,255,.6)',
      'rgba(255,255,255,.7)',
      'rgba(255,255,255,.8)',
      'rgba(255,255,255,.9)',
    ],
    // ... and so on
  },
};

Style functions will try to find a value from the theme object, these could be deeply nested values, and fallback to a hard-coded value if they are unable to.

// font-size: 24px (theme.fontSizes[4])
<Box fontSize={4} />

// margin: 16px (theme.space[3])
<Box m={2} />

// color: #333 (theme.colors.blacks[0])
<Box color="blacks.3" />

// background color (theme.colors['light-red'])
<Box bg="light-red" />

// line-height: 1.5 (theme.lineHeights.copy)
<Box lineHeight="copy" />

// renders CSS `50%` width since it's not defined in theme
<Box width={1/2} />
Responsive Style Props

All core props accept arrays as values for specifying responsive styles. Refer to the Table of Style Props to see which props support responsive values.

<Box
  width={[
    1, // 100% below the smallest breakpoint
    1 / 2, // 50% from the next breakpoint and up
    1 / 4, // 25% from the next breakpoint and up
  ]}
/>

// responsive font size
<Text fontSize={[ 2, 3, 4 ]} />
System Components

If you have settled on using styled-components with styled-system, then you can get an even simpler authoring experience by using system-components — a lightweight wrapper around those two libraries.

That’s a lot of libraries with similar sounding names 😅 To avoid confusion, here’s a quick overview of the ecosystem:

system-components

simpler authoring experience to create design-system-driven React UI components

Additional features such as default value for props & is prop

clean-tag & clean-element

removes styled-system props from the underlying DOM elements

styled-system

framework agnostic design system utilities

styled-components

a css-in-js library

system-components is comprised of styled-components, styled-system, clean-tag and clean-element. It also gives us the ability to provide default values for props.

Let’s start by creating a <Box /> primitive. I would recommend applying the core functions to all components. Beyond that, it depends on the component you are building. For the <Box /> component I include border, layout, flex and position functions.

import system from 'system-components';

const Box = system(
  // core
  'space',
  'width',
  'color',
  'fontSize',
  // borders
  'borders',
  'borderColor',
  'borderRadius',
  // layout
  'display',
  'maxWidth',
  'minWidth',
  'height',
  'maxHeight',
  'minHeight',
  // flexbox
  'alignItems',
  'alignContent',
  'justifyContent',
  'flexWrap',
  'flexDirection',
  'flex',
  'flexBasis',
  'justifySelf',
  'alignSelf',
  'order',
  // position
  'position',
  'zIndex',
  'top',
  'right',
  'bottom',
  'left'
);
Box.displayName = 'Box';

System-components used clean-tag and clean-element to strip out styled-system props automatically. Preventing them from passing down onto the underlying DOM elements. It allows you to control which DOM element is rendered by using the is prop. And you can also specify default values for all the props.

System-components automatically apply styled-system functions based on the keys of the default values object. In the example below, <Text /> will have fontSize, color and fontFamily functions automatically applied so, you don’t need to list them out.

This is honestly my favourite way to build UI components right now party parrot

const Text = system(
  {
    is: 'p',
    fontSize: 2,
    color: 'dark-gray',
    fontFamily: 'sansSerif',
  },
  'space',
  'width',
  'textAlign',
  'lineHeight',
  'fontWeight',
  'letterSpacing'
);
Text.displayName = 'Text';

const Heading = system(
  {
    is: 'h1',
    m: 0,
    fontSize: 6,
    color: 'dark-gray',
    fontFamily: 'sansSerif',
  },
  'width',
  'textAlign',
  'lineHeight',
  'fontWeight',
  'letterSpacing'
);
Heading.displayName = 'Heading';

In the previous, post I had created a Profile Card component. Here’s the whole example recreated with system-components.

Edit Design System Demo — Profile Card

import React from 'react';
import { Card, Box, Heading, Text, Avatar } from './design-system';

export const ProfileCard = ({ image, name, title, ...props }) => (
  <Card {...props} p={[3, 4, 4]} borderColor="blacks.3">
    <Box textAlign="center">
      <Avatar width={3} height={3} mb={2} borderColor="blacks.2" src={image} />
      <Heading is="h2" fontSize={4} mb={2}>
        {name}
      </Heading>
      <Text fontSize={2} my={0}>
        {title}
      </Text>
    </Box>
  </Card>
);
Input With Adornment Example

Moving onto some more complex examples. The <InputWithAdornment/> component allows you to place an adornment before or after an input. The adornment can be a bit of text, an icon or any valid node.

Currency InputAn illustration of the currency input with an icon adornmentCurrency

One possible way of building this is to wrap an HTML <input /> and containers for the adornments in a <Box />. The adornment containers are absolutely positioned while the input has padding to make room for the adornment.

Input With AdornmentAn illustration of the Input With Adornment component layoutBefore Adornment ContainerAfter Adornment Container<input /><InputWithAdornment />

You can pass in any node to the before and after props, while all other props are spread onto the <input /> element so as not to limit any functionality.

const InputWithAdornment = ({ before, after, disabled, ...props }) => (
  <Box display="flex" alignItems="center" position="relative">
    {before && (
      <Adornment left="0" pl={2} disabled={disabled}>
        {before}
      </Adornment>
    )}
    <HtmlInput
      py={2}
      pl={before ? 4 : 2}
      pr={after ? 4 : 2}
      disabled={disabled}
      {...props}
    />
    {after && (
      <Adornment right="0" pr={2} disabled={disabled}>
        {after}
      </Adornment>
    )}
  </Box>
);
Extending Components

System-components generate styled-components, allowing you to use all built-in methods such as .extend. <Adornment /> is a <Box /> extended to have an absolute position and a style function to control opacity based on the disabled prop.

const Adornment = Box.extend`
  position: absolute;
  opacity: ${props => (props.disabled ? 0.25 : 1)};
`;
Using Custom Functions

You can also pass in custom style functions to the system function. The function signature is system(defaultValues, styleFn1, styleFn2, ... customStyleFn). For the <HtmlInput /> component a custom style function adds styling for focus and disabled states. themeGet is a utility to get a value from your theme.

import { themeGet } from 'styled-system';

const HtmlInput = system(
  {
    is: 'input',
    type: 'text',
    fontFamily: 'sansSerif',
    fontSize: 'inherit',
    p: 0,
    m: 0,
    lineHeight: 'solid',
    width: 4,
    color: 'mid-gray',
    bg: 'white',
    border: 1,
    borderRadius: 2,
    borderColor: 'moon-gray',
  },
  props => ({
    display: 'inline-block',
    verticalAlign: 'middle',
    appearance: 'none',
    flex: '1 0 auto',
    '&:focus': {
      outline: 'none',
      borderColor: themeGet('colors.blue')(props),
    },
    '&:disabled': {
      opacity: 0.25,
      backgroundColor: themeGet('colors.light-gray')(props),
    },
  })
);
Product Card Example

The Aldo product card is perfect for demonstrating the power of composition. It displays an image with a particular aspect ratio. Its other contents change based on the status of the product.

.product-card-img { width: 49% !important; margin-bottom: 0.5rem; } @media screen and (min-width: 30em) { .product-card-img { width: 33% !important; margin-bottom: 0.25rem; } }
Product Cards

A more generic form of this component is the <ImageCard /> component. It consists of a <BackgroundImage /> component and containers for content that can be absolutely positioned on top of it. All wrapped in a <Box />.

Image CardAn illustration of the Image Card component layoutTop LeftContainerTop RightContainerBottom RightContainerBottom LeftContainer<ImageCard /><BackgroundImage />

<BackgroundImage /> is essentially the same as Rebass’ BackgroundImage component. It uses the padding technique to render images in a specific aspect ratio.

const BackgroundImage = system(
  {
    width: 1,
    ratio: 3 / 4,
    backgroundSize: 'cover',
    backgroundPosition: 'center',
    blacklist: ['src'],
  },
  'space',
  'color',
  'fontSize',
  'ratio',
  props => ({
    backgroundImage: props.src ? `url(${props.src})` : undefined,
  })
);

Notice how you can use the blacklist prop to prevent props from passing onto the DOM element.

The absolutely positioned content containers use <Box />. Alternatively, you could create an Absolute component. The wrapper <Box /> has all other props spread onto it. Allowing you to the control the size and layout of the entire component.

const ImageCard = ({ tl, tr, br, bl, src, ratio, ...props }) => (
  <Box position="relative" {...props}>
    <BackgroundImage width="100%" ratio={ratio} src={src} />
    {tl && (
      <Box position="absolute" top={0} left={0}>
        {tl}
      </Box>
    )}
    {tr && (
      <Box position="absolute" top={0} right={0}>
        {tr}
      </Box>
    )}
    {br && (
      <Box position="absolute" bottom={0} right={0}>
        {br}
      </Box>
    )}
    {bl && (
      <Box position="absolute" bottom={0} left={0}>
        {bl}
      </Box>
    )}
  </Box>
);

The <ProductCard /> is a more domain-specific version of the <ImageCard />. It holds the logic to display the appropriate product-tag, pricing and the favourite button. See the CodeSandbox below for the implementation of those other components.

const ProductCard = ({ shoe, ...props }) => (
  <ImageCard
    ratio={803 / 632}
    src={shoe.image}
    {...props}
    tl={shoe.status && <ProductTag>{statusText(shoe.status)}</ProductTag>}
    tr={<FavouriteButton p={2} m={2} isFavourite={shoe.favourite} />}
    bl={
      <Box ml={3} mb={2}>
        <HeadingWithDash dashWidth={1} fontSize={1} lineHeight="solid" mb={1}>
          {shoe.name}
        </HeadingWithDash>
        <Price {...shoe} />
      </Box>
    }
  />
);

These are just a few examples that demonstrate how you can use styled-system. There are other features such as complex-styles, pseudo-styles and even creating custom style utilities for you to explore. For further real-world examples check out Rebass and Priceline Design System.

In my opinion, styled-system is a genuinely fantastic tool for building a component-based design system. It manages to get the balance right between flexibility and maintainability.

Further reading
https://varun.ca/styled-system/
Component Based Design System With Tachyons
A Design system is a collection of rules, constraints and principles applied to your design and development process. I most often encounter…
Show full content

A Design system is a collection of rules, constraints and principles applied to your design and development process. I most often encounter it as a set of rules that control the typography, colours, spacing, sizing, icons and other visual styles. These rules form a foundation that guides your work. They reduce inconsistencies in both design and development implementations yet giving you the room to explore creative solutions.

Tachyons Design System

Tachyons is a functional CSS framework and a design system. If you are new to Tachyons, I suggest starting with Jason Li’s Tachyons CSS Reading List.

tachyons design constraints

The Tachyons design system uses a spacing scale, based on powers of two, which starts at 0.25rem and modular scale for typography. Other styles such as sizing, borders, opacity and shadows also use scales. It then exposes this design system via a set of immutable classes. For example:

  • Typographic scale .f1.f7
  • Spacing scale .ma0.ma7 & .pa0.pa7
  • Widths .w1.w5
  • Max-Widths .mw1.mw9

You compose these classes in markup to construct components and layouts, for example, className="f2 mt4 mb2". You have the option to customize this design system using tachyons-css/generator. Alternatively, extend it by writing CSS that follows the same API as that used by Tachyons itself. There are handy tools such as tachyons-tldr to assist you with this.

Component-Based Design System

Having experimented with BEM and modular CSS in the past, I’ve settled on components as my preferred API for a design system. As Michael Chan puts it, “components are a more powerful styling construct than CSS class names.” I wholeheartedly agree! In my experience, composing components has been a much better experience than having to write CSS.

Remember, you are the design system author and your users are other developers. Aim to give them a good user experience.

Using Tachyons is not at all like writing BEM or modular CSS. You rarely have to write CSS. Most of the work tends to be composing classes in templates or JSX. I enjoyed this workflow. However, I did find I was repeating specific tasks often. That is where components come in. They allowed me to abstract out some of those repetitive tasks and hide the implementation details.

In this two-part series, I am going to share techniques that I have used for building component-based design systems. Part one focuses on using Tachyons and part two on using Styled-System.

Tachyons Design System Components

The first, somewhat obvious, approach is to build low-level components that encapsulate styling and allow customization through the className prop.

const Button = ({ className = '', ...props }) => {
  const cx = 'bn f6 dim br2 pv3 ph4 white bg-purple ' + className;
  return <button className={cx} {...props} />;
};

render(<Button>Button Text</Button>);

There is a problem with this approach. You won’t always be able to customize the component when using it. For example, <Button className="f7">Button Text</Button> will work because .f7 class is declared after .f6 in the Tachyons stylesheet. However, <Button className="f1">Button Text</Button> won’t work as intended. The .f6 class is declared after .f1 so, it has a higher specificity and will win out.

Design System As Props

You can provide your users with a lot more control by exposing the design system as props. I would suggest using the classNames library to make generating and joining class names a bit easier.

const Button = ({
  f = 6,
  color = 'white',
  bg = 'purple',
  className,
  ...props
}) => {
  const cx = classNames(
    'bn dim br2 pv3 ph4',
    `f${f}`,
    color,
    `bg-${bg}`,
    className
  );

  return <button className={cx} {...props} />;
};

render(
  <div>
    <Button>Update</Button>
    <Button bg="moon-gray" color="dark-gray">
      Cancel
    </Button>
  </div>
);
Props 🔀 Design System ➡️ className

I love this pattern of mapping props onto the Tachyons design system and outputting class names. So, simple yet so effective. It allows you to build extremely reusable components that have base styling applied to them and accept overrides via props. You can choose to limit what is customizable depending on the component you are building.

You might notice a few missing features here such as media query support and type checking props. Plus it is going to be quite cumbersome to have to keep repeating this pattern for all your components.

withDesignSystem Higher-Order Component

You can create a higher-order component (HOC) that encapsulates the functionality of mapping props to the Tachyons design system. See the CodeSandbox below for a sample implementation. Tachyons-measured, while no longer maintained, shows how you can add media query support to this HOC.

You can use this HOC to enhance elements or other components into design system components quickly. A good place to get started is by creating a whole bunch of primitives. These are essentially HTML elements styled in accordance with your product’s design language and enhanced by withDesignSystem. For example:

  • Box (general purpose layout component)
  • Text & Heading
  • Image & Avatar (circular image)
  • Button & Link
  • Input, TextArea & Label

Beyond that, I would suggest looking at Rebass’ documentation. It is a fantastic example of the type of low-level primitives you should be building.

export const Box = withStyleProps('div');
Box.displayName = 'Box';

export const Text = withStyleProps('p');
Text.displayName = 'Text';

export const Heading = withStyleProps(({ level = 1, children, ...props }) => {
  return React.createElement('h' + level, props, children);
});
Heading.displayName = 'Heading';
Example

Consider this profile card taken from the Tachyons documentation. You can start by breaking down this layout into its various constituting components. There are many different ways you can approach this. I would personally break this down into: <Card />, <Box/>, <Avatar />, <Heading/> & <Text/> components.

tachyons cardtachyons card slices

As shown above, <Box/> and <Text/> are enhanced HTML elements. <Heading/> is an enhanced component that uses the level prop to decide which heading tag to render. <Card /> and <Avatar /> require a bit more work. Using the defaultProps HOC from Recompose you can provide some default styling. <Card /> is essentially an <article /> with a rounded border and some padding. And <Avatar /> is a circular image with a border.

export const Card = compose(
  defaultProps({
    bg: 'white',
    radius: 2,
    pa: 3,
    ba: 'black-10',
  }),
  withStyleProps
)('article');
Card.displayName = 'Card';

export const Avatar = compose(
  defaultProps({
    h: 4,
    w: 4,
    ba: 'black-05',
    pa: 2,
    radius: '-100',
  }),
  withStyleProps
)('img');
Avatar.displayName = 'Avatar';

Lastly, we can compose these components to create the <ProfileCard /> component.

const ProfileCard = ({ name, title, image, ...props }) => (
  <Card {...props}>
    <Box tc>
      <Avatar src={image} title={`Photo of ${name}`} className="dib" />
      <Heading f={3} mb={2}>
        {name}
      </Heading>
      <Text f={5} fw={4} color="gray" mt={0}>
        {title}
      </Text>
    </Box>
  </Card>
);
Tachyons-Components

Not everyone can or even wants to invest time into building a withDesignSystem HOC. I had a hard time maintaining tachyons-measured 😊 Luckily; Brent Jackson built a library called Tachyons-Components that gives you a slightly different but, faster way of building Tachyons design system components.

Instead of mapping props to the design system, it enables you to apply Tachyons class names directly as props. Using a styled-components like API you can set default styles when creating a component.

import styled from 'tachyons-components';

export const Box = styled('div')``;
Box.displayName = 'Box';

export const Text = styled('p')``;
Text.displayName = 'Text';

export const Heading = styled(({ level = 1, children, ...props }) => {
  return React.createElement('h' + level, props, children);
});
Heading.displayName = 'Heading';

export const Card = styled('article')`
  bg-white br2 pa3 ba b--black-10
`;
Card.displayName = 'Card';

export const Avatar = styled('img')`
  h4 w4 ba b--black-05 pa2 br-100
`;
Avatar.displayName = 'Avatar';

Then apply Tachyons classes as props when using the component to extend or customize its styles. Below you can see the complete profile card example recreated with tachyons-components.

<Text f5 fw4 gray mt0>
  {title}
</Text>

TL;DR use tachyons-components

Component-based design systems are a fantastic tool to make your product UI more consistent and improve the development workflow. By baking design decisions within the system itself, it reduces the number of decision that developers have to make. Therefore, allowing teams to make changes or build new features faster.

One restriction with Tachyons is that it does come with an opinionated set of design constraints. In part two I’m going to cover a different tool called Styled-System which gives you full control over defining your design system and uses a similar approach to withDesignSystem to expose the design system constraints as props.

Further reading
https://varun.ca/tachyons-components/
Components
Everything is a component. It took me a surprisingly long time to come to terms with this idea. I learnt to build UIs using templates. These…
Show full content

Everything is a component. It took me a surprisingly long time to come to terms with this idea.

I learnt to build UIs using templates. These templates were driven by routing. The routing structure of the app dictated how the UI was sliced up.

I mostly worked with AngularJS with MVVM architecture. Initially, these views had massive templates. Switching to UI-Router forced me to break them down a bit more; this was because UI-Router supported nested routes. There were some chunks of the UI which fit the component ideology, for example, modals and forms. However, that was rare.

Making the switch to components was hard. There were no apparent boundaries for a view. What is a component? What is the right level at which to slice a component? I struggled with this a lot. I know I am not the only one. I often saw other people taking the template style approach to components. Creating large components that handled everything from design language to UI logic and state.

It turns out we all sort of knew what the right answer was. Designers had been using symbols in design tools to break down the UI and create appropriate abstractions. Developers did the same thing in CSS with BEM and other such techniques. We were all reasoning about the UI in similar ways. We weren’t extending that approach to the way we built it, or at least I wasn’t.

A sign up form with email and password inputs and a sign-up button

Everything is a component. The Sign-up form above is a component. The Button, Labels, Inputs and FieldGroups are components too.

UI System

Your UI is a system of components; where each component should be flexible, independent, reusable and often stateless. Start small, use a design system and compose variations & complexity.

A well-designed UI system should be composable. Much like functions are composed together to complete more complex tasks. Components can be composed together to create more complex UI patterns. See my previous blog post for a deeper dive on this subject.

Component Dichotomy

Like many, I started by applying the concepts of Atomic Design to components; creating a hierarchy of components — atoms, molecules, organisms & containers. In hindsight, these classifications were helpful guides but, not something that was necessary. In some cases, this even introduced unnecessary restrictions. I now use more of an “everything is a composition” approach as outlined in Daniel Eden’s theory of Subatomic Design Systems.

Design System constraints or variables are the lowest level building blocks. Compose them with HTML/JSX/etc. to create the first set of components. These form the foundation of your component-based design system. Compose them into UI patterns. State is yet another layer of composition.

At some point, these compositions become more aware of the product that they belong to. A language emerges. A product-specific language shared between designers and developers. Which allows you to discuss ideas using abstracted components and not get lost in the minutia of rems, pixels and hex codes.

Further reading
https://varun.ca/components/
JavaScript Frameworks: The Year of Convergence
If 2016 was the year of JavaScript fatigue then 2017 was most certainly the year of convergence. Most JavaScript frameworks have converged…
Show full content

If 2016 was the year of JavaScript fatigue then 2017 was most certainly the year of convergence. Most JavaScript frameworks have converged towards using similar tooling and concepts.

In this post, I am going to focus on some of the similarities between JavaScript frameworks. I want to show how knowledge of one framework transfers quite well to the others.

Component Based Architecture

If there is one thing everyone can agree on, it is that components are awesome. Designers love them because it allows them to create a shared language with developers and roll out design systems. Developers love them because it means they can focus on building small, self-contained and reusable features, then composing them to build larger views and the entire application itself. Product managers love them because they enable sharing code across multiple apps.

React popularized the component model for modern front-end development. In the 1.x days, some people in the Angular community started writing Component-Based Directives. Then Angular 1.6 introduced angular.component() which made it easier to write those directive components. In 2016, with Angular 2.0 we got real components. Meanwhile, Vue was launched with components as one of its core features.

Below are examples of how you would write components with all three frameworks. You’ll notice a lot of similarities.

// React Component
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
// Angular Component
@Component({
  selector: 'greet',
  template: '<h1>Hello, {{ name }}</h1>',
})
export class WelcomeComponent {
  @Input() name: string;
}
// Vue Component
Vue.component('greet', {
  props: ['name'],
  template: '<h1>Hello, {{ name }}</h1>',
});

There are variations available. For example, in React you can write components as functions. Angular and Vue allow you to reference templates defined in HTML files. Vue also allows you to write single-file components where HTML, CSS and JS for your component are all in one .vue file (more on that later). However, the core idea of what constitutes a component is the same in all three frameworks.

Defining the View

The view part of a component is what we want the framework to render when we use the component somewhere in our application. With Angular we define the view as templates. These are HTML partials that use a mustache/handlebars-like syntax to bind to data — two curly braces wrapping a JavaScript expression. These templates also support several built-in components and directives, which allow you to define template logic such as conditional and list rendering.

React uses JSX, which allows you to define views in JavaScript using XML-like syntax. Bindings work pretty much like templates — single curly brace wrapping an expression. The biggest difference is how we add control statements and template logic. With templates you need to use built-in components and directives; with JSX, however, you just use JavaScript features such as if statements, ternary operator or Array.map. There is a bit of a learning curve to it, but you’ll be surprised by how much of your knowledge of working with templates translates to JSX.

Vue supports both! Templates in Vue were inspired by Angular. They use pretty much the same components and directives for template logic and the double curly brace syntax for bindings. You can also use JSX with Vue by defining a render function, instead of using the template property.

One of the major benefits of JSX is that it allows you to write all the code for a component in one file. That is the view, the JavaScript logic and even styles. Vue supports Single File Components, which allow you to write all the component code in one file. However, you are not limited to using just JavaScript. You can write your view in an HTML template or Pug or just use a render function. Styles can be written in CSS or SCSS or PostCSS, etc.

<template>
  <h1 class="f1">Hello, {{ name }}</h1>
</template>

<script>
  export default {
    props: { name: String },
  };
</script>

<style>
  .f1 {
    font-size: 3rem;
  }
</style>
Styling & Encapsulation

Styling of components generally comes in three different flavours:

  • Classic: all your CSS is available globally and the components can use any styles.
  • Encapsulated: each component has styles scoped to itself. It doesn’t use any global styles and none of the component styles leak out.
  • Mix: your components rely mostly on scoped styles, but there are also some globally defined defaults that cascade down.

Do you prefer one approach over the others? Good news! All three frameworks support all three approaches. Global CSS just works out of the box. You can use the class attribute in templates or the className prop in JSX.

Angular also has built-in support for scoping. You can choose from one of three encapsulation strategies and styles can be run through pre-processors, such as Sass or PostCSS, before loading them in a component.

// Angular Component
@Component({
  selector: 'greet',
  template: '<h1 class="f1">Hello, {{ name }}</h1>',
  styleUrls: ['./welcome.component.css'],
  encapsulation: ViewEncapsulation.Emulated,
})
export class WelcomeComponent {
  @Input() name: string;
}

Vue supports scoping through the single file component syntax. Just add the scoped attribute to the style tag. And similar to Angular, you can set up your build tool to pre-process the CSS before loading it.

<template>
  <h1 class="f1">Hello, {{ name }}</h1>
</template>

<script>
  export default {
    props: { name: String },
  };
</script>

<style scoped>
  .f1 {
    font-size: 3rem;
  }
</style>

CSS Modules is another popular system for modularizing and scoping CSS. To use CSS Modules with Vue you can add the module attribute to the style tag.

<template>
  <h1 :class="$style.f1">
    {% raw %}Hello, {{ name }}{% endraw %}
  </h1>
</template>

<style module>
  .f1 {
    font-size: 3rem;
  }
</style>

React does not have built in scoping support. However, the React community has many vibrant and innovative solutions for writing component scoped CSS. You can of course, use CSS Modules.

import React from 'react';
import styles from './welcome.css';

class Welcome extends React.Component {
  render() {
    return <h1 className={styles.f1}>Hello, {this.props.name}</h1>;
  }
}

More often though, scoped CSS in React is achieved using CSS-in-JS based solutions such as styled-components, glamorous, emotion, and many more.

If you prefer the CSS-in-JS approach, some popular CSS-in-JS libraries support Vue too, e.g. styled-components/vue-styled-components and emotion — vue styled.

Passing Data Into a Component

Each instance of a component is isolated. It can, however, receive data from its parent. In React and Vue this concept is known as props. In Angular this is referred to as inputs.

Props and inputs are read-only data that can be used by a component in its view or further pass down to its children. They flow unidirectionally down the component hierarchy. When these props or inputs update, they trigger the components that are receiving them to re-render.

The child component must explicitly declare the props or inputs it expects to receive. We can also specify the types for these props. The PropTypes library is one way of specifying prop types in React. For example:

MyComponent.propTypes = {
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  requiredFunc: PropTypes.func.isRequired,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),
  customProp: function(props, propName, componentName) {...},
};

Vue provides similar support for prop validation.

Vue.component('example', {
  props: {
    propA: Number,
    propB: [String, Number],
    propC: { type: String, required: true },
    propD: {
      validator: function (value) { ... }
    },
  },
});

Angular 2+ was created to use with TypeScript primarily. Therefore, it relies on the TypeScript for validating input types. You just need to annotate the input properties with a type.

@Component({ ... })
export class WelcomeComponent {
  @Input() inputA: string;
  @Input() inputB: number;
  @Input() inputC: MyModelClass;
  @Input() inputC: MyFunctionInterface;
}

This idea of using static type checkers is quite powerful. They generally improve developer workflow by identifying certain issues even before the code is executed. This can be extremely beneficial for larger apps. Therefore, in early 2017 PropTypes was moved out of React core. The current official recommendation (for larger apps) is to use Flow or TypeScript.

TypeScript support for Vue is not as robust as that for Angular and React, but it is improving fast. Microsoft maintains a TypeScript-Starter and there is even an official library that allows you to use class style syntax to write a Vue component.

import Vue from 'vue';
import Component from 'vue-class-component';

@Component({
  template: {% raw %}'<h1>Hello, {{ name }}</h1>',{% endraw %}
})
export default class MyComponent extends Vue {
  name: string;
}
Events

All three frameworks allow you to specify event handlers on components. This allows us to listen to DOM events and execute some JavaScript when they are triggered.

// React
<button onClick={this.handleClick}>Action</button>
<!-- Angular -->
<button (click)="handleClick($event)">Action</button>
<!-- Vue -->
<button @click="handleClick($event)">Action</button>

Props and Inputs allow data to flow down into a component, but we quite often want to propagate changes back up the component hierarchy. Angular and Vue allow us to do that by emitting custom events. Components can then bind onto these custom events.

With Vue we can trigger a custom event by using $emit.

Vue.component('counter', {
  template: `
    <button @click="increment">
      {% raw %}{{ count }}{% endraw %}
    </button>`,
  data: function() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count += 1;
      this.$emit('increment');
    },
  },
});

With Angular custom events are instances of EventEmitter annotated with an @Output decorator. To trigger a custom event we call the event emitter’s emit method.

import { Component, EventEmitter } from '@angular/core';

@Component({
  selector: 'counter',
  template: `
    <button (click)="increment()">
      {% raw %}{{ count }}{% endraw %}
    </button>`,
})
class CounterComponent {
  private count: number = 0;
  @Output() increment = new EventEmitter<number>();

  increment() {
    this.count += 1;
    this.increment.emit('increment');
  }
}

React takes a slightly different approach here. Instead of bubbling up custom events we can pass handlers down as props and call those handlers at the appropriate time.

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment() {
    this.setState(
      prevState => ({ count: prevState.count + 1 }),
      () => {
        this.props.handleIncrement(this.state.count);
      }
    );
  }

  render() {
    return <button onClick={() => this.increment()}>{this.state.count}</button>;
  }
}
Conclusion

I’ve focused primarily on the idea of components here but, similarities extend beyond that. Bundling, state management, reactivity and CLIs are other concepts where frameworks have learnt from each other and ended up picking not too dissimilar solutions. There is even hope that someday we will be able to share components across frameworks.

The past few years have been a bit stressful because there seemed to be a new framework to learn every month. People were worried about investing time in the “wrong” framework. Now that things have converged, I am excited to say that investment in any one of those frameworks will pay off. Your knowledge and learning from one framework applies just about everywhere.

This post was first published on blog.rangle.io
https://varun.ca/convergence/
Polar Coordinates 🌀
Cartesian Coordinate System A coordinate system allows us to use numbers to determine the position of points in a 2D or 3D space. The most…
Show full content
Cartesian Coordinate System

A coordinate system allows us to use numbers to determine the position of points in a 2D or 3D space. The most popular coordinate system is probably the Cartesian coordinate system. It allows you to locate each point by a pair of numerical coordinates (x, y). There is a very good chance that you have used this system. It is everywhere — SVG, Canvas, WebGL and even Sketch & Illustrator.

(x, y)xyPO
Figure 1: Cartesian coordinate system
Polar Coordinate System

Polar coordinate system is a 2D coordinate system in which each point is determined by r & θ. Where r is the distance from the origin and θ is the angle from the x-axis.

θ = 30°r = 2xyPO
Figure 2: Polar coordinate system
Converting Between Polar and Cartesian Coordinates

Trigonometry! Love it or hate, it is everywhere. You can convert a point from polar coordinates (r, θ) to Cartesian coordinates (x, y) using the equations below. These come in really handy since most tools only accept x & y as point locations.

const x = r * Math.cos(theta);
const y = r * Math.sin(theta);
Patterns

Below I have a few animations by Dave Whyte aka bees & bombs. They all have one thing in common, they use polar coordinates to generate a pattern.

Figure 3: Patterns generated using polar coordinates

Cartesian coordinates are a great choice for placing things evenly on a rectangular grid. However, if you want to distribute things evenly around a circle then polar coordinates are generally the better option. Keeping the radius constant we compute the angle for each point as angle = 360° * index / number of sides.

360°30°60°90°120°150°180°210°240°270°300°330°
Figure 4: Placing items evenly around a circle

Below is the JavaScript implementation of this idea. The points function also accepts an optional offset argument to shift the points along the perimeter of the circle. For example, the outermost circle of points in Figure 4 can be generated using points(12, 200). The next one inwards using points(12, 175, 15). The one after that using points(12, 150, 30) and so on. My splash CodePen is was built using this.

function points(count, radius, offset = 0) {
  const angle = 360 / count;
  const vertexIndices = range(count);

  return vertexIndices.map(index => {
    return {
      theta: offset + degreesToRadians(offset + angle * index),
      r: radius,
    };
  });
}

// number => [0, 1, 2, ... number]
function range(count) {
  return Array.from(Array(count).keys());
}

function degreesToRadians(angleInDegrees) {
  return (Math.PI * angleInDegrees) / 180;
}
Polygon Generator

A regular polygon is a polygon that is equiangular i.e., all its angles are equal and all its sides have the same length. This means that all the vertices of a regular polygon are points evenly spaced on a circle. And isn’t it handy that we just created a function that generates exactly this!

360°30°60°90°120°150°180°210°240°270°300°330°
Figure 5: Drawing a polygon by connecting polar coordinates

To generate an SVG polygon we will generate the list of of points using the points function and then simply connect the dots. With SVG we have two options for this: <polygon> or <path>. The example below generates the points attribute for a <polygon> element.

/**
 * Usage with Vanilla JS/DOM:
 *   polygonEl.setAttribute('points', polygon((5, 64, 18)));
 *
 * Usage with React:
 *   <polygon points={polygon((5, 64, 18)} />
 *
 * Usage with View:
 *   <polygon :points="polygon((5, 64, 18)" />
 */
function polygon(noOfSides, circumradius, rotation) {
  return points(noOfSides, circumradius, rotation)
    .map(toCartesian)
    .join(' ');
}

function toCartesian({ r, theta }) {
  return [r * Math.cos(theta), r * Math.sin(theta)];
}

Seems simple but, you can do a lot of interesting things with it. Here are a couple of examples:

Gems generated using polar coordinates
Relative Polar Coordinates

When you define a point as (r, θ), by default, this is relative to the origin (0, 0). We can define points relative to other points by shifting the origin. This is often used for defining the position of curve handles relative to a vertex.

const x = cx + r * Math.cos(theta);
const y = cy + r * Math.sin(theta);
60 degr = 3cxcyPOC
Figure 6: Relative polar coordinates

We can modify the polygon generator to allow us to draw a polygon centred at any location in the SVG canvas.

function polygon(noOfSides, circumradius, rotation, [cx = 0, cy = 0]) {
  return points(noOfSides, circumradius, rotation)
    .map(pt => toCartesian(pt, [cx, cy]))
    .join(' ');
}

function toCartesian({ r, theta }, [cx, cy]) {
  return [cx + r * Math.cos(theta), cy + r * Math.sin(theta)];
}
Rotation

Another common application of polar coordinates is to rotate things around a point. Here (cx, cy) is the point about which you want to rotate.

x = cx + r * Math.cos(theta);
y = cy + r * Math.sin(theta);

// somewhere in an animation loop
window.setInterval(() => {
  theta++;
}, 1000 / 60);
Polar curves

We started by looking at individual points. Then we grouped a few points into a set to define shapes. For this we used the polygon generator function to compute the location of each vertex of the shape. We can write similar functions using other mathematical equations. Allowing us to generate more complex shapes and curves.

Two dimensional curves are described by equations of the type y = f(x). For example the equation of a circle is x2 + y2 = r2. We can generate the set of points, called locus, by iterating x and computing the corresponding y value or vice-versa. Therefore, each point will be of the form (x, f(x)) or (g(y), y).

With polar coordinates we can similarly draw polar curves. For example, the polar equation of a circle is r = 2 * cos(0). The points on a polar curve have the form (r(0), 0).

// examples of fn:
//   circle     : 2 * Math.cos(theta)
//   blob thing : a * (1 - Math.cos(theta) * Math.sin(3 * theta))
const r = fn(theta);

const x = cx + r * Math.cos(theta);
const y = cy + r * Math.sin(theta);
Eukleides

All the diagrams in this post were created using a language called eukleides. It is a fantastic tool for making geometric drawings. Just look at this declarative API 😍

c = circle(point(3, 0), 3)
P = point(c, 170°)
M = point(0, 0)
N = point(6, 0)

draw (M.N.P)
label M, P, N right, 0.6
https://varun.ca/polar-coords/
Animating Clipped Shapes
Another #postAboutYourPen post. This time I'm going to explain how I recreated Herry Koo's SA 15 dribbble shot using SVG and animejs…
Show full content

Another #postAboutYourPen post. This time I’m going to explain how I recreated Herry Koo’s SA 15 dribbble shot using SVG and animejs.

The Ingredients
  • 2 Arc Slices
  • 2 Clipped Rectangles
  • 1 Circle

An arc is a section of circle or an ellipse. To create this arc you need the starting point, the end point and the radius of the circle that these two points are on. There are two circles that can connect any two points: a big one and a small one. SVG allows us to pick which one we want using the large-arc-flag. We can also control whether the arc should be drawn clockwise or anti-clockwise using the sweep-flag.

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

Dribbble shots are 800 x 600px so that’s what I picked for the vieBox. This makes it easy to open the gif in Preview, measure stuff and use those values in the SVG. I measured the circle to be 113px. The left slice was 154px wide and the right slice 72px. With a bit of trial and error I found the start and end-points of the arcs. I drew the arcs using the path element.

<!--
  Move to the starting point then draw an arc to the end point
  and close the shape by going back to the starting point

  M start_x start_y A 113 113 0 large-arc-flag 1 end_x end_y Z
-->
<path class="js-left-slice" d="M 441.6 405 A 113 113 0 1 1 441.6 195 Z" />

<path class="js-right-slice" d="M 441.6 195  A 113 113 0 0 1 441.6 405 Z" />

These paths create the outline. The sliding fill inside them was created using clipped rectangles. For the clipPath we use the same paths as the outline and then apply them to the rectangles using the clip-path attribute.

<defs>
  <clipPath id="left-slice-mask">
    <path d="M 441.6 405 A 113 113 0 1 1 441.6 195 Z" fill="#fff" />
  </clipPath>
  <clipPath id="right-slice-mask">
    <path d="M 441.6 195  A 113 113 0 0 1 441.6 405 Z" fill="#fff" />
  </clipPath>
</defs>
<rect
  class="js-right-fill"
  x="441.6"
  y="187"
  width="72"
  height="226"
  clip-path="url(#right-slice-mask)"
/>

To slide the fill in-and-out I animate the x value using animejs. The clipPath remains static. It’s only the rectangles that are moving.

The combined shape is moved up-down and left-right by animating translateX and translateY. We apply the same animation at the same time to both the arc slice and clipped rectangle. Note, you do not need to animate the position of the clipPath.

The little circle that burst out. Well… that’s just a circle. At the appropriate time I hide the left slice and rectangle and show the circle instead. The circle scales down as it moves to the left and then stretches — using scaleX — as it moves back to the right.

The squirmy line effect that everything has is known as squigglevision. I used the code from this example.

The rest is all about matching the movement and timing of the original animation.

Here are a couple more examples of animating clipped shapes. For these I used a slightly different technique. They have shapes which are filled with an animated SVG pattern.

https://varun.ca/clip-path/
Metaballs
Metaballs, not to be confused with meatballs, are organic looking squishy gooey blobs. From a mathematical perspective they are an iso…
Show full content

Metaballs, not to be confused with meatballs, are organic looking squishy gooey blobs. From a mathematical perspective they are an iso-surface. They are rendered using equations such as f(x,y,z) = r / ((x - x0)2 + (y - y0)2 + (z - z0)2). Jamie Wong has a fantastic tutorial on rendering metaballs with canvas.

We can replicate the metaball effect using CSS & SVG by applying both blur and contrast filters to an element. For example in Chris Gannon’s Bubble Slider below.

SVG Metaball

I discovered another approach to creating this metaball effect from Paper.js examples. Back in the days of Scriptographer Hiroyuki Sato created a script for generating gooey blobs in Adobe Illustrator. Unlike the previous techniques this does not render pixels or rely on filters. Instead it connects two circles with a membrane. Which means that the we can generate the entire blob as a path. For the Amoeba CodePen I followed exactly this technique.

In this blog post I am going break down the steps required to generate the metaball. We are going to go through a function called metaball which generates the black shaded path that you see below. This consists of the connector plus a part of the second circle.

Building the Metaball

To figure out where the connector touches the two circles we start by locating two tangents that touch both circles. This is the widest the connector can be. BTW I’m focusing on the case when the circles are not overlapping first.

We can calculate the maximum angle of spread using:

const maxSpread = Math.acos((radius1 - radius2) / d);

Why? This took me a while to figure out. I could attempt to explain here, but you are probably better of seeing the step-by-step illustration in this external tangents to two given circles guide.

max-spread

This is the maximum possible spread that the connector can have. We can control spread amount by multiplying it with a factor called v. The Paper.js code has v = 0.5. That seems to work well.

The spread for the smaller circle is (Math.PI - maxSpread) * v. This is because the sum of the opposite angles of a polygon is 180°.

Next we need to find the location of those four points. We know the centre of the circles (center1 & center2) and the radii (radius1 & radius2). Therefore, we will only be dealing in terms of angles and then use polar coordinates to convert it into (x, y) values later.

const angleBetweenCenters = angle(center2, center1);
const maxSpread = Math.acos((radius1 - radius2) / d);

// Circle 1 (left)
const angle1 = angleBetweenCenters + maxSpread * v;
const angle2 = angleBetweenCenters - maxSpread * v;
// Circle 2 (right)
const angle3 = angleBetweenCenters + (Math.PI - (Math.PI - maxSpread) * v);
const angle4 = angleBetweenCenters - (Math.PI - (Math.PI - maxSpread) * v);

The angles need to be measured clockwise. Therefore, for the second circle we take that into account by subtracting from Math.PI. We add angleBetweenCenters to all because the circles can be moving diagonally too. Then convert polar coords to cartesian.

// Points
const p1 = getVector(center1, angle1, radius1);
const p2 = getVector(center1, angle2, radius1);
const p3 = getVector(center2, angle3, radius2);
const p4 = getVector(center2, angle4, radius2);

To convert the trapezium shaped connector into a curved one we need to add handles to all four points. The next part of the process is to figure out the location of the handles.

The handle for a particular point should be aligned to the tangent to the circle at that point. Again we’ll use polar coords to locate the handle. This time however, it will be relative to the point itself.

ABCangle 1

The lines AB and BC are perpendicular because AB is radial and BC is a tangent to the circle. Therefore, the angle for the handle 1 is angle1 - Math.PI / 2. Similarly we can calculate the angles for the other three handles.

The length of the handle is relative to the radius of the circle they originate from times the factor d2. For example, the length of handle 1 is radius1 * d2. We can now calculate the location of the handles like so:

const totalRadius = radius1 + radius2;
// Handle length scaling factor
const d2 = Math.min(v * handleSize, dist(p1, p3) / totalRadius);
// Handle lengths
const r1 = radius1 * d2;
const r2 = radius2 * d2;

const h1 = getVector(p1, angle1 - HALF_PI, r1);
const h2 = getVector(p2, angle2 + HALF_PI, r1);
const h3 = getVector(p3, angle3 + HALF_PI, r2);
const h4 = getVector(p4, angle4 - HALF_PI, r2);

We have all the points 🙌🏽 Time to construct the SVG path. The path is made of three sections: curve from point 1 to point 3, arc of radius2 from point 3 to point 4 and curve from point 4 to point 2.

function metaballToPath(p1, p2, p3, p4, h1, h2, h3, h4, escaped, r) {
  return [
    'M', p1,
    'C', h1, h3, p3,
    'A', r, r, 0, escaped ? 1 : 0, 0, p4,
    'C', h4, h2, p2,
  ].join(' ');
}
Circle Overlap

We have a gooey metaball! But you’ll notice that path gets all weird and twisty when the circles start to overlapping. We are going to fix this by expanding the spread in proportion to how much the circles are overlapping.

The spread expansion will be controlled using the angles u1 and u2. We can calculate these using the law of cosines.

radius1dradius2u1u2
u1 = Math.acos(
  (radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)
);

u2 = Math.acos(
  (radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)
);

But what shall we do with these 🤔 To be honest I’m not entirely sure how this works. What I do know is that it expands the spread as the circles get closer and then collapses it once circle 2 is completely inside circle 1.

const angle1 = angleBetweenCenters + u1 + (maxSpread - u1) * v;
const angle2 = angleBetweenCenters - (u1 + (maxSpread - u1) * v);
const angle3 =
  angleBetweenCenters + Math.PI - u2 - (Math.PI - u2 - maxSpread) * v;
const angle4 =
  angleBetweenCenters - (Math.PI - u2 - (Math.PI - u2 - maxSpread) * v);

And one final change to account for overlapping circles. The length of the handles will also be proportional to the distance between the circles.

// Define handle length by the distance between both ends of the curve
const totalRadius = radius1 + radius2;
const d2Base = Math.min(v * handleSize, dist(p1, p3) / totalRadius);
// Take into account when circles are overlapping
const d2 = d2Base * Math.min(1, (d * 2) / (radius1 + radius2));

const r1 = radius1 * d2;
const r2 = radius2 * d2;
Conclusion

And here is the final result and the entire code snippet for metaball. Try forking it and playing around with different values of handleSize and v. See how they impact the shape of the connector. There are so many amazing little details in these 70 lines of code. Fascinating work by Hiroyuki Sato. I learnt so much from it!

/**
 * Based on Metaball script by Hiroyuki Sato
 * http://shspage.com/aijs/en/#metaball
 */
function metaball(
  radius1,
  radius2,
  center1,
  center2,
  handleSize = 2.4,
  v = 0.5
) {
  const HALF_PI = Math.PI / 2;
  const d = dist(center1, center2);
  const maxDist = radius1 + radius2 * 2.5;
  let u1, u2;

  // No blob if a radius is 0
  // or if distance between the circles is larger than max-dist
  // or if circle2 is completely inside circle1
  if (
    radius1 === 0 ||
    radius2 === 0 ||
    d > maxDist ||
    d <= Math.abs(radius1 - radius2)
  ) {
    return '';
  }

  // Calculate u1 and u2 if the circles are overlapping
  if (d < radius1 + radius2) {
    u1 = Math.acos(
      (radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)
    );
    u2 = Math.acos(
      (radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)
    );
  } else {
    // Else set u1 and u2 to zero
    u1 = 0;
    u2 = 0;
  }

  // Calculate the max spread
  const angleBetweenCenters = angle(center2, center1);
  const maxSpread = Math.acos((radius1 - radius2) / d);
  // Angles for the points
  const angle1 = angleBetweenCenters + u1 + (maxSpread - u1) * v;
  const angle2 = angleBetweenCenters - u1 - (maxSpread - u1) * v;
  const angle3 =
    angleBetweenCenters + Math.PI - u2 - (Math.PI - u2 - maxSpread) * v;
  const angle4 =
    angleBetweenCenters - Math.PI + u2 + (Math.PI - u2 - maxSpread) * v;

  // Point locations
  const p1 = getVector(center1, angle1, radius1);
  const p2 = getVector(center1, angle2, radius1);
  const p3 = getVector(center2, angle3, radius2);
  const p4 = getVector(center2, angle4, radius2);

  // Define handle length by the distance between both ends of the curve
  const totalRadius = radius1 + radius2;
  const d2Base = Math.min(v * handleSize, dist(p1, p3) / totalRadius);
  // Take into account when circles are overlapping
  const d2 = d2Base * Math.min(1, (d * 2) / (radius1 + radius2));

  // Length of the handles
  const r1 = radius1 * d2;
  const r2 = radius2 * d2;

  // Handle locations
  const h1 = getVector(p1, angle1 - HALF_PI, r1);
  const h2 = getVector(p2, angle2 + HALF_PI, r1);
  const h3 = getVector(p3, angle3 + HALF_PI, r2);
  const h4 = getVector(p4, angle4 - HALF_PI, r2);

  // Generate the connector path
  return metaballToPath(p1, p2, p3, p4, h1, h2, h3, h4, d > radius1, radius2);
}

// prettier-ignore
function metaballToPath(p1, p2, p3, p4, h1, h2, h3, h4, escaped, r) {
  return [
    'M', p1,
    'C', h1, h3, p3,
    'A', r, r, 0, escaped ? 1 : 0, 0, p4,
    'C', h4, h2, p2,
  ].join(' ');
}
https://varun.ca/metaballs/
Learning CSS Grid – Part 2
In part one I introduced the CSS Grid Layout Module. We covered some basic terminology and talked about how to create layouts by placing…
Show full content
+-------------------------+ +---+ | | | | | CSS Grid | | | | Layout Module | | | | | | | +-------------------------+ | | +----+ +------------------+ | | | | | | | | | | | | | | | | +------------------+ +---+ | | +------+ +---------------+ | | | | | part 2 | +----+ +------+ +---------------+

In part one I introduced the CSS Grid Layout Module. We covered some basic terminology and talked about how to create layouts by placing items onto a grid-area.

Let’s start with quick refresher. Grid Area is the space created by the intersection of four grid lines. We define it in terms of start and end points for columns and rows.

+-- grid-column-start | +-- grid-column +--+ | | | +-- grid-column-end grid-area +--+ | +-- grid-row-start | | +-- grid-row +-----+ | +-- grid-row-endGrid area is made up of a grid column and a grid row. Grid column and row both require a start and an end point.

In this post I am going to introduce another approach to creating grid based layouts – using grid template areas.

Grid Template Areas

This property allows us to divide the grid into named areas which can then be referenced for placing items. We start by declaring the names in the grid-template-areas property. Once declared we can use them as keywords in any of the grid-placement properties.

We are still defining grid areas however instead of providing row/column start & end points we are doing it through a layout template.

.my-grid { display: grid; grid-template-areas: "header header header" "content content side" "footer footer footer"; }Named Areas +-----------------------+ |░░░░░░░░░░░░░░░░░░░░░░░| |░░░░░░░ Header ░░░░░░░░| |░░░░░░░░░░░░░░░░░░░░░░░| +---------------+-------+ |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|░░░░░░░| |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|░░░A░░░| |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|░░░s░░░| |▓▓▓ Content ▓▓▓|░░░i░░░| |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|░░░d░░░| |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|░░░e░░░| |▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓|░░░░░░░| +---------------+-------+ |░░░░░░░░░░░░░░░░░░░░░░░| |░░░░░░░ Footer ░░░░░░░░| |░░░░░░░░░░░░░░░░░░░░░░░| +-----------------------+We can define named grid areas to place content into.

You’ll notice that the syntax of the grid-template-areas property provides a visualization of the grid structure. This makes it relatively easy to understand the layout. To place items onto this grid we can use grid-area property. For example: grid-area: header; or grid-area: content;, etc.

The grid-template-areas property allows us to define the shape of the layout, but not the sizing. By default the sizing is dependant on grid items placed within them. You can pair grid-template-areas with other properties such as grid-template-rows & grid-template-columns to control the sizing of the areas too!

Example 1 – Magazine Style Layout

The above is a magazine style layout of Hawaii’s Online Gaming Curse article from The Outline – built using both CSS Grid and multi-column layout. Below is a schematic of what the layout looks like at the three break points.

Large-Breakpoint min-width: 60em +----+ +------------------+ +-----------+ |meta| | title | | copy2 | | | | | | | +----+ +------------------+ +-----------+ +-------------------------+ +-----------+ | copy1 | | copy2 | | | | | +-------------------------+ +-----------+ +-------------------------+ +-----------+ | copy1 | | copy2 | | | | | +-------------------------+ +-----------+ +---------------------------------------+ | media | | | +---------------------------------------+ +-----------+ +-------------------------+ | aside | | copy3 | | | | | +-----------+ +-------------------------+Medium-Breakpoint min-width: 30em and max-width: 60em +-----+ +-------------------+ |meta | | title | | | | | +-----+ +-------------------+ +---------------------------+ | copy1 | | | +---------------------------+ +---------------------------+ | copy2 | | | +---------------------------+ +---------------------------+ | media | | | +---------------------------+ +---------------------------+ | aside | | | +---------------------------+ +---------------------------+ | copy3 | | | +---------------------------+Small-Breakpoint min-width: 60em +---------------+ | title | | | +---------------+ +---------------+ | meta | | | +---------------+ +---------------+ | copy1 | | | +---------------+ +---------------+ | copy2 | | | +---------------+ +---------------+ | media | | | +---------------+ +---------------+ | aside | | | +---------------+ +---------------+ | copy3 | | | +---------------+The magazine style layout of Hawaii's Online Gaming Curse – The Outline built using CSS Grid and multi-column layout.

Given this was one of the first grid layouts I ever built I made some assumptions which later turned out to be false. I defined the grid-template-areas for all three breakpoints using the same number of columns. While you can do this you don’t have to. You can completely modify the template by changing the values for the grid-template-areas, grid-template-columns and grid-template-rows. The other option you have is to change the area that an element is placed in. More on that in the next example.

Example 2 – BBC Sport Live Football

For this example I recreated the BBC Sport Live Reporting page. Below you can see the schematic for the large breakpoint layout on the right and its grid template representation on the left. And yes, you can use emoji for template area names!

grid-template-areas: "✈️ ✈️ ✈️ ✈️ ✈️" "⚽️ ⚽️ ⚽️ ⚽️ ⚽️" "🖼 🖼 🖼 🖼 🖼" ". 👀 📻 📊 ." ". 👀 📻 🤼 ."; Where: ✈️: Navigation ⚽️: Match Score 🖼: Media 👀: Summary 📻: Live Reporting 📊: Stats 🤼: Line-ups+-------------------------------------+ | Navigation | +-------------------------------------+ | Match Score | +-------------------------------------+ | | | Media | | | +--+------+-+-------------+-+------+--+ | | su | | | | | | | | mm | | | | | | | | ar | | Live | | stats| | | | y | | Reporting | | | | | +------+ | | +------+ | | | | | | | | | | | line | | | | | | -ups | | | | | | | | | | | | | | | | | | | | +-------------------------------------+BBC Sport Live Reporting page recreated with CSS Grid.

In the main content area there are three columns: summary (👀), live-reporting (📻) & stats/line-up (📊/🤼🏽‍). To centre this content I placed an unnamed spacer on either side. You can define unnamed areas using a sequence of one or more ..

At the medium break point live-reporting (📻), stats (📊) & line-up (🤼) get collapsed into a tab based interface (📋). We can do this by placing those items in the 📋 grid-area instead. And for the small breakpoint things collapse further.

grid-template-areas: "✈️ ✈️ ✈️ ✈️" "⚽️ ⚽️ ⚽️ ⚽️" "🖼 🖼 🖼 🖼" ". 👀 📋 ."; Where: ✈️: Navigation ⚽️: Match Score 🖼: Media 👀: Summary 🗂: Tabs 📋: Tab-Content+-------------------------------------+ | Navigation | +-------------------------------------+ | Match Score | +-------------------------------------+ | | | Media | | | +--+------+-+---+---+---+----------+--+ | | su | | | | | | | | | mm | +---+---+---+----------+ | | | ar | | | | | | y | | | | | +------+ | | | | | | | | | Tabs! | | | | | | | | | | | | | | | | | | +-------------------------------------+BBC Sport Live Reporting page recreated with CSS Grid.

Each entry in the live-reporting (📻) section is also using a grid layout, but I’ll let you discover that by reading through the code.

Conclusion

One of the main takeaways for me from these experiments was that CSS Grid removes the need for wrapper elements to create layouts. This flattens the markup and the main blocks of content generally end up being direct descendants of the grid container i.e. grid-items. Or at least, we should aim to have them be grid items. This then allows us to drastically modify how the content will be displayed by moving items anywhere within the grid.

You can find all my CSS Grid experiments in this CodePen collection. Also, I would highly recommend reading the CSS Grid Spec. It has a lot of amazing examples, detailed explanation of all concepts and it is not as daunting as you would think.

https://varun.ca/css-grid-2/
Draggable Elements with RxJS
I've written previously about making DOM elements draggable using a combination of mouse and touch events . Recently I discovered a more…
Show full content

I’ve written previously about making DOM elements draggable using a combination of mouse and touch events. Recently I discovered a more elegant way to achieve this using RxJS and Hammer.

Tools

Hammer JS needs no introduction. It is the go to library for supporting touch gestures. Plus it provides an abstraction over the browser events allowing us to handle mouse and touch at the same time.

RxJS is a reactive programming library for JavaScript. We will use it to convert events into an observable stream and for animations.

I’ve been using RxJS for a couple of years now. Mostly for state management with Angular and with redux-observable. I love working with observables. It allows me to write succinct and declarative code.

A few months ago David Khourshid introduced me to his library RxCSS and to the idea of using observables to create reactive animations. Reactive programming makes it really easy to convert events into data and drive animations. This pushed me to learn more about observables and discover lots of new patterns. In this post I am going to share one of those patterns.

⚠️ I am going to assume a basic understanding of RxJS. If you are new to RxJS then I would highly recommend reading David’s animated intro to RxJS first.

Drag Gesture

The drag gesture can be broken down into three stages: start, move & end. On start we grab the current location of the element. The move event provides the delta which we can use to move the element. Lastly, the end event provides us with a hook to do any kind of cleanup once the gesture has ended. Hammer’s Pan Recognizer provides us with panstart, panmove & panend events which work perfectly for the drag gesture.

Events to Observable

We start by creating a Hammer manager and configure it to handle pan in all directions. Rx.Observable.fromEvent allows us to convert events into an observable sequence. This one observable stream – pan$ – will allow us to subscribe to events for pan-start, pan-move and pan-end.

// Create a new Hammer Manager
const hammerPan = new Hammer(element, {
  direction: Hammer.DIRECTION_ALL,
});

hammerPan.get('pan').set({ direction: Hammer.DIRECTION_ALL });

// Convert hammer events to an observable
const pan$ = Rx.Observable.fromEvent(hammerPan, 'panstart panmove panend');
Composing the Drag Observable

For the drag gesture we want to create an observable stream such that it emits values from the pan-move event once the pan-start event has been triggered and then stops emitting those values once the pan-end event is triggered.

drag$panStart$panMove$panEnd$⬇️🔁🔁🔁🔁🔁🔁🔁🔁🔁⬆️
Visualization of the drag observables. Generated using rxviz.com

The filter operator allows us to filter values based on a provided condition. We can use this to target specific events. For example, pan$.filter(e => e.type === 'panstart') to subscribe only to pan-start events. Then to generate the drag$ observable we then need to combine panstart$, panmove$ & panend$ in the following pattern:

const drag$ = panstart$.switchMap(() =>
  panmove$.map(calculateNewPosition).takeUntil(panend$)
);

Let’s break this down step by step. panstart$ is the observable that is driving the whole thing. When it emits the first value it switches to the panmove$ observable. This switching is done using the switchMap operator. The panmove$ observable then starts emitting the location values. We can tell it to stop when panend$ emits a value by chaining on the takeUntil operator. Therefore, all subscribers to drag$ only ever receive location values. You can see a simulated visualization of this setup here.

Now that we understand the basic structure we can flush out the details. The panmove event only provides delta values. To calculate the absolute position we need to start by getting the initial location. In this example I am getting that information from the element itself. To provide a cleanup hook we can subscribe to the move$ observable and handle it via the onComplete callback.

// Generates the drag$ observable
const drag = ({ element, pan$ }) => {
  const panStart$ = pan$.filter(e => e.type === 'panstart');
  const panMove$ = pan$.filter(e => e.type === 'panmove');
  const panEnd$ = pan$.filter(e => e.type === 'panend');

  panstart$.switchMap(() => {
    // Get the starting point on pan-start
    const start = {
      x: +element.getAttribute('cx'),
      y: +element.getAttribute('cy'),
    };

    // Create observable to handle pan-move and stop on pan-end
    const move$ = panmove$
      .map(pmEvent => ({
        x: start.x + pmEvent.deltaX,
        y: start.y + pmEvent.deltaY,
      }))
      .takeUntil(panend$);

    // We can subscribe to move$ and handle cleanup in the onComplete callback
    move$.subscribe(null, null, () => {
      /* Handle cleanup when pan ends */
    });

    return move$;
  });
};

The pattern I shared above is based on the dragndrop example from the RxJS documentation.

Scaling to Canvas

Quite often I end up having to limit the element to a parent container. For example, a <circle> that can only be dragged within the <svg> container where the cx and cy values need to be calculated in the viewBox coordinate system.

This is essentially a global to local coordinate transform. With SVG this can get slightly tricky depending on how you want the SVG to scale. I generally prefer preserveAspectRatio="xMidYMid slice". This makes the SVG grow until it entirely covers the container – very similar to background-size: cover.

{% include diagrams/svg-scaling.html %}

Therefore, we can figure out how much the SVG has scaled using the aspect ratio of the container. Then use that value to map the viewport based coordinates to the SVG coordinate system.

function scaleToCanvas({ start: { x, y }, w, h }) {
  const svgW = w > h ? VIEWBOX_SIZE.W : (VIEWBOX_SIZE.W * w) / h;
  const svgH = w > h ? (VIEWBOX_SIZE.H * h) / w : VIEWBOX_SIZE.H;

  return e => ({
    x: x + mapFromToRange(e.deltaX, 0, w, 0, svgW),
    y: y + mapFromToRange(e.deltaY, 0, h, 0, svgH),
  });
}

function mapFromToRange(x, x1, x2, y1, y2) {
  return (x - x1) * ((y2 - y1) / (x2 - x1)) + y1;
}

And here’s the complete example:

Smooth Motion

In the example above you’ll notice that the motion is somewhat rigid. The circle is stuck to the pointer and instantly stops wherever the pointer stops. We can make this better by adding smooth motion. This will also provide a bit of momentum to the circle.

For smooth motion I am using the LERP-ing technique. It is described in detail by David Khourshid in the An Animated Intro to RxJS article I mentioned earlier. The gist of it is that instead of using the drag$ observable directly we combine it with an animation timer. This allows us to smooth out the motion by using linear interpolation. However, we still have the possibility to subscribe to drag$ if we want access to the raw location.

Here’s the final version with smooth motion.

Conclusion

You can see the power of RxJS here. We were able to convert events into observable streams. Then we composed those streams to create the drag$ observable. And finally we added the animation layer to smooth out the motion. The code is quite declarative. Each layer that we created is modular and can be easily composed to create complex scenarios – for example see the demo below and the one at the top of the page. Looking for more inspiration? Checkout CodePen for many more examples.

https://varun.ca/drag-with-rxjs/
Learning CSS Grid
CSS Grid is here! As of March 2017, all major browsers support it. Given this good news I decided to spend some time to better understand…
Show full content
+-------------------------+ +---+ | | | | | CSS Grid | | | | Layout Module | | | | | | | +-------------------------+ | | +----+ +------------------+ | | | | | | | | | | | | | | | | +------------------+ +---+ | | +------+ +---------------+ | | | | | | +----+ +------+ +---------------+

CSS Grid is here! As of March 2017, all major browsers support it. Given this good news I decided to spend some time to better understand how to use it. The learning curve was much steeper than I expected – the module introduces something like 17 new concepts. It was quite overwhelming to take it all in, so I started breaking things down, taking notes and making demos, which I am going to share here. This is by no means an exhaustive guide. It might not even be 100% factually correct. But this should be enough information for you to form a basic mental model of how CSS Grids work and start building layouts with it.

Terminology

Before we start creating grid layouts we need to learn a few new terms. A Grid Container is an element on which display: grid has been applied. All its direct descendants become Grid Items.

When we define the grid we specify the number of rows and columns we want. The grid layout system then generates numbered lines which divide the grid horizontally and vertically. These are known as Grid Lines. The space between any two adjacent grid lines is called Grid Track. This is essentially a generic term for columns and rows.

Grid Lines +---+---+---+---+---+---+ 1 | | | | | | | | | | | | | | | | | | | | | +-------+---+-----------+ 2 | |xxx| | | | | | |xxx|<-- Grid Cell | | |xxx| | | | | +-----------------------+ 3 | | | | | | | | | | | | | | | | | | | | | +---+---+---+---+---+---+ 4 1 2 3 4 5 6 7Row Tracks +---+---+---+---+---+---+ |xxxxxxxxxxxxxxxxxxxxxxx| |xxxxxxxxxxxxxxxxxxxxxxx| |xxxxxxxxxxxxxxxxxxxxxxx| +-----------------------+ | | | | | | | | | | | | | | | | | | | | | +-----------------------+ | | | | | | | | | | | | | | | | | | | | | +---+---+---+---+---+---+Column Tracks +---+---+---+---+---+---+ |xxx| | | | | | |xxx| | | | | | |xxx| | | | | | +xxx--------------------+ |xxx| | | | | | |xxx| | | | | | |xxx| | | | | | +xxx--------------------+ |xxx| | | | | | |xxx| | | | | | |xxx| | | | | | +---+---+---+---+---+---+The grid gives us numbered lines to use when positioning items. A grid track is the space between any two lines on the grid.

A Grid Cell is the smallest unit on the grid. It is the intersection of a row and column. Conceptually it is quite similar to a table cell.

Grid Area is the space created by the intersection of four grid lines. In other words, it is a collection of one or more adjacent grid cells. The grid area can only be rectangular! It is not possible to create T- or L-shaped grid areas. Which for some reason was the first thing I wanted to do.

Grid Area +---+---+---+---+---+---+ | | | | | | | | | | | | | | | | | | | | | +---+---+-----------+---+ | | |xxxxxxxxxxx| | | | |xxxxxxxxxxx| | | | |xxxxxxxxxxx| | +---+---|xxxxxxxxxxx|---+ | | |xxxxxxxxxxx| | | | |xxxxxxxxxxx| | | | |xxxxxxxxxxx| | +---+---+-----------+---+Grid items can span one or more cells – by row or by column – this is called a grid area.Defining a Grid

There are many different ways to define a grid. I’m going to start by focusing on a basic scenario: 4 columns x 5 rows. grid-template-rows & grid-template-columns allow us to define the count and sizing of rows and columns respectively. We can list out the tracks as shown in the example below.

.my-grid-container { display: grid; grid-template-columns: 20px 20px 20px 20px; grid-template-rows: 20px 20px 20px 20px 20px; }.my-grid-container +---+----+----+----+-------------+ | | | | | | | | | | | | +---+----+----+----+ | | | | | | | | | | | | | +---+----+----+----+ | | | | | | | | | | | | | +---+----+----+----+ | | | | | | | | | | | | | +---+----+----+----+ | | | | | | | | | | | | | +---+----+----+----+ | | | | | | | | | +--------------------------------+A basic grid with 4 columns and 5 rows. The rows are defined using grid-template-rows and columns using grid-template-columns.

We have a grid ✅ Not the most useful grid ever, but a grid nonetheless. In the above example, each row is 20px tall and each column is 20px wide. The one thing to note here is that grid-template-rows & grid-template-columns allow us to define the grid tracks. The browser then generates the grid lines automatically. These lines are invisible. They are there to help us with positing items on the grid, but do not impact our design visually. That said, being able to see these lines is extremely helpful for debugging. Luckily for us Firefox has a built-in Grid Inspector which visualizes the grid for us 🙌🏽

In this example, the grid has fixed track sizes. Which is a fancy way of saying that rows and/or columns have fixed widths. Therefore, the grid will remain the same size regardless of how big/small the container is.

Flexible Grids

We can also define grids with flexible track sizes. Or even have both fixed and flexible tracks at the same time! (See the example below.) Here percentage values refer to a percentage of the grid container.

.my-weird-but-flexible-grid { display: grid; grid-template-columns: 20px 5% 80px 40%; grid-template-rows: 4rem 2vh 12% 80px 20em; }.my-weird-but-flexible-grid +-+--+----+----------------------+ | | | | | | +-+--+----+-----------------+ | +-+--+----+-----------------+ | | | | | | | | | | | | | +-+--+----+-----------------+ | | | | | | | +-+--+----+-----------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-+--+----+-----------------+ | | | | | | | | | | | +--------------------------------+A flexible grid where we are using a mixture of units for track measurements such as, pixels, percentage, viewport units and ems.The fr Unit

Let’s look at another example of a flexible grid. In this case we are still building a 4x5 grid, but we want the rows and columns to stretch out and fill the entire container. To do this, we will use the fr unit.

At this point you are probably asking yourself. The what unit? 🤷🏽‍♂️

.my-grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr 1fr 1fr; } /* The above can also be written as: grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(5, 1fr); */.my-grid +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | | | | | | +------+------+------+------+A flexible grid with 4 columns and 5 rows. It is made flexible by setting the rows and columns to 1fr.

We have a new unit! The fr unit represents a fraction of the available space in the grid container. Have you ever used flex: 1 & flex: 2, etc? This works exactly like that. You can specify track sizing as a ratio of free space, eg: 1fr 2fr.

📝 We can define track sizing as length, percentage or fr.

Gutters

It wouldn’t be a real grid without some gutters. For this we can use grid-column-gap and grid-row-gap properties, or the shorthand grid-gap. The grid only generates gutters between the tracks. There is no gutter before the first track or after the last track.

⚠️ Even though there is a gap between two adjacent tracks there is still only one grid line.

.my-grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr 1fr 1fr; grid-gap: 1rem; }.my-grid 1 2 3 4 5 +----+-+----+-+----+-+----+ 1 | | | | | | | | | | | | | | | | +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ 2 | | | | | | | | | | | | | | | | +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ 3 | | | | | | | | | | | | | | | | +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ 4 | | | | | | | | | | | | | | | | +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ 5 | | | | | | | | | | | | | | | | +----+-+----+-+----+-+----+ 6A flexible 4x5 grid with gutters. Gutters can be created by using grid-gap property.Positioning Grid Items

Now that we have defined a grid, let’s explore some options for how to place items on it. Much like flexbox, grid placement is not dependent on source order. This allows us to create complex layouts that can be drastically re-arranged using media queries. More on that later. Let’s focus on some basics for now.

Positioning items on a grid is quite similar to using absolute positioning. Except, instead of specifying locations in measurement units we specify start and end grid lines.

.my-grid 1 2 3 4 5 +----+-+----+-+----+-+----+ 1 | | | | | | | | | | | | | | | | | | | | | | | | grid-row-start (2)-------------> o-----------o <-----(5) grid-column-end grid-column-start (3) | | | | |xxxxxxxxxxx| | | | | |xxxxxxxxxxx| | | | | |xxxxxxxxxxx| +----+ +----+ |xxxxxxxxxxx| | | | | |xxxxxxxxxxx| | | | | |xxxxxxxxxxx| | | | | |xxxxxxxxxxx| grid-row-end (4)------------------> o-----------o 4 | | | | | | | | | | | | | | | | | | | | | | | | +----+ +----+ +----+ +----+ 5 | | | | | | | | | | | | | | | | | | | | | | | | +----+-+----+-+----+-+----+ 6We can position items on the grid using grid-column-start, grid-column-end and grid-row-start, grid-row-end.

Grid lines are automatically numbered. Starting with 1 for the topmost and leftmost lines. We use these numbers to declare grid-column-start / grid-column-end and grid-row-start / grid-row-end. We can also start counting from the end. For that we start with -1 for the rightmost and topmost lines. Lastly, we can also specify these locations as spans. For example, grid-row-end: span 3 means that the end location is starting grid line + 3 tracks.

Below is a CodePen that demonstrates a few different positioning styles. Notice how grid items can overlap. We can use the z-index property to control the stacking order.

🤖 Automate All the Things

I want to end by showcasing a couple more concepts through an example. I am going to replicate this product grid from the Aldo website. A few things to note here:

  • It is a 4x5 grid on large devices (> 60em)
  • There are 13 items placed on it
  • Some items span 2 columns and/or 2 rows
  • All images have a ratio of 1000 : 1270
  • There is a 1rem gutter and a 1rem padding all around the grid
  • The maximum width of the grid is restricted to 60em

The grid also changes based on break points. It reduces to 3 columns on medium-sized devices (30em ↔️ 60em) and 2 columns on small devices < 30em. Finally, we need to maintain 1000 : 1270 ratio between the row and column track sizing.

Large-Breakpoint min-width: 60em (4 columns) +-----------+ +-----------+ | wide 2 | | | | | | | +-----------+ | wide 2 | +----+ +----+ | tall 2 | | | | | | | | | | | | | +----+ +----+ +-----------+ +----+ +----+ +----+ +----+ | | | | | | | | | | | | | | | | +----+ +----+ +----+ +----+ +-----------+ +----+ +----+ | | | | | | | | | | | | | wide 2 | +----+ +----+ | tall 2 | +----+ +----+ | | | | | | | | | | | | +-----------+ +----+ +----+ |<----max-width: 60em---->|Medium-Breakpoint min-width: 30em and max-width: 60em (3 columns) +------------------+ | wide 3 | | | +------------------+ +-----------+ +----+ | | | | | | | | | wide 2 | +----+ | tall 2 | +----+ | | | | | | | | +-----------+ +----+ +----+ +----+ +----+ | | | | | | | | | | | | +----+ +----+ +----+ +-----------+ +----+ | | | | | | | | | wide 2 | +----+ | tall 2 | +----+ | | | | | | | | +-----------+ +----+ +----+ +----+ +----+ | | | | | | | | | | | | +----+ +----+ +----+Small-Breakpoint min-width: 60em (2 columns) +-----------+ | wide 2 | | | +-----------+ +-----------+ | | | | | wide 2 | | tall 2 | | | | | +-----------+ +----+ +----+ | | | | | | | | +----+ +----+ +----+ +----+ | | | | | | | | +----+ +----+ +----+ +----+ | | | | | | | | +----+ +----+ +-----------+ | | | | | wide 2 | | tall 2 | | | | | +-----------+ +----+ +----+ | | | | | | | | +----+ +----+ +----+ +----+ | | | | | | | | +----+ +----+The aldo style product grid. 2 columns on small devices, 3 on medium and 4 on large devices.Defining the Row and Column Sizes

Previously, we used something like grid-template-columns: repeat(4, 1fr) and grid-template-rows: repeat(5, 1fr) to generate the grid. Because of the 1000 : 1270 ratio we can no longer do that. Instead, we need to use a bit of math to figure out the row and column sizes. For this we can use CSS variables and calc 🏄 We can calculate the width of the columns and height of the rows using this formula:

width = (width_of_grid - (gutters + padding)) / number_of_columns;
height = (1270 * width) / 1000;
Break PointWidth of GridGutters + PaddingNumber of ColumnsDefault (less than 30em)100vw(1 + 2)rem2min-width: 30em and
max-width: 60em100vw(2 + 2)rem3min-width: 60em60em(3 + 2)rem4

So, for the default case width = (100vw - 3rem) / 2. Awesome! Let’s put it all together. We start by defining the default value in :root. Then at each break point we recalculate width and update the grid-template-columns property.

:root {
  --width: calc((100vw - 3rem) / 2);
  --height: calc(1270 * var(--width) / 1000);
}

.layout {
  grid-gap: 1rem;
  grid-template-columns: repeat(2, var(--width));
  grid-auto-rows: var(--height);
}

@media screen and (min-width: 30em) and (max-width: 60em) {
  :root {
    --width: calc((100vw - 4rem) / 3);
  }
  .layout {
    grid-template-columns: repeat(3, var(--width));
  }
}

@media screen and (min-width: 60em) {
  :root {
    --width: calc((60em - 5rem) / 4);
  }
  .layout {
    grid-template-columns: repeat(4, var(--width));
  }
}
Auto Rows

In the code snippet above you might have noticed grid-auto-rows: var(--height). This tells the browser to automatically generate just enough rows to fit all the items. So if we add more items, it will add more rows and vice versa. The value for grid-auto-rows is what we want the height of the row to be.

The CodePen below demonstrates what the grid would look like at this point. Open it in the edit mode to see how the resizing works and what happens when you add or remove items.

📝 Did you notice that we did not have to place the items on the grid?! If you don’t specify positioning then the browser automatically places items on the grid. This is known as auto flow.

Item Spans ↔️

As I mentioned earlier, some items span multiple tracks. For this we can use the grid-<row|column>-end: span <number> technique. The span size for the images remains the same across all break points. However, the product info spans 3 columns on medium-sized devices and 2 columns for all other breakpoints. Therefore, we apply wide-2 wide-3-m to it. The other items use wide-2 tall-2.

.wide-2 {
  grid-column-end: span 2;
}
.tall-2 {
  grid-row-end: span 2;
}

@media screen and (min-width: 30em) and (max-width: 60em) {
  .wide-3-m {
    grid-column-end: span 3;
  }
}

Want to learn more about auto placement? Check out Rachel Andrew’s fantastic tutorial and demo.

Dense Packing

We have a grid. We used auto placement to position items and some items span multiple columns & rows. So we’re done? Not quite. If you inspect the above CodePen in edit mode you will notice that the grid ends up with holes at certain break points.

holes in the grid

This is because by default auto placement uses a sparse packing algorithm which can lead to holes in the layout. The good news is that we can switch to a dense packing algorithm which causes the browser to backtrack and fill any empty cells in the layout party parrot

.layout {
  grid-gap: 1rem;
+ grid-auto-flow: dense;
  grid-template-columns: repeat(2, var(--width));
  grid-auto-rows: var(--height);
}

That’s it! Below is the final version. And here’s another example that uses auto layout and dense packing to display music albums.

Conclusion

Take a moment to think about the possibilities 🤔 Think of all the crazy layouts we can build now. Can’t think of any? Not a problem. Hop on over to Jen Simmons’s Layout Lab for some ideas. CSS Grids will likely have a drastic impact on web design. All those layouts you abandoned because they were not possible with CSS will likely be possible thanks to CSS Grids.

What Next? Did you know you can name grid lines? Did you know you can align items in a track using align and justify properties? Did you know you can specify sizes as a range using minmax? There is so much more to discover! I know it can feel a bit overwhelming. Take little steps. Build things. Experiment. It’s okay to not understand everything at once.

Further Reading
https://varun.ca/css-grid/
Git Rebase
What is a rebase? Assume the following git history where you are working on the my-feature branch. After git rebase master this would…
Show full content
What is a rebase?

Assume the following git history where you are working on the my-feature branch.

          A---B---C my-feature
         /
    D---E---F---G master

After git rebase master this would become:

                  A--B--C my-feature
                 /
    D---E---F---G master
  • git rebase origin/master rebases your current branch against master
  • git rebase -i origin/master is the interactive version which allows you to do things such as squashing commits.
Workflow for Single Remote

This is assuming:

  1. Everyone on your team is working from one repository.
  2. You are currently on a feat/chore/fix branch and not on master.
git fetch origin
git rebase -i origin/master

At this point the editor will launch and will allow you to pick commits for squashing:

rebase starts

We are going to squash all commits into one. Leave the first commit untouched and convert all the other pick into squash or s. Save and close the editor window.

squash the commits

If no conflicts were encountered the rebase will complete and you move to the final step. Your editor will open up again. This time it shows you the messages from the various commits that are going to be squashed. Here you get a chance to compose the message for the new squashed commit.

  • Use a descriptive commit message.
  • If you have a Pivotal Tracker, JIRA, or Github issue number for the feature, reference it in the commit.

all the commit messages

Save and close the editor window once you have composed a new git message.

new commit message

At this point the rebase is complete 🎉 Last step, push your changes to remote.

$ git push origin my-feature -f

Yes, that’s a force push there. Because you have successfully re-written git history. Where you previously had multiple commits you now only have one commit.

Resolving Conflicts

During the rebase process you might encounter conflicts.

conflitc

Relax. Take a deep to breath. We can deal with this 💪

Start by running git status this will show you a list of files that have conflicts. Go through them one by one and resolve conflicts.

The conflicts show up something like this (there can be more than one in a file):

  This is the first line
  This is the second line
  This is the third line I added
<<<<<<< HEAD
  Someone else added this line
  Someone else added this line too
=======
  This is the fourth line I added
  This is the fifth line I added
>>>>>>> f78d8ae... <commit message>

Here everything between <<<<<<< HEAD and ======= is stuff that is in the master branch. And everything between ======= and >>>>>>> f78d8ae... <commit message> is your on the my-feature branch. Git could not automatically merge these so it wants you to decide how these lines should be merged.

You can pick master version or your version depending on what you were implementing. You are the best judge here. Sometimes the correct answer will be a combination of the two.

Pick the appropriate combination and delete the conflict markers (<<<<<<< HEAD, =======, etc).

  This is the first line
  This is the second line
  This is the third line I added
  Someone else added this line
  This is the fourth line I added
  This is the fifth line I added

Do the same for all the other files with conflicts. Then:

  1. Stage the changes by executing git add <file-name>
  2. Continue the rebase git rebase --contine

If you messed something up you can always git rebase --abort. Remember to rebase early and often. This will prevent messy merge conflicts.

Workflow for Multiple Remotes

This is assuming:

  1. There is a main repository that you can only update through pull requests. We will call this remote upstream.
  2. You create a fork of this repository and clone that locally. We will call your forked remote origin.

The setup process will be something like this:

$ git clone git@github.com:[your user name]/[your repo].git
$ cd [your repo]
$ git remote add upstream git@github.com:rangle/[your repo].git

The only difference between this setup and previous is that you will be fetching upstream and rebasing against upstream/master. Then push to your forked origin. The actual rebase & conflict resolution process is the same.

Let us break this down step-by-step:

# (1) Start by updating your fork from the main repo.
# At this point you are on the master branch.
$ git fetch upstream
$ git rebase upstream/master

# (2) Create a new branch for your feature
$ git checkout -b my-feature

# (3) Implement the feature and commit your changes as usual.
# This might be single or multiple commits.

# (4) Push your changes to origin
$ git push origin my-feature

# (5) Before we rebase we must get the latest upstream
$ git fetch upstream

# (6) Next, do the actual rebase. This point onwards it is the
# same process as single remote workflow above.
$ git rebase upstream/master

# (7) Push your changes to origin not upstream!
$ git push origin my-feature -f
Github

If you are using github then a lot of this stuff can be done through their GUI. Read more about it here. github squash and rebase features

Undo

Scenario: I messed up conflict resolution during a rebase, pushed changes to remote and lost hours of work 😭

i've made a huge mistake

Use git reflog to undo your rebase. Running the command will show you the reflog for your local repository. For example:

adc164e HEAD@{0}: rebase -i (finish): returning to refs/heads/my-feature
adc164e HEAD@{1}: rebase -i (pick): Bugfix: SVG icons on android
8ab621c HEAD@{2}: rebase -i (start): checkout master
067be75 HEAD@{3}: checkout: moving from master to my-feature
8ab621c HEAD@{4}: pull: Fast-forward

In this scenario to undo my rebase I need to run git reset --hard HEAD@{3} because that was the last state before I started the rebase.

https://varun.ca/rebase/
Component based SVG Icon System
Much has been written about SVG Icon Systems and why they are amazing . In this post I want to share my workflow for creating a component…
Show full content

icons

Much has been written about SVG Icon Systems and why they are amazing. In this post I want to share my workflow for creating a component based SVG icon system.

The focus here is on front-end JavaScript frameworks such as React, Angular, Vue.js, etc. These frameworks allow us to split the UI into discrete, reusable components. Which means we can create a generic icon component. And use the type property to render inline the appropriate icon.

<!-- React -->
<Icon type="cloud-with-snow" className="f4 blue" />
<!-- Angular -->
<svg icon type="cloud-with-snow" class="f4 blue"></svg>
<!-- Vue.JS -->
<icon type="cloud-with-snow" class="f4 blue"></icon>
Prepare SVG Files

Our starting point will be .svg files – one per icon. In most cases these will be generated using an app such as Illustrator or Sketch, although you can also obtain them from an icon pack, for example: Material Design icons, iconmonstr, SVG Icons, etc.

folder full of SVG icon files

I like to start by running all the files through GitHub - svg/svgo or SVGOMG. This not only optimizes the SVG but also strips out any editor artifacts and tries to reduce it to a single <path>.

Take a moment to visually check all your icon files. You might have to play with the SVGO settings, especially the precision setting, to ensure that the optimized version does not get distorted.

Next we are going to make a couple of modifications to all the .svg files.

  1. Ensure that all the icons use viewBox and remove any width or height attributes. You can configure SVGO to do this for you automatically. This will make it easier to control the size of the icon.

  2. Set the fill and stroke (or whichever of the two you are using) to currentColor. This sets the icon colour to be the same as the surrounding text. We can then control this colour by setting the color property on the icon element.

At this point the SVG files should look something like this:

<svg viewBox="0 0 20 20">
  <path fill="currentColor" d="..." />
</svg>
Generate the Sprite Sheet

One of the more popular techniques for implementing an icon system is to use <symbol> and <use> elements.

An improvement is to use the <symbol> element in SVG instead of directly referencing shapes (or a <g>), because you can define the viewBox directly on the <symbol> and then not need one when you <use> it later in an <svg>.

— Chris Coyier (SVG symbol a Good Choice for Icons)

The <symbol> element allows us to define an SVG template. It is never displayed. Therefore, we can use it to create an icon sprite sheet.

<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  style="position:absolute;width:0;height:0;visibility:hidden"
>
  <defs>
    <symbol id="sun" viewBox="0 0 20 20">
      <path fill="currentColor" d="..." />
    </symbol>
    <symbol id="moon" viewBox="0 0 20 20">
      <path fill="currentColor" d="..." />
    </symbol>
  </defs>
</svg>

We can then render an icon using the <use> element.

<svg>
  <use xlink:href="moon" />
</svg>

If you are working with React or Angular or Vue.js then there is a good chance that your project was setup using webpack. We will use the svg-sprite-loader for webpack to convert a folder full of SVG files into an icon sprite sheet.

const files = require.context('!svg-sprite-loader!./assets', false, /.*\.svg$/);
files.keys().forEach(files);

To do so:

  1. We are using require.conext to generate a list of SVG files in the assets folder.

  2. We then iterate over this list and load all the files using svg-sprite-loader.

  3. svg-sprite-loader then generates the sprite sheet and injects it into DOM on run-time. Similar to how style-loader works.

Injected sprite sheet

⚠️ Not using webpack? There are also node, gulp or grunt based tools that you can use as an alternative.

Icon Component

Time to put everything together and build the icon component. Below is the React version of the component – the Angular and Vue.js versions are quite similar. For the <svg> element I have set display to inline-block and verticalAlign to middle. I am using tachyons for styling here; however, you can set those styles using any technique.

import React from 'react';
const files = require.context('!svg-sprite-loader!./assets', false, /.*\.svg$/);
files.keys().forEach(files);

const Icon = ({ type, className }) => (
  <svg className={`dib v-mid ${className}`} width="1em" height="1em">
    <use xlinkHref={`#${type}`}></use>
  </svg>
);

export default Icon;

The width & height attributes are set to 1em. If needed, adjust the values based on the aspect ratio of your icons. This will give us more flexibility to control the size of the icon.

The component itself has two props:

  1. type: to pick which icon needs to be rendered.
  2. className: to allow us to add more CSS classes to <svg> element.

The following will render a cloud-with-snow icon.

<Icon type="cloud-with-snow" />
Colour

We can control the colour of the icon by using the font-color. The following will render a green rainbow icon.

<Icon type="rainbow" className="green" />
Size

We have two options for setting size of the icon:

Using font-size: this works great when you are rendering the icon next to some kind of text. For example, in a paragraph or a button. Because we set the width & height attributes to 1em the icon scales to match the font size. The following will render a blue wind icon that is 1.25rem tall.

<Icon type="wind" className="f4 blue" />

In some scenarios you might want to set the size of the icon manually instead of relying on font-size. SVG attributes have the lowest specificity so, you can always override them using CSS. The following will render a yellow orbit icon that is 4rem wide and tall.

<Icon type="orbit" className="w3 h3 yellow" />
Usage with Base Element

The <base> element specifies the base URL to use for resolving all the relative URLs in an HTML document. On some browsers this prevents the icons from rendering 😞 We can fix this by using absolute values for xlinkHref.

const baseUrl = window.location.href.replace(window.location.hash, '');
const xlinkHref = baseUrl + `#${type}`;
— [svgfixer.js](https://gist.github.com/leonderijke/c5cf7c5b2e424c0061d2)

I recently encountered this issue when working with the Angular router. It relies on the <base> element being set. However, you can provide APP_BASE_HREF instead and still use the router and the SVG sprite sheet.

🍞 Full source for React, Vue.js & Angular versions.

https://varun.ca/icon-component/
Touch and Mouse Together
If you have built a web app in the past few years, you've probably had to deal with touch events. In many cases this was limited to handling…
Show full content

If you have built a web app in the past few years, you’ve probably had to deal with touch events. In many cases this was limited to handling tap and removing that pesky 300ms delay. However with touch devices becoming more powerful, we now have to implement more complex gestures— gestures that work for both mouse and touch.

chat-head demo

In this post we will walk through the process of implementing one such gesture– pan. I will demonstrate this by building a draggable chat-head component.

Note: This will not be the most robust implementation of a draggable component. It is intended to demonstrate how one would handle touch and mouse event simultaneously.

This example consists of three components:

1. App Component

The root node of the application.

function App() {
  return (
    <Draggable>
      <ChatHead src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/149125/profile.jpg" />
    </Draggable>
  );
}
2. ChatHead

A stateless component that renders a circular avatar.

function ChatHead({ src, ...props }) {
  return (
    <img
      draggable="false"
      src={src}
      style={{ transition: 'all 300ms ease-in-out' }}
      className="br-100 pa1 ba b--black-10 h3 w3"
      alt="chat head"
      {...props}
    />
  );
}
3. Draggable

A stateful component for making components draggable. To achieve this, we start by wrapping children with a div. This will act as a draggable container.

class Draggable extends React.Component {

  constructor() {
    super();
    this.state = {
      x: window.innerWidth / 2,
      y: window.innerHeight / 2,
      dragging: false,
    };
  }

  onPanStart = e => { ... };
  onPan = e => { ... };
  onPanEnd = e => { ... };

  render() {
    const { x, y, dragging } = this.state;
    const { children } = this.props;

    return (
      <div draggable="true"
        className="dib move"
        style={ {
          display: 'inline-block',
          cursor: 'move',
          WebkitTransform: `translate3d(${ x - 32 }px, ${ y - 32 }px, 0)`,
          transform: `translate3d(${ x - 32 }px, ${ y - 32 }px, 0)`,
        } }
        onTouchStart={ this.onPanStart }
        onDragStart={ this.onPanStart }
        onDrag={ this.onPan }
        onTouchMove={ this.onPan }
        onTouchEnd={ this.onPanEnd }
        onDragEnd={ this.onPanEnd}>
        { children }
      </div>
    );
  }
}
Pan Gesture

The pan gesture can be broken down into three stages: pan start, pan & pan end. On desktop these map quite nicely to the drag event handlers, while for touch devices we will have to use touch events. In the end we have three event handlers:

  • Pan Start:

    onTouchStart={ this.onPanStart }
    onDragStart={ this.onPanStart }
  • Pan:

    onTouchMove={ this.onPan }
    onDrag={ this.onPan }
  • Pan End:

    onTouchEnd={ this.onPanEnd }
    onDragEnd={ this.onPanEnd}>
breakdown of the drag gesturePan Start

The onPanStart handler is primarily responsible for setting the dragging state to true.

onPanStart = e => {
  if (e.type === 'dragstart') {
    e.dataTransfer.setDragImage(getDragImage(), 0, 0);
  }
  this.setState({ dragging: true });
};

When you drag an element on desktop you end up with a ghost image. This is known as the drag image. To get around this we can set the drag image to a fake 0px × 0px image.

ghost image that appears while dragging on desktop
function getDragImage() {
  let img = new Image();
  img.src = 'fake.gif';
  return img;
}
Pan

The onPan handler allows us to get the drag location and update the x, y coordinates in state. Again, we have to account for both touch and mouse events here.

onPan = e => {
  if (e.clientX <= 0 || e.clientY <= 0) return false;
  this.setState(getPan(e));
};

For drag events we just have one location for the mouse. Therefore, the drag location is e.clientX & e.clientY.

For touch events we receive a list of touches instead. In this scenario we only care about the first touch which is responsible for panning. We can access that at e.targetTouches[0].

function getPan(e) {
  if (e.type.includes('drag')) {
    return { x: e.clientX, y: e.clientY };
  }

  const touch = e.targetTouches[0];
  return { x: touch.clientX, y: touch.clientY };
}
Pan End

Finally, the onPanEnd handler is responsible for setting the dragging state to false.

onPanEnd = e => {
  this.setState({ dragging: false });
};

Here is the final result:

Pointer events

The upcoming Pointer events spec aims to unify all input devices – such as a mouse, pen/stylus or touch – into a single model. This will simplify the implementation process for us developers and allow us to provide a good user experience regardless hardware choices.

https://varun.ca/touch-and-mouse-together/
Flattening Deep Hierarchies of Components
Components are an awesome tool for building interfaces. They allow you to break down the UI into distinct reusable elements. These can then…
Show full content

Components are an awesome tool for building interfaces. They allow you to break down the UI into distinct reusable elements. These can then be composed to build complex applications in a more sustainable way.

Each component has its own well defined public API. In React this is defined by the component props. Where as, in Angular 2 it’s inputs and outputs. The choice of this API can have a drastic impact on your application. Consider the following example of a Card component.

Basic card with a title

It has one job: display an image with a title. You might be tempted to build it out such that it abstracts away all the logic for its constituent parts: image, title, etc.

<Card
  img={'img/rotary-phone.png'}
  title={'Choosing the Right Antique Rotary Phone for You.'}
  contentPaddingX={2}
  contentPaddingY={2}
/>

As the complexity of your app grows you might introduce other scenarios for this Card component. The card should now be able to display a caption or an icon with a click action?

Card with other requirements such as caption and icon

// Card with title and caption
<Card
  img={'img/rotary-phone.png' }
  title={'Choosing the Right Antique Rotary Phone for You.'}
  caption={'21 hours ago'}
  contentPaddingX={2}
  contentPaddingY={2}
/>

// Card with title, caption and icon
<Card
  img={'img/rotary-phone.png'}
  title={'Choosing the Right Antique Rotary Phone for You.'}
  caption={'21 hours ago'}
  contentPaddingX={2}
  contentPaddingY={2}
  icon={'add-to-cart'}
  iconAction={addToCart}
/>

You now have to add more logic to this component. It has to account for all these permutations and combinations of its constituent parts. You can probably see the issue here. What started as a simple component has bloated into something that is hard to maintain, hard to test and not at all flexible.

We can write better components by adopting an approach similar to function composition.

Function Composition

Function composition is the act of combining simple functions to build more complicated ones.

f(g(h(x)) = compose(f, g, h)(x);

Consider the following example:

function complexFunction(x) {
  const a = 2 * x;
  const b = a + 5;
  const c = b / 2;
  const d = c * 12;
  return d;
}

Here the complexFunction is very similar to what we attempted with the Card component. This one function is trying to do too much. We can achieve the same behaviour using compose instead:

function add(r, x) {
  return x + r;
}
function multiply(r, x) {
  return x * r;
}
function divideBy(r, x) {
  return x / r;
}

const complexFunction = compose(
  multiply(12),
  divideBy(2),
  compose(
    add(5),
    multiply(2)
  )
);

Here we have broken out the various math operations into pure functions. We can now compose these functions to create the complexFunction or any other type of combination.

Instead of having one massive function which accounts for various scenarios we have created a toolbox of smaller single responsibility functions. Testing this is much easier since you don’t have to account for the different scenarios.

Applying Function Composition to Components

Let us apply this same principle to the Card component. We start by breaking out its sub-components:

  • Media: a generic image component
  • Body: body container for the card
  • Title: the card title
  • Caption: card caption
  • Icon: a generic icon component

example of a basic card

Now, instead of passing all data into the Card component we instead use children (React) or ng-content (known as projection in Angular 2, and transclusion in Angular 1). The card on the right will now look something like this:

// Card with title, caption and icon
<Card>
  <Media source={'img/rotary-phone.png'} />

  <Body paddingX={2} paddingY={2}>
    <Title>Choosing the Right Antique Rotary Phone for You.</Title>

    <Caption>
      {'21 hours ago'}
      <Icon name="add-to-cart" handleClick={addToCart} />
    </Caption>
  </Body>
</Card>

This is much better. Let’s observe some benefits:

  • The Card is now agnostic to what content goes inside it. This flattens our component tree. There is less data that needs to be passed through the various levels of the component hierarchy.

  • Each component has one responsibility which makes testing trivial.

  • We gain a lot more flexibility. Developers can compose different types of cards or even create new ones since they have full control over the content of the card.

Highly Reusable Components

We can improve this further. You might notice that Body, Title and Caption all seem like specialized components. They are somewhat tied to the Card component. We can instead replace them with a few generic components:

  • Block: a generic box with visual styling
  • Heading: a heading component with size based on a font scale
  • Button: a generic button component with various styles

Notice the muted property for Heading. Instead of creating a Caption component we are reusing the Heading component and simply passing in a boolean with applies the muted colours.

// Card with title, caption and icon
<Card>
  <Media source={'img/rotary-phone.png'} />

  <Block paddingX={2} paddingY={2}>
    <Heading size={2}>Choosing the Right Antique Rotary Phone for You.</Heading>

    <Block>
      <Heading size={3} muted>
        21 hours ago
      </Heading>
      <Button style="clear" handleClick={addToCart}>
        <Icon name="add-to-cart" />
      </Button>
    </Block>
  </Block>
</Card>

And here is the final version in Angular 2:

<!-- Card with title, caption and icon -->
<Card>
  <Media [source]="'img/rotary-phone.png'"></Media>

  <Block paddingX="2" paddingY="2">
    <Heading size="2">
      Choosing the Right Antique Rotary Phone for You.
    </Heading>

    <Block>
      <Heading size="3" muted>21 hours ago</Heading>
      <button style="clear" (handleClick)="addToCart($event)">
        <Icon name="add-to-cart" />
      </button>
    </Block>
  </Block>
</Card>
https://varun.ca/flattening-deep-hierarchies-of-components/
OAM and React & SVG Starter
Last April Ainsley Wagoner posted this awesome dribbble shot. I loved the idea – it reminded me of colour field paintings. I reached out…
Show full content
OAM

Last April Ainsley Wagoner posted this awesome dribbble shot. I loved the idea – it reminded me of colour field paintings. I reached out to her and a few months later we started working together to built it.

ooaamm ainsley
OAM site dribbble shot

Around this time I had started to learn how to use redux. So, instead of building yet another To Do app I decided to learn redux by building OAM. This turned out to be a great experience. I learnt a lot about, React, redux and animating SVG.

React SVG Starter

I’ve been using React, SVG and redux for a lot of projects lately. Therefore, in order to save myself some time I made a React Redux SVG Starter. It’s based off of Rangle.io’s react-redux-starter and comes with the usual stuff like Webpack, Babel, HMR, Eslint, etc. In addition to this I’ve also setup a few components and reducers to get started quickly.

The Canvas

To setup the root SVG node I created a Canvas component. I learnt a few lessons here. First of all use children to keep the Canvas component light. This way the it doesn’t need to know about the state of the shapes being rendered. It is simply responsible for maintaining the <svg> element and sizing it to fill the window.

Secondly, using viewBox allows us to detach the canvas coordinate system from any pixel values. Thanks to this all the shapes can be described using relative coordinates yet, the SVG scales to whatever size you want.

const Canvas = ({ w, h, children }) => {
  const viewBox = [0, 0, w, h].join(' ');

  return (
    <svg
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      width="100%"
      height="100%"
      viewBox={viewBox}
      style={styles}
    >
      {children}
    </svg>
  );
};

That state for window and view box sizes is stored in the canvas reducer. The App container binds the window resize events to redux actions for updating this state.

The view box is calculated using this formula:

const w = width >= height ? 100 : (width * 100) / height;
const h = height > width ? 100 : (height * 100) / width;

This means that the longer side of SVG is 100 and the shorter side will be some value between 0 - 100 based on the aspect ratio. I found this made it easier to size & place any shapes and achieve responsive behaviour.

Animations

Chrome is going to deprecate SMIL so, I tried to use CSS for animations as much as possible. This came with a lot of cross browser issues. Eventually I gave up and switched to using GSAP for all animations. In the future I plan experiment more with React Motion and Web Animation API.

Sound

To load the sounds I used the webpack file loader which works great. For triggering them I initially used the HTML5 Audio API before switching to Howler.

import oam1 from '../audio/OAM_1.mp3';
const Sounds = { base: new Audio(oam1) };
Sounds.base.play();

With the Audio API, I found that if I triggered play in quick succession it wouldn’t actually play the sound if the previous play call was still executing. I haven’t worked much with the Audio API and probably just don’t know how to use it properly. In any case, Howler did exactly what we were looking for.

All the sounds are connected to UI actions. Therefore, I ended up placing them in the reducers, for example: circle-reducer.js#L30. This way the sound is played when the state is updated.

Creative Coding with React & SVG

Lastly I want to share the video for a talk I gave a talk at FITC Toronto 2016. In this talk I go through a basic tutorial of how to get started creating SVG images with React and adding animations to them. Also, I shared some examples and talked about the benefits of using this approach.

🎬 slides: winkervsbecks.github.io/creative-coding-with-react-svg

https://varun.ca/oam/
Chillwave
A few days ago I came across this creative studio called This Also . They just launched a new site. It not only showcases some of their…
Show full content

A few days ago I came across this creative studio called This Also. They just launched a new site. It not only showcases some of their amazing work but, also has this fun little loading animation.

chillwave

I absolutely love this! First thing I did was right click & inspect element and was expecting to find an SVG with SMIL animation baked in. To my surprise it turned out to be a sprite based animation with 24 frames. So, let’s recreate this with only SVG.

Draw the Wave

Step one was obvious… draw the wave using the SVG <path> element. In Sketch/Illustrator you would create something like this using the pen tool. It’s a Bézier curve with 2 points – each with a handle (control point). The wave is simply a collection of this path alternating with its mirror image.

wave path

To create this path with SVG we will use the Bézier curve command c – lower case which means relative coordinates. We need to repeat this pattern several times, instead of trying to figure out the absolute location of each point we can use relative coordinates to make our life easier.

<!--
  dx1 dy1: control point for the start
  dx2 dy2: control point for the end
  dx dy: the end point
-->
c dx1 dy1, dx2 dy2, dx dy
The Building Blocks

For this example let’s assume that the width of the SVG element is w, the height is h and the wave has an amplitude of 0.25 * h. The path can then be constructed by:

  • Moving to start point which will be the middle of the SVG (0.5 * h) + half the amplitude (0.125 * h):
['M', 0, 0.625 * h];
  • Then we begin the curve c and add the first control point. This is using relative coordinates so, the y coordinate is simply 0. The x coordinate is m times the amplitude; where m = 0.512286623256592433. The value of m is chosen such that it approximately creates a sine wave.
['M', 0, 0.625 * h, 'c', 0.25 * h * m, 0];
  • Then we add the second control point. For the y coordinate we have to go up by one amplitude and up means negative in SVG. Therefore, -0.25 * h. To calculate the x coordinate we go all the way to the end 0.25 * h and come back by 0.25 * h * m.
['M', 0, 0.625 * h, 'c', 0.25 * h * m, 0, 0.25 * h * (1 - m), -0.25 * h];
  • Finally we add the end point and create the path definition using the technique described in the react icons tutorial.
var pathData = [
  'M',
  0,
  0.625 * h,
  'c',
  0.25 * h * m,
  0,
  0.25 * h * (1 - m),
  -0.25 * h,
  0.25 * h,
  -0.25 * h,
].join(' ');

The next path section is a mirror of the one above. Luckily SVG has the s command.

You can string together several Bezier curves to create extended, smooth shapes. Often, in this case, the control point on one side of a point will be a reflection of the control point used on the other side (to keep the slope constant). In this case, you can use a shortcut version of the cubic Bezier, designated by the command S (or s).

MDN tutorial on Paths

Therefore, we can use the s command and extend our path definition. Remember the first control point is ✨automagically✨ inserted for us so, we only need to specify the 2nd control point and the end point of the second section.

var pathData = [
  'M',
  0,
  0.625 * h,
  'c',
  0.25 * h * m,
  0,
  0.25 * h * (1 - m),
  -0.25 * h,
  0.25 * h,
  -0.25 * h,
  's',
  0.25 * h * (1 - m),
  0.25 * h,
  0.25 * h,
  0.25 * h,
].join(' ');
And Repeat

We can now use the s command technique to expand this wave – alternating between down and up.

// down
's', 0.25 * h * (1 - m), 0.25 * h, 0.25 * h, 0.25 * h;
// and back up
's', 0.25 * h * (1 - m), -0.25 * h, 0.25 * h, -0.25 * h;
Move the Wave

To animate the wave we move the path from left to right using CSS transforms. The distance is equal to the width of the SVG element: transform: translate3d(-90px, 0 , 0). And we have to ensure that wave is long enough otherwise the animation doesn’t quite work.

The Finishing Touch

We could stop here, but you’ll notice that in the original GIF the wave has rounded ends. Adding that to a static wave is easy. We use stroke-linecap="round" and call it a day. But, in order to animate the wave its path extends beyond the visible SVG canvas.

The rounded ends are both somewhere offscreen. Therefore, to achieve the appropriate effect we have to rely on stroke-dasharray. The dash array takes values for lengths of dashes and gaps.

#wave {
  stroke-dasharray: 0 16 101 16;
}
  • We begin with 0 since we want a gap to begin with but, by default the dasharray applies the first value to a dash.
  • 16 is then the length of the first gap.
  • 101 is the length of the dash.
  • And finally16 is then the length of the last gap.

The total path is approximately 120px long. Using that as a starting point, I picked these numbers after a bit of trial and error.

At this point if we re-introduce the animation you’ll notice that it breaks the optical illusion of the wave staying in place as it oscillates. The path is moving left to right. To counter this movement we need to move the dash (yup, from the dasharray above) right to left at the same speed.

The gap + dash + gap = 16 + 101 + 16 = 133. And we can move the dash using stroke-dashoffset by exactly that amount – the now famous SVG line animation technique. Notice the difference. The one of the left doesn’t have the dashoffset and the one on the right does.

Update

Had to add the following fix for Safari. For some reason the SVG view-port is larger on it than other browsers.

<svg
  xmlns="http://www.w3.org/2000/svg"
  width="80px"
  height="60px"
  viewBox="5 0 80 60"
></svg>
https://varun.ca/chillwave/
Parallax With Angular
Lately I've been obsessed with Monument Valley. Love the game and the aesthetic even more. I even set up my Electric Object EO1 with a…
Show full content

Lately I’ve been obsessed with Monument Valley. Love the game and the aesthetic even more. I even set up my Electric Object EO1 with a “print” from the game. So, I just had to build something inspired by it – why not a Tintin/Monument Valley mash-up?

The starting point was this image. The shapes and gradients are easy to replicate with SVG. And instead of worrying about the low-poly sky I decided to focus on introducing some parallax effect.

monument valley

SVG with AngularJS

For my day job I primarily work with Angular and I was curious to see how the whole dynamically composed SVG thing would work out with it. So, instead of React + SVG this time it is Angular + SVG.

Adopting the same technique as React the SVG layers are just components – known as directives in the Angular world. To create a directive that renders SVG you need to specify templateNamespace: 'svg'. This allows you to have templates that contain SVG partials.

I call the top level component in this case the valley directive. This is where the SVG starts and the template for it is simply:

<svg xmlns="http://www.w3.org/svg/2000" width="100%" height="100%">
  <g ng-transclude></g>
</svg>

Now, since we are composing these layers in markup we need to set transclude: true for this directive. Angular by default strips the content within the directive tags. Enabling transclude allows us to have nested content, like so:

<valley id="valley" dx="main.dx" dy="main.dy">
  <defs ng-include="'templates/gradients'"> </defs>
  <g ng-repeat="peak in main.peaks">
    <peak
      fill="{ { peak.fill }}"
      w="main.width"
      h="main.height"
      def="peak.left"
      dx="main.dx"
      dy="main.dy"
      ng-attr-opacity="{ { peak.opacity }}"
    ></peak>
    <peak
      fill="{ { peak.fill }}"
      w="main.width"
      h="main.height"
      def="peak.right"
      dx="main.dx"
      dy="main.dy"
      ng-attr-opacity="{ { peak.opacity }}"
    ></peak>
  </g>
  <g
    mountain-range
    fill="url(#black-gradient)"
    w="main.width"
    h="main.height"
    dx="main.dx"
    dy="main.dy"
  >
  </g>
</valley>
Constructing the Shapes

I decided to construct the black peaks 🌄 in the front as one shape in the mountain-range directive. The shape is simply a path element. The path is made of 5 vertices… it’s like connect the dots:

2*               *4

        3*

1*               *5

All the other peaks were constructed individually using the peak directive. The shape here is once again a path but with 3 vertices. Let’s connect the dots again:

        2*


1*               *3

Now that shape is ready we can focus on the parallax. Parallax effect is a simple technique to fake 3D-ish in 2D. This would be achieved by moving the peaks in the back by a larger distance than those in the front.

On the iPhone you might have noticed the parallax effect when you tilt your phone. While that is awesome and can be replicated with JS I chose to simply use the mouse pointer to drive the parallax.

Tracking the Mouse

The mousemove event gives use the mouse position: event.pageX & event.pageY. This position is relative to the window. We only care about the position inside the card. So, to calculate the position relative to the card we use:

var cardInfo = cardElement.getBoundingClientRect();
var mouseX = event.pageX - cardInfo.left;
var mouseY = event.pageY - cardInfo.top;

We have the mouse coordinates relative to the card. However, the origin for these coordinates is the top left corner of the card. For the parallax effect it would be helpful to move the origin to the centre of the card – makes the math easier. For that we do a little transformation:

var cardInfo = cardElement.getBoundingClientRect();
var mouseX = event.pageX - cardInfo.left;
var mouseY = event.pageY - cardInfo.top;

var w = cardInfo.width;
var h = cardInfo.height;

var transformedMouseX = -(w / 2 - x);
var transformedMouseY = h / 2 - y;

And finally we map & constrain these values between -1 and 1 (again, to make the math easier).

// Re-maps a number from one range to another
function map(n, start1, stop1, start2, stop2) {
  return ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2;
}

See the Pen Mouse Position by Varun Vachhar (@winkerVSbecks) on CodePen.

The Parallax

The easiest way to move the peaks is to use CSS transforms. We could manipulate the path too but, that gets a bit more complex.

<path ng-attr-d="{ { peak.d() }}" ng-attr-transform="{ { peak.translate() }}">
</path>

You’ll notice the ng-attr prefix. We need this because the transform value is calculated dynamically based on the mouse position:

If an attribute with a binding is prefixed with the ng-attr prefix… allows you to bind to attributes that would otherwise be eagerly processed by browsers (e.g. an SVG element's circle[cx] attributes).

docs.angularjs.org/guide/directive

Each peak can be displaced by a maximum amount in the X and Y direction. The SVG is responsive therefore, we need to dynamically calculate the pixel value using the width and height of the SVG.

// Ratio is a number between 0 & 1
var xBase = svgWidth * ratio;
var yBase = svgHeight * ratio;

To achieve the parallax effect each peak needs a different ratio. It’s largest for the peak in the back and reduces as we go forward. The translate value is calculated by mapping the normalized mouse position from -xBase to xBase.

var xBase = svgWidth * ratio;
var yBase = svgHeight * ratio;
var xAmt = map(normalizedMouseX, -1, 1, -xBase, xBase);
var yAmt = map(normalizedMouseY, -1, 1, -yBase, yBase);

var translate = 'translate(' + xAmt + ',' + yAmt + ')';

See the Pen parallax step 1 by Varun Vachhar (@winkerVSbecks) on CodePen.

The card movement works in a similar fashion. Except we have to rotate the card instead of translate.

var yRotation = map(normalizedMouseX, -1, 1, 10, -10);
var xRotation = map(normalizedMouseX, -1, 1, 5, -5);

return {
  transform:
    'rotateX(' +
    xRotation +
    'deg)' +
    ' rotateY(' +
    yRotation +
    'deg) rotateZ(0deg)',
};

Mix all the ingredients together for a parallax filled card.

animated gif of a person whisking

https://varun.ca/parallax/
Truncation
I recently discovered the idea of truncation. It is a fascinating concept. You rip apart a vertex to create a new facet and in turn more…
Show full content

I recently discovered the idea of truncation. It is a fascinating concept. You rip apart a vertex to create a new facet and in turn more complex geometry. This pen by Ana Tudor shows the truncation of a tetrahedron:

You start with a simple tetrahedron. As you execute the truncation you can create some fairly complex shapes. So, what exactly is happening here? Let us break this down step by step:

  1. Each vertex is connected to some edges. In the case of a tetrahedron each vertex is connected to 3 edges.
  2. We pick one vertex called A.
  3. We start by splitting A three times since it is connected to 3 edges.
  4. The split vertices are called: A1, A2 & A3.
  5. They are connected to edges: L1, L2 & L3.
  6. To execute the truncation we simply move A1 along L1. Towards the mid-point of L1.
  7. We do the same for A2 and A3.

A fairly simple algorithm that can be generalise for the entire shape/solid. You can even generalize it for a set of shapes, such as 2D polygons. Which is exactly what I did for my demo.

I used React and SVG for this project too. A quick overview of the code:

  1. Construct the polygon using polar coordinates — an array of points.
  2. Calculate the mid-points of each side.
  3. To split the vertices we create a new array with duplicates of each vertex (each vertex is only connected to 2 sides in this case).
  4. Then we have to link each split vertex to a mid-point. For this we create another array where each item has two properties:
    • the split vertex
    • mid-point of the line it belongs to
  5. Finally, use liner interpolation to execute the truncation.

The end result is react component that requires two properties: vertex count & truncation amount.

https://varun.ca/truncation/
Triangle.life
I love triangles! Last year I spent 30 days making a new triangle every day . It was a great experience. I learnt so much and it was…
Show full content

I love triangles!

Last year I spent 30 days making a new triangle every day. It was a great experience. I learnt so much and it was amazing how much you can do with just one simple shape.

After a brief hiatus I have decided to get back to constructing more triangles. You can follow the progress at triangle.life.

In case you are wondering त्रिकोण is the Hindi word for triangle.

https://varun.ca/triangles/
SVG with React
I've been using ReactJS for about 6 months now and have been very happy with my experience as a developer. I never considered it for…
Show full content
interactive fermat point visualization

I’ve been using ReactJS for about 6 months now and have been very happy with my experience as a developer. I never considered it for generative art because it just seemed like a needlessly complex approach. Then I came across projects such as Flipboard’s react-canvas and React Art which made me reconsider this opinion. I love the idea of being able to build small isolated reusable components. Then use them to compose complex systems.

More recently I came across Brent Jackson’s fantastic tutorial on Building SVG Icons with React. Not sure about everyone else but, I prefer the Canvas API to that of SVG. The SVG syntax for constructing shapes just seems extremely complicated and then having to manipulate them in JS is even more painful. The upside of SVG however is that it’s part of the DOM. You can mix it with other parts of the DOM in more ehm real world scenarios.

With React you can compose a complex SVG images inside out — without having to worry about DOM manipulation. Every time state changes the document is re-rendered. Therefore, the SVG element updates to reflect this new state too.

I was really inspired by Brent’s tutorial and just had to try this out myself. The first thing I built was a Tetrahedron Generator. Fairly straightforward, not the most complex geometry in the world. You can find the source code here: github.com/winkerVSbecks/react-tetrahedrons.

It was really fast to build and this weird SVG syntax didn’t seem that weird any more. The next step was to build something a bit more complex… a Fermat point calculator. This required a bit more thinking but that was mostly because of geometry and math rather than SVG or React.

Let’s look at an example of a component. I needed to build an SVG circle and then allow the user to click and drag it around. I started by creating a new component called Handle. Here’s the template for it:

<circle
  className="handle"
  id={this.props.id}
  cx={this.props.x}
  cy={this.props.y}
  fill={clrs.yellow}
  onMouseDown={this.selectElement}
  onMouseMove={this.drag}
  onMouseUp={this.deSelectElement}
  onMouseLeave={this.deSelectElement}
/>

The logic for clicking and dragging and all the event handlers are isolated within this component itself. However, you can still pass in callbacks. This allows you to capture the drag and respond to it. Here’s an example of how you would use the Handle component:

<Handle x={pt[0]} y={pt[1]} id={idx} onUpdate={dragUpdate} />

In the long term I hope to extend this ideology to Canvas. The idea is to build a library of such components with React. Each component would then be rendered to Canvas using PaperJS.

https://varun.ca/react-svg/
Location Services With AngularJS
Building mobile apps often requires working with location information. While, the Cordova geo-location plugin makes it quite trivial to get…
Show full content

Building mobile apps often requires working with location information. While, the Cordova geo-location plugin makes it quite trivial to get the latitude and longitude values for the user’s current location, what we often want is location identifiers that are meaningful to the user - and not necessarily corresponding to the place where the user is right now. Below we look at two ways at acquiring meaningful location identifiers.

1. Geo-Location to Nearby Locations

We fetch nearby locations based on the geo-location data and allow the user to pick the most appropriate option. For this method we can split the tasks into two services:

Location

The Location service checks to see if geo-location is available and grabs the current location. It also allows us to register on-ready-tasks with it. Therefore, all the other directives/services which depend on the geo-location data only bootstrap once the data is available.

The geo-location data is captured using using the navigator.geolocation.getCurrentPosition method. This works for both desktop browsers and Cordova/PhoneGap.

Reverse Geocoder

Reverse geocoding is the process of converting geographic coordinates (like latitude 43.647118 and longitude -79.420194) into a human-readable address (such as Ossington Ave at Argyle St, Toronto, ON, M6J 2Z5).

The Reverse Geocoder sets up the Google Geocoder service and uses the geo-location data to fetch, reverse geocoded, nearby locations. The service provides various options with reducing resolution, for example:

  • House #, Street, City, Province, Postal Code, Country
  • Street, City, Province, Postal Code, Country
  • Neighbourhood, City, Province, Country
  • City, Province, Postal Code, Country
Location-Picker

The Location-Picker packages this into a simple directive. It utilizes the reverse-geocoder service to fetch a set of options for the user. The user selection is then bound to the object passed in through the ng-model attribute.

<!-- Requires access to the user's geo-location data -->
<location-picker ng-model="pickedLocation" limit-to="5"></location-picker>
2. Manually Query the Location Database

The second method is to allow the user to query the location database manually. In this case we use a set of nested directives and the Google Auto-complete service.

Location-Predictions

Location-Predictions directive generates a search box and sets up the Google Auto-complete service. The auto-complete service fetches predictions based on the user submitted query string.

Location-Lookup

The location-predictions directive generates a set of options which are passed into the Location-Lookup directive. Which in turn displays them as a list for the user to choose from. Once the user picks a location it uses the Google Places service to fetch the geo-location data for it.

<!-- Requires user to enter a query -->
<location-lookup ng-model="lookedUpLocation" limit-to="4"></location-lookup>

The Google Places Library has certain logo requirements. In this case we are not using a map therefore, we are required to display a Powered by Google logo along with the data.

Usage

Both, location-lookup and location picker, directives are fairly straight forward to use. They essentially behave as a <select> element. The selection is captured using ng-model. Optionally you can limit the number of choices by using the limit-to attribute.

The selection returns data of the following type:

{
  name: 'CN Tower',
  description: 'CN Tower, Front Street West, Toronto, ON, Canada',
  latitude: 43.642566,
  longitude: -79.38705700000003
}
Alternative Geocoding Providers

In this example I’ve used Google Maps JavaScript API v3 for all the location services required. Depending on your business and/or technical needs this might not be the best option. However, the idea remains the same and you could swap out the Google services with your preferred alternative.

https://varun.ca/locator/
The Core
Another p5js/Codepen.io experiment. This time playing around with contours and lerp to get gradient fills inside a polygon. I've been…
Show full content

Another p5js/Codepen.io experiment. This time playing around with contours and lerp to get gradient fills inside a polygon.

I’ve been trying to learn a bit more about easing functions. More specifically elastic easing. It is fairly easy to do a single bounce with CSS. Tools such as bounce.js can be used to generate more complex versions. However, I had no idea how to do this with JS or what the underlying equations were …

Despite the commonality of the classic easing equations, largely attributed to Penner, there doesn’t seem to be the in-depth examination of “how it works” that a lot of code is subject to nowadays.

Explaining Penner’s equations – JavaScript and ActionScript

We start with this basic equation where:

  • t is the current time (or position) of the tween. This can be seconds or frames, steps, seconds, ms, whatever – as long as the unit is the same as is used for the total time.
  • b is the beginning value of the property.
  • c is the change between the beginning and destination value of the property.
  • d is the total time of the tween.
function noEasing (t, b, c, d) {
  return c \* t / d + b;
}

And then use polynomial functions to create all kinds of easing effects:

function bounce(t, b, c, d) {
  var ts = (t /= d) * t;
  var tc = ts * t;
  return b + c * (33 * tc * ts + -106 * ts * ts + 126 * tc + -67 * ts + 15 * t);
}

Tim Groleau built this a really cool Easing Function Generator which I used to generate the bounce easing function.

https://varun.ca/the-core/
Vector Field
While going through the Google Material Design handbook the illustration in the Users Initiate Change caught my eye. Around the same…
Show full content

While going through the Google Material Design handbook the illustration in the Users Initiate Change caught my eye. Around the same time p5js was announced, so I figured it would be fun replicating this with Canvas.

My first attempt was to build a grid of vectors and then rotate them towards the mouse location. The rotation would be scaled down based on the distance from the mouse location, i.e: rotation = angle * scale(0, 1, 0, width-of-the-canvas). This gave an interesting result but, it wasn’t quite the same spiral effect.

The next step was to look into how vector fields work. With a bit of help from Paul’s Online Math Notes, Wolfram Alpha and math.stackexchange.com I discovered that: f(x,y) = [(y−5)-(x−5), -(x−5)-(y−5)] produces the exact spiralling effect.

vector field

Building this with p5js was fairly straightforward. Processing has an awesome drawing API and p5js brings it to the web. If you are interested in getting started with p5js I have built a seed project. There are some great tutorials on the project site and Daniel Shiffman has ported his amazing The Nature of Code Examples to p5js too.

for (var i = locs.length - 1; i >= 0; i--) {
  var h = calcVec(locs[i].x - mouseX, locs[i].y - mouseY);
  line(
    locs[i].x,
    locs[i].y,
    locs[i].x + 15 * cos(h.heading()),
    locs[i].y + 15 * sin(h.heading())
  );
}
https://varun.ca/vector-field/
Squiggle
I've been obsessed with this illustration for a while now and really want to build an animated/generative version of it. But, as usual I…
Show full content

I’ve been obsessed with this illustration for a while now and really want to build an animated/generative version of it. But, as usual I have no idea how to do that.

The aim is to get those paths to flow across the screen with an organic movement. Which probably means using a 2D Perlin noise field. This is just a first step towards animating Jupiter.

Next step is figuring out how to make those tentacle like paths. Probably using spline extrusions.

Built using Joseph Gentle’s noisejs library and Two.js for the graphics.

https://varun.ca/squiggle/
Custom Easing with Sass
patakk is an insanely talented GIF artist and on his tumblr blog he shared this simple yet amazing easing function: The time variable…
Show full content

patakk is an insanely talented GIF artist and on his tumblr blog he shared this simple yet amazing easing function:

The time variable goes from 0 to 1 and g adjusts the amount of easing.

x = 300 * ease(time, g);

float ease(float p, float g){
  if (p < 0.5)
    return 0.5 * pow(2*p, g);
  else
    return 1 - 0.5 * pow(2*(1 - p), g);
}

3D animation has this concept of baking an animation. The same idea can be extended to baking an easing effect with Sass. The easingGenerator generates keyframe animations using this custom easing function.

@function ease($time, $g) {
  @if $time < 50 {
    @return 0.5 * pow(2 * $time/100, $g);
  } @else {
    @return 1 - (0.5 * pow(2 * (1 - $time/100), $g));
  }
}

@mixin easingGenerator($g) {
  @for $i from 0 through 100 {
    // calculate
    $percent: 0% + $i;
    $left: 0% + 100 * ease($i, $g);
    // set position
    #{$percent} {
      left: $left;
    }
  }
}
https://varun.ca/custom-easing-with-sass/
Jamie XX
Trying out the React framework for the first time. Built a very minimal animation for Far Nearer by Jamie XX . Using the Rdio API to…
Show full content

Trying out the React framework for the first time. Built a very minimal animation for Far Nearer by Jamie XX. Using the Rdio API to for the music.

https://varun.ca/jamie-xx/
Open & Close
I wanted to replicate CreativeDash's open & close animation with pure CSS/SASS, but don't think it's possible without SVG path animations…
Show full content

I wanted to replicate CreativeDash’s open & close animation with pure CSS/SASS, but don’t think it’s possible without SVG path animations. This is the closest I got.

Kyle Henwood wrote a great blog post on how to build this with SVG.

Also, Anders Ingemann’s tutorial – Radial progress indicator using CSS – was really helpful in trying to figure out the circular mask.

The animation is based on this Open & Close dribbble shot by CreativeDash
https://varun.ca/open-and-close/
Two Level Shrinking Header and Footer with Ionic Framework
The Ionic Framework is an open source front-end framework for developing hybrid mobile apps with Cordova and AngularJS. I'm a big fan and…
Show full content

The Ionic Framework is an open source front-end framework for developing hybrid mobile apps with Cordova and AngularJS. I’m a big fan and use it a lot in my work at Rangle.io! We are a full-stack web and mobile app development consultancy.

One of the apps we are currently developing has an infinite stream similar to Facebook. Therefore, we decided to mimic the iOS shrinking header UI, to give the user more screen space for actual content. Ionic has had a demo for this for quite some time. However, the shrinking behaviour is limited to the top of the scroll view.

We wanted to take this one step further and allow the shrinking and expansion to happen at any point during the scroll view. Also, we have a two level header: main header and a secondary toolbar.

Since, the animation can occur at any point we have to keep a track of the scroll direction. This is done by calculating the delta:

delta = e.detail.scrollTop - prev;

If delta is greater than zero then the user is scrolling up and vice versa:

dir = delta >= 0 ? 1 : -1;

The shrink amount is calculated based on the scroll position:

// If Shrinking
shrinkAmt = headerHeight + subHeaderHeight - ( (starty + headerHeight + subHeaderHeight) - e.detail.scrollTop) );

// If Expanding
shrinkAmt = prevShrinkAmt - (starty - e.detail.scrollTop);

This works great until the user scrolls to the top/bottom edge and the view has bounce. The bounce causes all kinds of weird glitches. To prevent these from happening we need to add max and min limits:

// If Shrinking
shrinkAmt =
  headerHeight +
  subHeaderHeight -
  Math.max(0, starty + headerHeight + subHeaderHeight - e.detail.scrollTop);

// If Expanding
shrinkAmt = prevShrinkAmt - Math.min(threshold, starty - e.detail.scrollTop);

The final step is to apply the actual CSS transforms from within the ionic.requestAnimationFrame function. This is also where we check to see if the shrink amount is greater than the height of the sub-header and only apply the transforms to the main-header if it is.

The ionic.requestAnimationFrame calls the window.requestAnimationFrame or a polyfill if it’s not available. This helps optimize the animation updates making it much more smoother:

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes as an argument a callback to be invoked before the repaint.

This will request that your animation function be called before the browser performs the next repaint. The number of callbacks is usually 60 times per second.

Mozilla Developer Network
https://varun.ca/shrinking-header-footer/
Swipe-Li
Recently we – at rangle.io – had a chance to work on a short project. The app was largely a functional prototype built in 1 week. One…
Show full content

swipable list elements

Recently we – at rangle.io – had a chance to work on a short project. The app was largely a functional prototype built in 1 week. One of the requirements was to build an interface to allow the user to quickly accept or reject items. We came up with swipe-li: a three pane swipeable <li> element. Swipe right for accept. Swipe left for reject.

Most of the swiping code is taken from the hammer.js carousel example. To support the directive I built a hammerRemote service. This delegate service allows you to register/destroy/control multiple instances of hammer.

<div
  swipe-li
  disabled=""
  intent="true"
  accept="done(item)"
  reject="skip(item)"
  main-content="'sample-content.html'"
  accept-content="'accept-content.html'"
  reject-content="'reject-content.html'"
  reset-to-content="false"
></div>

Where:

  • disabled: disable the swipe-li element
  • intent: user intent detection, if the user drags the pane > 50% of the width - the swipe will auto complete
  • accept: accept callback
  • reject: reject callback
  • main-content: template for the start pane
  • accept-content: template for the accept pane
  • reject-content: template for the reject pane
  • reset-to-content: should the swiped pane reset back to start pane on complete
https://varun.ca/swipe-li/
Ellsworth Kelly Animated
In February 2014 Google launched it's DevArt project in partnership with Barbican . In Google's words: DevArt is a celebration of art…
Show full content

bouncy orange blob

In February 2014 Google launched it’s DevArt project in partnership with Barbican. In Google’s words:

DevArt is a celebration of art made with code by artists that push the possibilities of creativity - where technology is their canvas and code is their raw material.

This was my entry and it ended up being shortlisted for the finals.

A couple of years ago I made a Processing sketch with the logic described in the image below. Shortly thereafter I came across Ellsworth Kelly’s Black Relief II. It seemed that, unknowingly, I had created an animated version of his painting.

polygon

This led me to explore more of his work. His paintings carry an immense amount of potential energy in my opinion. It’s as if they are kinetic sculptures frozen in time.

This project is an attempt to animate some of his pieces using web-based technologies.

Most of the works are made using Box2dWeb. A simple mouse click allows you to animate them.

In the first piece, you can grab the interface of the blue blob and the surrounding green fill and drag it around. Press ? to see the underlying skeleton.

The project is also available as a Google Chrome App: Ellsworth Kelly Animated

The Code

I wanted to play around with the idea of springy membranes. This was particularly inspired by the Red Curve Relief artwork by Ellsworth Kelly.

To do so, I had two options:

  • Use easing functions to replicate springy movements
  • Use a physics engine to create a skeleton and add organic movements

I see these shapes as animated creatures, so I chose the latter. The skeleton is made up of particles and spring-links.

skeleton

The bottom three particles are anchors. The remaining are dynamic particles connected to the anchors and adjacent particles using springs.

An example of how the impulse is provided to the particles in order to animate the shapes:

SpringyTriangle.prototype.impulse = function() {
  var ping = Math.random(-1, 1);
  var pinger = 1;
  if (ping > 0) pinger = 1;
  if (ping <= 0) pinger = -1;
  var appliedForce = new b2Vec2(
    (this.force.x * pinger) / scale,
    this.force.y / (2 * scale)
  );
  this.imp = this.a_imp.getPosition();
  this.a_imp.body.ApplyImpulse(appliedForce, this.imp);
};

The other pieces used similar ideas of node based skeleton with an overlaid shape. I explain the process behind each piece in more detail on the project site.

Early Prototypes Made with Processing

springy wavy-box

springy triangles

many polygons

https://varun.ca/ellsworth-kelly-animated/
AngularJS WebGL Directive
AngularJS directives are siloed reusable components. This is an example of a WebGL directive built using three.js . You can pass setup…
Show full content

angularJS webGL Directive

AngularJS directives are siloed reusable components. This is an example of a WebGL directive built using three.js. You can pass setup information such as: canvas size, model size, material, lighting, etc. to the directive.

You can even bind these values to the WebGL context – use the controls below to resize the object or change it’s material type.

Events such as window resizing or mouse moved can also be bound to the directive to update the scene. With the Responsive Canvas checkbox selected try resizing the window. The canvas and the scene will resize to fit the container.

https://varun.ca/angularjs-webgl-directive/