Learn the basics of GLSL while creating a distorted mask effect on images using Babylon.js.


From our weekly sponsor: Design every part of your website with the brand new Divi Theme Builder. Try it for free.

Nowadays, it’s really hard to navigate the web and not run into some wonderful website that has some stunning effects that seem like black magic.

Well, many times that “black magic” is in fact WebGL, sometimes mixed with a bit of GLSL. You can find some really nice examples in this Awwwards roundup, but there are many more out there.

Recently, I stumbled upon the Waka Waka website, one of the latest works of Ben Mingo and Aristide Benoist, and the first thing I noticed was the hover effect on the images.

It was obvious that it’s WebGL, but my question was: “How did Aristide do that?”

Since I love to deconstruct WebGL stuff, I tried to replicate it, and in the end I’ve made it.

In this tutorial I’ll explain how to create an effect really similar to the one in the Waka Waka website using Microsoft’s BabylonJS library and some GLSL.

This is what we’ll do.

The setup

The first thing we have to do is create our scene; it will be very basic and will contain only a plane to which we’ll apply a custom ShaderMaterial.

I won’t cover how to setup a scene in BabylonJS, for that you can check its comprehensive documentation.

Here’s the code that you can copy and paste:

import { Engine } from "@babylonjs/core/Engines/engine";
import { Scene } from "@babylonjs/core/scene";
import { Vector3 } from "@babylonjs/core/Maths/math";
import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
import { Effect } from "@babylonjs/core/Materials/effect";
import { PlaneBuilder } from "@babylonjs/core/Meshes/Builders/planeBuilder";

class App {
  constructor() {
    this.canvas = null;
    this.engine = null;
    this.scene = null;

  init() {

  setup() {
    this.canvas = document.querySelector("#app");
    this.engine = new Engine(this.canvas, true, null, true);
    this.scene = new Scene(this.engine);

    // Adding the vertex and fragment shaders to the Babylon's ShaderStore
    Effect.ShadersStore["customVertexShader"] = require("./shader/vertex.glsl");
    ] = require("./shader/fragment.glsl");

    // Creating the shader material using the `custom` shaders we added to the ShaderStore
    const planeMaterial = new ShaderMaterial("PlaneMaterial", this.scene, {
      vertex: "custom",
      fragment: "custom",
      attributes: ["position", "normal", "uv"],
      uniforms: ["worldViewProjection"]
    planeMaterial.backFaceCulling = false;

    // Creating a basic plane and adding the shader material to it
    const plane = new PlaneBuilder.CreatePlane(
      { width: 1, height: 9 / 16 },
    plane.scaling = new Vector3(7, 7, 1);
    plane.material = planeMaterial;

    // Camera
    const camera = new ArcRotateCamera(
      -Math.PI / 2,
      Math.PI / 2,

    this.engine.runRenderLoop(() => this.scene.render());

  addListeners() {
    window.addEventListener("resize", () => this.engine.resize());

const app = new App();

As you can see, it’s not that different from other WebGL libraries like Three.js: it sets up a scene, a camera, and it starts the render loop (otherwise you wouldn’t see anything).

The material of the plane is a ShaderMaterial for which we’ll have to create its respective shader files.

// /src/shader/vertex.glsl

precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varyings
varying vec2 vUV;

void main(void) {
    gl_Position = worldViewProjection * vec4(position, 1.0);
    vUV = uv;
// /src/shader/fragment.glsl

precision highp float;

// Varyings
varying vec2 vUV;

void main() {
  vec3 color = vec3(vUV.x, vUV.y, 0.0);
  gl_FragColor = vec4(color, 1.0);

You can forget about the vertex shader since for the purpose of this tutorial we’ll work only on the fragment shader.

Here you can see it live:

Good, we’ve already written 80% of the JavaScript code we need for the purpose of this tutorial.

The logic

GLSL is cool, it allows you to create stunning effects that would be impossible to do with HTML, CSS and JS alone. It’s a completely different world, and if you’ve always done “web” stuff you’ll get confused at the beginning, because when working with GLSL you have to think in a completely different way to achieve any effect.

The logic behind the effect we want to achieve is pretty simple: we have two overlapping images, and the image that overlaps the other one has a mask applied to it.

Simple, but it doesn’t work like SVG masks for instance.

Adjusting the fragment shader

Before going any further we need to tweak the fragment shader a little bit.

As for now, it looks like this:

// /src/shader/fragment.glsl

precision highp float;

// Varyings
varying vec2 vUV;

void main() {
  vec3 color = vec3(vUV.x, vUV.y, 0.0);
  gl_FragColor = vec4(color, 1.0);

Here, we’re telling the shader to assign each pixel a color whose channels are determined by the value of the x coordinate for the Red channel and the y coordinate for the Green channel.

But we need to have the origin at the center of the plane, not the bottom-left corner. In order to do so we have to refactor the declaration of uv this way:

// /src/shader/fragment.glsl

precision highp float;

// Varyings
varying vec2 vUV;

void main() {
  vec2 uv = vUV - 0.5;
  vec3 color = vec3(uv.x, uv.y, 0.0);
  gl_FragColor = vec4(color, 1.0);

This simple change will result into the following:

This is becase we moved the origin from the bottom left corner to the center of the plane, so uv‘s values go from -0.5 to 0.5. Since you cannot assign negative values to RGB channels, the Red and Green channels fallback to 0.0 on the whole bottom left area.

Creating the mask

First, let’s change the color of the plane to complete black:

// /src/shader/fragment.glsl

precision highp float;

// Varyings
varying vec2 vUV;

void main() {
  vec2 uv = vUV - 0.5;
  vec3 color = vec3(0.0);
  gl_FragColor = vec4(color, 1.0);

Now let’s add a rectangle that we will use as the mask for the foreground image.

Add this code outside the main() function:

vec3 Rectangle(in vec2 size, in vec2 st, in vec2 p, in vec3 c) {
  float top = step(1. - (p.y   size.y), 1. - st.y);
  float right = step(1. - (p.x   size.x), 1. - st.x);
  float bottom = step(p.y, st.y);
  float left = step(p.x, st.x);
  return top * right * bottom * left * c;

(How to create shapes is beyond of the scope of this tutorial. For that, I suggest you to read this chapter of “The Book of Shaders”)

The Rectangle() function does exactly what its name says: it creates a rectangle based on the parameters we pass to it.

Then, we redeclare the color using that Rectangle() function:

vec2 maskSize = vec2(0.3, 0.3);

// Note that we're subtracting HALF of the width and height to position the rectangle at the center of the scene
vec2 maskPosition = vec2(-0.15, -0.15);
vec3 maskColor =  vec3(1.0);

color = Rectangle(maskSize, uv, maskPosition, maskColor);

Awesome! We now have our black plane with a beautiful white rectangle at the center.

But, wait! That’s not supposed to be a rectangle; we set its size to be 0.3 on both the width and the height!

That’s because of the ratio of our plane, but it can be easily fixed in two simple steps.

First, add this snippet to the JS file:

this.scene.registerBeforeRender(() => {
  plane.material.setFloat("uPlaneRatio", plane.scaling.x / plane.scaling.y);

And then, edit the shader by adding this line at the top of the file:

uniform float uPlaneRatio;

…and this line too, right below the line that sets the uv variable

uv.x *= uPlaneRatio;

Short explanation

In the JS file, we’re sending a uPlaneRatio uniform (one of the GLSL data type) to the fragment shader, whose value is the ratio between the plane width and height.

We made the fragment shader wait for that uniform by declaring it at the top of the file, then the shader uses it to adjust the uv.x value.

Here you can see the final result: a black plane with a white square at the center; nothing too fancy (yet), but it works!

Adding the foreground image

Displaying an image in GLSL is pretty simple. First, edit the JS code and add the following lines:

// Import the `Texture` module from BabylonJS at the top of the file
import { Texture } from '@babylonjs/core/Materials/Textures/texture'
// Add this After initializing both the plane mesh and its material
const frontTexture = new Texture('src/images/lantern.jpg')
plane.material.setTexture("u_frontTexture", frontTexture)

This way, we’re passing the foreground image to the fragment shader as a Texture element.

Now, add the following lines to the fragment shader:

// Put this at the beginninng of the file, outside of the `main()` function
uniform sampler2D u_frontTexture;
// Put this at the bottom of the `main()` function, right above `gl_FragColor = ...`
vec3 frontImage = texture2D(u_frontTexture, uv * 0.5   0.5).rgb;

A bit of explaining:

We told BabylonJS to pass the texture to the shader as a sampler2D with the setTexture() method, and then, we made the shader know that we will pass that sampler2D whose name is u_frontTexture.

Finally, we created a new variable of type vec3 named frontImage that contains the RGB values of our texture.

By default, a texture2D is a vec4 variable (it contains the r, g, b and a values), but we don’t need the alpha channel so we declare frontImage as a vec3 variable and explicitly get only the .rgb channels.

Please also note that we’ve modified the UVs of the texture by first multiplying it by 0.5 and then adding 0.5 to it. This is because at the beginning of the main() function I’ve remapped the coordinate system to -0.5 -> 0.5, and also because of the fact that we had to adjust the value of uv.x.

If you now add this to the GLSL code…

color = frontImage;

…you will see our image, rendered by a GLSL shader:


Always keep in mind that, for shaders, everything is a number (yes, even images), and that 0.0 means completely hidden while 1.0 stands for fully visible.

We can now use the mask we’ve just created to hide the parts of our image where the value of the mask equals 0.0.

With that in mind, it’s pretty easy to apply our mask. The only thing we have to do is multiply the color variable by the value of the mask:

// The mask should be a separate variable, not set as the `color` value
vec3 mask = Rectangle(maskSize, uv, maskPosition, maskColor);

// Some super magic trick
color = frontImage * mask;

Et voilà, we now have a fully functioning mask effect:

Let’s enhance it a bit by making the mask follow a circular path.

In order to do that we must go back to our JS file and add a couple of lines of code.

// Add this to the class constructor
this.time = 0
// This goes inside the `registerBeforeRender` callback
this.time  ;
plane.material.setFloat("u_time", this.time);

In the fragment shader, first declare the new uniform at the top of the file:

uniform float u_time;

Then, edit the declaration of maskPosition like this:

vec2 maskPosition = vec2(
  cos(u_time * 0.05) * 0.2 - 0.15,
  sin(u_time * 0.05) * 0.2 - 0.15

u_time is simply one of the uniforms that we pass to our shader from the WebGL program.

The only difference with the u_frontTexture uniform is that we increase its value on each render loop and pass its new value to the shader, so that it updates the mask’s position.

Here’s a live preview of the mask going in a circle:

Adding the background image

In order to add the background image we’ll do the exact opposite of what we did for the foreground image.

Let’s go one step at a time.

First, in the JS class, pass the shader the background image in the same way we did for the foreground image:

const backTexture = new Texture("src/images/lantern-bw.jpg");
plane.material.setTexture("u_backTexture", backTexture);

Then, tell the fragment shader that we’re passing it that u_backTexture and initialize another vec3 variable:

// This goes at the top of the file
uniform sampler2D backTexture;

// Add this after `vec3 frontImage = ...`
vec3 backgroundImage = texture2D(iChannel1, uv * 0.5   0.5).rgb;

When you do a quick test by replacing

color = frontImage * mask;


color = backImage * mask;

you’ll see the background image.

But for this one, we have to invert the mask to make it behave the opposite way.

Inverting a number is really easy, the formula is:

invertedNumber = 1 - 

So, let’s apply the inverted mask to the background image:

backImage *= (1.0 - mask);

Here, we’re applying the same mask we added to the foreground image, but since we inverted it, the effect is the opposite.

Putting it all together

At this point, we can refactor the declaration of the two images by directly applying their masks.

vec3 frontImage = texture2D(u_frontTexture, uv * 0.5   0.5).rgb * mask;
vec3 backImage = texture2D(u_backTexture, uv * 0.5   0.5).rgb * (1.0 - mask);

We can now display both images by adding backImage to frontImage:

color = backImage   frontImage;

That’s it, here’s a live example of the desired effect:

Distorting the mask

Cool uh? But it’s not over yet! Let’s tweak it a bit by distorting the mask.

To do so, we first have to create a new vec2 variable:

vec2 maskUV = vec2(
  uv.x   sin(u_time * 0.03) * sin(uv.y * 5.0) * 0.15,
  uv.y   cos(u_time * 0.03) * cos(uv.x * 10.0) * 0.15

Then, replace uv with maskUV in the mask declaration

vec3 mask = Rectangle(maskSize, maskUV, maskPosition, maskColor);

In maskUV, we’re using some math to add uv values based on the u_time uniform and the current uv.

Try tweaking those values by yourself to see different effects.

Distorting the foreground image

Let’s now distort the foreground image the same way we did for the mask, but with slightly different values.

Create a new vec2 variable to store the foreground image uvs:

vec2 frontImageUV = vec2(
  (uv.x   sin(u_time * 0.04) * sin(uv.y * 10.) * 0.03),
  (uv.y   sin(u_time * 0.03) * cos(uv.x * 15.) * 0.05)

Then, use that frontImageUV instead of the default uv when declaring frontImage:

vec3 frontImage = texture2D(u_frontTexture, frontImageUV * 0.5   0.5).rgb * mask;

Voilà! Now both the mask and the image have a distortion effect applied.

Again, try tweaking those numbers to see how the effect changes.

10 – Adding mouse control

What we’ve made so far is really cool, but we could make it even cooler by adding some mouse control like making it fade in/out when the mouse hovers/leaves the plane and making the mask follow the cursor.

Adding fade effects

In order to detect the mouseover/mouseleave events on a mesh and execute some code when those events occur we have to use BabylonJS’s actions.

Let’s start by importing some new modules:

import { ActionManager } from "@babylonjs/core/Actions/actionManager";
import { ExecuteCodeAction } from "@babylonjs/core/Actions/directActions";
import "@babylonjs/core/Culling/ray";

Then add this code after the creation of the plane:

this.plane.actionManager = new ActionManager(this.scene);

  new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, () =>

  new ExecuteCodeAction(ActionManager.OnPointerOutTrigger, () =>

Here we’re telling the plane’s ActionManager to listen for the PointerOver and PointerOut events and execute the onPlaneHover() and onPlaneLeave() methods, which we’ll add right now:

onPlaneHover() {

onPlaneLeave() {

Some notes about the code above

Please note that I’ve used this.plane instead of just plane; that’s because we’ll have to access it from within the mousemove event’s callback later, so I’ve refactored the code a bit.

ActionManager allows us to listen to certain events on a target, in this case the plane.

ExecuteCodeAction is a BabylonJS action that we’ll use to execute some arbitrary code.

ActionManager.OnPointerOverTrigger and ActionManager.OnPointerOutTrigger are the two events that we’re listening to on the plane. They behave exactly like the mouseenter and mouseleave events for DOM elements.

To detect hover events in WebGL, we need to “cast a ray” from the position of the mouse to the mesh we’re checking; if that ray, at some point, intersects with the mesh, it means that the mouse is hovering it. This is why we’re importing the @babylonjs/core/Culling/ray module; BabylonJS will take care of the rest.

Now, if you test it by hovering and leaving the mesh, you’ll see that it logs hover and leave.

Now, let’s add the fade effect. For this, I’ll use the GSAP library, which is the de-facto library for complex and high-performant animations.

First, install it:

yarn add gsap

Then, import it in our class

import gsap from 'gsap

and add this line to the constructor

this.maskVisibility = { value: 0 };

Finally, add this line to the registerBeforeRender()‘s callback function

this.plane.material.setFloat( "u_maskVisibility", this.maskVisibility.value);

This way, we’re sending the shader the current value property of this.maskVisibility as a new uniform called u_maskVisibility.

Refactor the fragment shader this way:

// Add this at the top of the file, like any other uniforms
uniform float u_maskVisibility;

// When declaring `maskColor`, replace `1.0` with the `u_maskVisibility` uniform
vec3 maskColor = vec3(u_maskVisibility);

If you now check the result, you’ll see that the foreground image is not visible anymore; what happened?

Do you remember when I wrote that “for shaders, everything is a number”? That’s the reason! The u_maskVisibility uniform equals 0.0, which means that the mask is invisible.

We can fix it in few lines of code. Open the JS code and refactor the onPlaneHover() and onPlaneLeave() methods this way:

onPlaneHover() {, {
    duration: 0.5,
    value: 1

onPlaneLeave() {, {
    duration: 0.5,
    value: 0

Now, when you hover or leave the plane, you’ll see that the mask fades in and out!

(And yes, BabylonJS has it’s own animation engine, but I’m way more confident with GSAP, that’s why I opted for it.)

Make the mask follow the mouse cursor

First, add this line to the constructor

this.maskPosition = { x: 0, y: 0 };

and this to the addListeners() method:

window.addEventListener("mousemove", () => {
  const pickResult = this.scene.pick(

  if (pickResult.hit) {
    const x = pickResult.pickedPoint.x / this.plane.scaling.x;
    const y = pickResult.pickedPoint.y / this.plane.scaling.y;

    this.maskPosition = { x, y };

What the code above does is pretty simple: on every mousemove event it casts a ray with this.scene.pick() and updates the values of this.maskPosition if the ray is intersecting something.

(Since we have only a single mesh we can avoid checking what mesh is being hit by the ray.)

Again, on every render loop, we send the mask position to the shader, but this time as a vec2. First, import the Vector2 module together with Vector3

import { Vector2, Vector3 } from "@babylonjs/core/Maths/math";

Add this in the runRenderLoop callback function

  new Vector2(this.maskPosition.x, this.maskPosition.y)

Add the u_maskPosition uniform at the top of the fragment shader

uniform vec2 u_maskPosition;

Finally, refactor the maskPosition variable this way

vec3 maskPosition = vec2(
  u_maskPosition.x * uPlaneRatio - 0.15,
  u_maskPosition.y - 0.15

Side note; I’ve adjusted the x using the uPlaneRatio value because at the beginning of the main() function I did the same with the shader’s uvs

And here you can see the result of your hard work:


As you can see, doing these kind of things doesn’t involve too much code (~150 lines of JavaScript and ~50 lines of GLSL, including comments and empty lines); the hard part with WebGL is the fact that it’s complex by nature, and it’s a very vast subject, so vast that many times I don’t even know what to search on Google when I get stuck.

Also, you have to study a lot, way more than with “standard” website development. But in the end, it’s really fun to work with.

In this tutorial, I tried to explain the whole process (and the reasoning behind everything) step by step, just like I want someone to explain it to me; if you’ve reached this point of this tutorial, it means that I’ve reached my goal.

In any case, thanks!


The lantern image is by Vladimir Fetodov


While there are plenty of fantastic color palette generators available on the web, in this post, we thought we’d share our favorite color tools specific to UI design.

It’s important to keep in mind that choosing colors for user interfaces calls for a different set of requirements than for example, a graphic design project. Not only does UI design require a comprehensive set of colors with a range of variations and shades — but designers also need to think about how color will add to the user experience of a digital product. This means carefully considering color semantics and ensuring designs are accessible, all while remaining on-brand.

It’s no surprise that color is one of the most essential foundations for a digital product’s design language, so it’s crucial that you choose your color palette with intention. Below are a handful of UI color picking tools we recommend that will help ensure the effectiveness of your designs and of course, keep them looking nice and polished!

1. Accessible Color Matrix

When it comes to product design, we should all be keeping accessibility in mind. Ensuring your UI’s color contrasts are in line with the Web Content Accessibility Guidelines (WCAG) is one of the ways you can do this. Accessible Color Matrix makes it super easy to test out potential color schemes for your user interfaces. What makes the tool especially unique is the ability to test a range of colors rather than just two at a time. Check it out:


2. Eva Colors

This handy AI tool generates a semantic color palette based on your brand’s primary color — each color is assigned to a purpose you should apply it to: success, info, warning, and danger. On top of that, Eva Colors produces the various shades for each color generated and has a really easy export feature. You can even toggle to view the colors in both light and dark mode! Simple, effective, and intuitive.


3. Palx

Palx is an automatic UI color palette generator. Input your base color, and the system instantly provides you with a full-spectrum color palette based on the primary hex code entered. The colors generated work harmoniously together and you can also easily export all of them by scrolling to the bottom of the page.


4. Copy Palette

Created by Dribbbler Dimitris Raptis, Copy Palette enables you to create consistent monochromatic color combinations and export them directly into your favorite design software. According to Dimitris, the idea came to him after struggling repeatedly with generating the consistent monochromatic color palettes he envisioned for his interface designs. We love that Copy Palette also lets you can adjust parameters like the contrast ratio of shades and number of color variations.


5. Cloud Flare

Cloud Fare is a custom tooling that not only helps you build out color sets, but also preview palettes against a plethora of UI elements like headers, icons, buttons, and much more. The best part is that you can check each palette’s accessibility contrast scores and edit those colors as needed. It’s an insanely helpful two-in-one color palette and visualization tool to help you work more seamlessly with color. Check out their extensive instructions so you can make use out of all of their awesome features!


6. Palettte

Use Palette to create and sample color schemes in which colors seamlessly smooth into each other. You have full editing capabilities in terms of fine-tuning hue and saturation, and adding more color swatches as needed. Simply click on the plus icon at the top left corner of the tool to get started and when you’ve got a palette finalized, hit the export button at the top right! If you already have a color palette on hand, you can easily import and edit it further to get your desired values.


7. Open Color

If you prefer to simply pull a pre-made UI color palette that’s guaranteed to work well, check out Open Color. The website essentially provides an open-source color scheme that is optimized for user interfaces. If you don’t have a brand color set in stone, this is a sure-fire way to ensure your UI color palette is both effective and attractive. And, if you’re new to using color in UI design, check out their Instruction tab which includes a helpful manual specifying the intended use of each color!


For more web design resources, check out our roundup of accessibility tools to evaluate your design’s contrast ratio, 7 best illustration resources for web design projects, and learn how to avoid the top 5 mistakes new web designers make when starting out.

Find more Community stories on our blog Courtside.
Have a suggestion? Contact


Joey DiGangi

Animation provides a great medium through which an entrepreneur can easily showcase key concepts about his or her business. One of the most fascinating things, in my opinion, about animation is how the barriers to entry are slowly eroding. Think back to elementary and middle school art classes, where your teacher tried to explain the finer points by making it look and sound easy through examples, only to discover that it’s significantly harder to do when you actually put pencil to paper.

I’m starting to learn, however, that there’s more than one way to create animated content to support your startup— and some are more accessible than you’d think!

Rather than getting into which tools and methods work best, it makes more sense to start by looking at the building blocks needed to create an animation in the first place.

As a novice, I’m not the best person to break down the mechanics of what constitutes good animation. Fortunately, I have been staying in touch with Amanda Kopacz, a former classmate of mine from Juniata College, and current student at the Rochester Institute of Technology, where she studies Film and Animation with a concentration in 3D Animation.

She was happy to offer advice to someone that’s just getting started, talk about some of the more technical aspects of creating animations, and explain how beginners can learn the basics.

Some of Amanda’s work created on Animation Desk!

As a student pursuing a terminal degree in Fine Arts, Amanda’s learned to use a myriad of Hollywood-grade tools and functions to complete her work.

I’ll use programs like Photoshop, After Effects, and Autodesk Maya to create my animations,” she told me. “These tools have so many different functions that I feel like I’m always learning. It takes me anywhere from a few weeks to a few months to start to get the hang of them.

But Amanda got her start by sticking to the fundamentals of animation and working her way up to the more-advanced tools she’s using in her classwork today.

“I started out using basic techniques, like the classic pencil and paper, or creating stop motion animations with a cell phone camera,” she explained.

Stop motion is the term for animation created by taking a series of pictures of a physical object as it makes tiny, incremental movements to form an action sequence. Think about any famous claymation video — that’s stop motion.

Image Taken from Claymation Film, “Nightmare Before Halloween”

When using pencil and paper, one of the challenges of creating each frame is remembering where the previous one left off. For this, Amanda suggests that beginners use a technique called onion skin, where you lay new paper over completed drawings to use as a reference point.

“The trick requires you to put the paper down and have the light shine through so you can see your work clearly.”

Advances in technology and software are not only creating more possibilities for professional animators, but they’re also making it easier for beginners to use advanced techniques early on.

“I worked for a STEM camp this summer called MY TECH LEARNING,” she said. “I taught animation, and used FlipaClip to help the students learn.”

FlipaClip is a basic animation tool available on iOS and Android that makes it easy to start animating as a student thanks to its support of multiple-layer drawings, storyboarding, and straightforward interface.

I also have enjoyed using Animation Desk,” she mentioned. “It is super-helpful for someone starting to learn the basics of animation. There are different brushes to play around with and it’s easy to duplicate frames. It even supports onion skin, which allows you to see the frames in front of and behind the one you’re working on.”

Promoting Your Startup

My personal interest in animation came when I wanted to explore its usefulness at helping me convey some of the core functions of my startup’s app.

We released the AssureTech Mobile App just over a year ago to help people with severe food allergies — like myself — travel safely in foreign countries. The app offers a number of different translation and emergency tools that can sometimes be difficult to explain in a succinct way.

One colleague suggested that I look at something called whiteboard animation. Even if you don’t know the term, you’re probably familiar with the finished product: a video with terms and workflows drawn out in a logical procession. They’re all over social media and are one of the easiest ways to bring complex or abstract ideas “down to earth.”

The artist uses a white board to do an animation,” said Amanda. “So you can start with an image, take a picture, and then add, subtract, or change it in some way; take another picture; and continue to do that to illustrate a point. Once you have all the pictures, you put them together to create the animation.

I’m still getting the hang of this, but I think it’s a good — and frankly, quite fun — way to design marketing material for my startup!

This was my first-ever attempt at whiteboard animation. I used Animation Desk to complete the work.

For Aspiring Artists

Amanda and other aspiring animators or creative entrepreneurs — even if you’re just starting out — can use their skillsets in a number of different ways to build a professional portfolio and create opportunities to earn additional income.

I’ve enjoyed working as a freelance artist,” explains Amanda. When she’s not teaching animation or attending class, she’s creating opportunities for herself by offering her photography, video — and, of course — animation skills to businesses looking for support.

I started working as a freelance animator for Kdan Mobile this past spring,” she said. “That’s when I was first introduced to Animation Desk.

Amanda was hired to create promotional animations for the company’s 8th Annual iAniMagic Contest. The Contest is held for animators — from first-time learners to professionals — and offers cash prizes, professional recognition, and more to artists with the top submissions.

Whether you’ve got a startup that could benefit from quick-and-easy content, or a creative looking to add to to your portfolio, I encourage you to give animation a try!


9th Oct 2019

When you create a custom focus style, you want to think about four things:

  1. Adding an outline
  2. Creating animations that contain movement
  3. Changing the background color
  4. Changing the text color

I wrote more about this in my article on designing focus. During my research, I found three kinds of focus style I liked.

  1. The one on Smashing Magazine
  2. The one on WTF Forms
  3. The one on Slack
Focus styles on Smashing Mag, WTF Forms and Slack

Today, I want to show you how to create these focus styles and use them effortlessly across your website.

Creating the focus for Smashing Magazine

Smashing Magazine uses a large dotted outline for focus. To create this focus style, you set the outline property to 3px dotted.

Focus styles on Smashing Magazine.
*:focus {
  outline: 3px dotted #761b15;

See the Pen
Focus style Smashing Mag (default)
by Zell Liew (@zellwk)
on CodePen.

If you want to change the color of the outline, you can change the outline-color property.

.red-background *:focus {
  outline-color: white;

See the Pen
Focus style Smashing Mag (changing outline colors)
by Zell Liew (@zellwk)
on CodePen.

Alternatively, you can use CSS Variables.

:root {
  --outline-color: #761b15;

*:focus {
  outline: 3px dotted var(--outline-color);

.red-background {
  --outline-color: white;

See the Pen
Focus style Smashing Mag (with CSS Variables)
by Zell Liew (@zellwk)
on CodePen.

Creating focus styles for WTF Forms

The focus style for WTF forms contains two parts:

  1. A white border
  2. A blue border
Focus styles for WTF Forms.

This style can be created with box-shadow. The idea is you create two shadows:

  1. The first shadow with the background’s color
  2. The second shadow with the focus’s color
*:focus {
  outline: none;
  box-shadow: 0 0 0 .075rem #fff, 
              0 0 0 .2rem #0069d4;  

If you want to change the focus color, you need to rewrite the entire box-shadow.

.blue-background *:focus {
  outline: none;
  box-shadow: 0 0 0 .075rem #0069d4, 
              0 0 0 .2rem #fff;  

Note: WTF Forms does not have styles for links and buttons. Only form elements. It doesn’t have styles for a darker background either. I created this demo according to what I thought looks okay.

See the Pen
WTF Forms focus style
by Zell Liew (@zellwk)
on CodePen.

There’s an easier way. If you used CSS variables, you only need to switch the colors.

:root {
  --focus-inner-color: #fff;
  --focus-outer-color: #0069d4;

*:focus {
  outline: none;
  box-shadow: 0 0 0 .075rem var(--focus-inner-color), 
              0 0 0 .2rem var(--focus-outer-color);

.blue-background {
  --focus-inner-color: #0069d4;
  --focus-outer-color: #fff;

See the Pen
WTF Forms focus style (with CSS Variables)
by Zell Liew (@zellwk)
on CodePen.

Creating focus styles for Slack

The focus style on Slack contains two parts:

  1. A dark blue outline
  2. A light-blue border
Focus styles on Slack.

This focus style can be created with the same technique as WTF Forms.

*:focus {
  outline: none;
  box-shadow: 0 0 0 2px hsla(210, 52%, 42%, 1.00), 
              0 0 0 .6rem hsla(200, 72%, 83%, 0.75);

The CSS Variables trick works wonders if you need to change colors.

:root {
  --focus-inner-color: hsla(210, 52%, 42%, 1.00);
  --focus-outer-color: hsla(200, 72%, 83%, 0.75);

*:focus {
  outline: none;
  box-shadow: 0 0 0 2px var(--focus-inner-color), 
              0 0 0 .6rem var(--focus-outer-color);

.dark {
  --focus-inner-color: hsla(0, 0%, 100%, 0.75);
  --focus-outer-color: hsla(0, 0%, 100%, 0.25);

See the Pen
Slack Forms focus style (with CSS Variables)
by Zell Liew (@zellwk)
on CodePen.

If you use this technique on elements with borders, you might want to remove the borders. It’s not pretty to see two stacking borders.

button:focus {
  border-color: transparent;

See the Pen
Slack Forms focus style (improved border)
by Zell Liew (@zellwk)
on CodePen.

Combined demo

I combined the different methods onto one demo for you to play with. Here it is:

See the Pen
Focus style
by Zell Liew (@zellwk)
on CodePen.

Thanks for reading. Did this article help you out? If it did, I hope you consider sharing it. You might help someone else out. Thanks so much!


A configurator for creating unique fullscreen image animations with WebGL distortion effects powered by Three.js.


From our monthly sponsor: Take WordPress to a whole new level with Divi’s incredibly advanced visual builder technology. Try it for free.

In one of our previous tutorials we showed you how to create thumbnail to fullscreen WebGL distortion animations. Today we would like to invite you to build your own personalized effects by using the configurator we’ve created.

We’ll briefly go over some main concepts so you can make full use of the configurator. If you’d like to understand the main idea behind the work, and why the animations behave the way they do in more depth, we highly recommend you to read the main tutorial Creating Grid-to-Fullscreen Animations with Three.js.

Basics of the configurator

The configurator allows you to modify all the details of the effect, making it possible to create unique animations. Even though you don’t have to be a programmer to create your own effect, understanding the options available will give you more insight into what you can achieve with it.

To see your personalized effect in action, either click on the image or drag the Progress bar. The Duration option sets the time of the whole animation.

Under Easings you can control the “rate of change” of your animation. For example:

  • Power1.easeOut: Start really fast but end slowly
  • Power1.easeInOut: Start and end slowly, but go really fast in the middle of the animation
  • Bounce: Bounce around like a basketball

The simplest easings to play around with are Power0-4 with ease-out. If you would like to know the difference between each easing, check out this ease visualizer.

Note that the configurator automatically saves your progress for later use. Feel free to close the page and come back to it later.

Timing, Activation and Transformation

Timing, Activation and Transformation are concepts that come from our previous tutorial. Each on of them has their own list of types, that also have their own set of options for you to explore.

You can explore them by changing the types, and expanding the respective options tab. When you swap one type for another, your previous set of options is saved in case you want to go back to it.



The timing function maps the activation into actual progress for each vertex. Without timing, the activation doesn’t get applied and all the vertices move at the same rate. Set timing type to none to see it in action.

  • SameEnd: The vertices have different start times, but they all end at the same time. Or vice versa.
  • sections: Move by sections, wait for the previous section to finish before starting.

The same activation with a different timing will result in a very different result.


The activation determines how the plane is going to move to full screen:

  • side: From left to right.
  • corners: From top-left to bottom-right
  • radial: From the position of the mouse
  • And others.

For a visual representation of the current activation, toggle debug activation and start the animation to see it in action.


Transform the plane into a different shape or position over the course of the animation:

  • Flip: Flip the plane on the X axis
  • simplex: Move the vertices with noise over the while transitioning
  • wavy: Make the plane wavy while transitioning
  • And more

Some effects, use seed for their inner workings. You can set the initial seed and determine when this seed is going to be randomized.

Note that although these three concepts allow for a large amount of possible effects, some options won’t work quite well together.

Sharing your effect

To share the effect you can simply copy and share the URL.

We would love to see what you come up with. Please share your effect in the comments or tag us on Twitter using @anemolito and @codrops.

Adding your effect to your site

Now that you made your custom effect, it is time to add it to your site. Let’s see how to do that, step by step.

First, download the code and copy some of the required files over:

  • THREEjs: js/three.min.js
  • TweenLite: js/TweenLite.min.js
  • ImagesLoaded: js/imagesloaded.pkgd.min.js
  • For preloading the images
  • The effect’s code: js/GridToFullscreenEffect.js
  • TweenLite’s CSSPlugin: js/CSSPlugin.min.js (optional)
  • TweenLite’s EasePack:js/EasePack.min.js (optional; if you use the extra easings)

Include these in your HTML file and make sure to add js/GridToFullscreenEffect.js last.

Now let’s add the HTML structure for the effect to work. We need two elements:

  • div#App: Where our canvas is going to be
  • div#itemsWrapper: Where our HTML images are going to be


Note: You can use any IDs or classes you want as long as you use them when instantiating the effect.

Inside #itemsWrapper we are going to have the HTML items for our effect.

Our HTML items inside #itemsWrapper can have almost any structure. The only requirement is that it has two image elements as the first two children of the item.

The first element is for the small-scale image and the second element is the large-scale image.

Aside from that, you can have any caption or description you may want to add at the bottom. Take a look at how we did ours in our previous post:

An image

Our Item Title

Our Item Description


You may add as many items as you want. If you add enough items to make your container scrollable. Make sure to send your container in the options, so the effect can account for its scroll.

With our HTML items in place, let’s get the effect up and running.

We’ll instantiate GridToFullscreenEffect, add our custom options, and initialize it.

Our effect is now mounted and working. But clicking on an item makes the image disappear and we end up with a black square.

The effect doesn’t take care of loading the images. Instead, it requires you to give them to the effect whenever they load. This might seem a bit inconvenient, but it allows you to load your images the way it’s most suitable for your application.

You could preload all the images upfront, or you could only load the images that are on screen, and load the other ones when needed. It’s up to how you want to do that.

We decided to preload all the images using imagesLoaded like this:

imagesLoaded(document.querySelectorAll("img"), instance => {

    // Make Images sets for creating the textures.
    let images = [];
    for (var i = 0, imageSet = {}; i 

With that last piece of code, our effect is running and it shows the correct images. If you are having troubles with adding it to your site, let us know!

Our Creations

While working on this configurator, we managed to create some interesting results of our own. Here are three examples. You can use the parameters and attach it to the URL or use the settings:

Preset 1

See Here



Preset 2

See Here



Preset 3

See Here



Check out all the demos to explore more presets!

We hope you enjoy the configurator and find it useful for creating some unique animations!


We recently held our first Data workshop here at the Wiredcraft office, focusing on the basics of Google Data Studio so that attendees could understand its uses, tools, and create their first reporting dashboard.

The class had limited spots so that we could give each attendee the guidance they needed to complete the 5 exercises we assigned them – in case you missed out, here’s a quick summary of the workshop, along with the slides and exercises so you can try it out for yourself.

What is Google Data Studio?

Data Studio is a free reporting and data visualization tool from Google. It works best with data from Google Analytics, but it can be connected up to any data source, including raw CSV or Google Sheets. There are also partner connectors that allow you to use data from sites such as Twitter, Facebook or HubSpot.

It allows you to create a reporting dashboard that pulls from your data source in real-time, and create tables and charts that best fit your needs.

Why should I use it?

The biggest reason is automation. There are often standard reports that are regularly requested by our clients that use the same metrics, but are just for a different time period or user segment. By creating a dashboard, clients can self-serve that data, so that they have easier and quicker access to what they need.

Dashboards are linked to the data in real-time, so can pull whatever date range you need.

The second reason is visualization. Tables of data are fine for analysts who are looking for specific numbers, or are used to working with data in this way, but our brains are more suited to seeing patterns using charts instead. Using a dashboard can help to uncover seasonal or long-term insights that may have been missed.

Data Studio offers a range of chart types to visualize your data:

  • Table
  • Scorecard
  • Time Series
  • Bar
  • Pie
  • Geo Map
  • Line
  • Area
  • Scatter
  • Pivot Table
  • Bullet
  • Treemap
Some of the chart types available in Data Studio.

The third reason is accessibility. Google Analytics, while easier to use than other similar platforms, still has a fairly steep learning curve in learning how data is recorded and where reports are kept. By creating a dashboard, clients do not need to learn how to use the GA admin, instead having all data they need in one single report.

So how do I get started creating my first dashboard?

Luckily, the slides from the workshop can be downloaded here. There are step by step instructions to help you get started:

  • Getting access to the Google Demo account (in case you don’t have an existing data set)
  • Creating a blank dashboard
  • Connecting your data source

From there, we have 5 exercises, building up in difficulty:

  • Creating a scorecard
  • Adding various charts and understanding how metrics and dimensions interact
  • Exploring data with a heat map to spot trends
  • Understanding calculated fields to create your own metrics
  • Custom conversion funnels to see how users drop off at each shopping stage

There are hints given for each exercise, but the last one in particular can be a little tricky! Let me know at [email protected] if you get stuck.

Samantha Cheah

Online Marketing & Analytics

Posted on September 04, 2019
in Data

Stay tuned

Follow our newsletter or our WeChat account; every other week you’ll receive news about what we’re up to, our events and our insights on digital products, omnichannel, cross-border ecommerce or whatever the team fancies.

  • Add us on WeChat

  • Subscribe to our newsletter


One of my favorite ways of adding icons to a site is by including them as data URL background images to pseudo-elements (e.g. ::after) in my CSS. This technique offers several advantages:

  • They don’t require any additional HTTP requests other than the CSS file.
  • Using the background-size property, you can set your pseudo-element to any size you need without worrying that they will overflow the boundaries (or get chopped off).
  • They are ignored by screen readers (at least in my tests using VoiceOver on the Mac) so which is good for decorative-only icons.

But there are some drawbacks to this technique as well:

  • When used as a background-image data URL, you lose the ability to change the SVG’s colors using the “fill” or “stroke” CSS properties (same as if you used the filename reference, e.g. url( 'some-icon-file.svg' )). We can use filter() as an alternative, but that might not always be a feasible solution.
  • SVG markup can look big and ugly when used as data URLs, making them difficult to maintain when you need to use the icons in multiple locations and/or have to change them.

We’re going to address both of these drawbacks in this article.

The situation

Let’s build a site that uses a robust iconography system, and let’s say that it has several different button icons which all indicate different actions:

  • A “download” icon for downloadable content
  • An “external link” icon for buttons that take us to another website
  • A “right caret” icon for taking us to the next step in a process

Right off the bat, that gives us three icons. And while that may not seem like much, already I’m getting nervous about how maintainable this is going to be when we scale it out to more icons like social media networks and the like. For the sake of this article, we’re going to stop at these three, but you can imagine how in a sophisticated icon system this could get very unwieldy, very quickly.

It’s time to go to the code. First, we’ll set up a basic button, and then by using a BEM naming convention, we’ll assign the proper icon to its corresponding button. (At this point, it’s fair to warn you that we’ll be writing everything out in Sass, or more specifically, SCSS. And for the sake of argument, assume I’m running Autoprefixer to deal with things like the appearance property.)

.button {
  appearance: none;
  background: #d95a2b;
  border: 0;
  border-radius: 100em;
  color: #fff;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  padding: 1em calc( 1.5em   32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: background-color 200ms ease-in-out;

  &:active {
    background: #8c3c2a;

This gives us a simple, attractive, orange button that turns to a darker orange on the hover, focused, and active states. It even gives us a little room for the icons we want to add, so let’s add them in now using pseudo-elements:

.button {

  /* everything from before, plus... */

  &::after {
    background: center / 24px 24px no-repeat; // Shorthand for: background-position, background-size, background-repeat
    border-radius: 100em;
    bottom: 0;
    content: '';
    position: absolute;
    right: 0;
    top: 0;
    width: 48px;

  &--download {

    &::after {
      background-image: url( 'data:image/svg xml;utf-8,' );

  &--external {

    &::after {
      background-image: url( 'data:image/svg xml;utf-8,' );

  &--caret-right {

    &::after {
      background-image: url( 'data:image/svg xml;utf-8,' );

Let’s pause here. While we’re keeping our SCSS as tidy as possible by declaring the properties common to all buttons, and then only specifying the background SVGs on a per-class basis, it’s already starting to look a bit unwieldy. That’s that second downside to SVGs I mentioned before: having to use big, ugly markup in our CSS code.

Also, note how we’re defining our fill and stroke colors inside the SVGs. At some point, browsers decided that the octothorpe (“#”) that we all know and love in our hex colors was a security risk, and declared that they would no longer support data URLs that contained them. This leaves us with three options:

  1. Convert our data URLs from markup (like we have here) to base-64 encoded strings, but that makes them even less maintainable than before by completely obfuscating them; or
  2. Use rgba() or hsla() notation, not always intuitive as many developers have been using hex for years; or
  3. Convert our octothorpes to their URL-encoded equivalents, “#”.

We’re going to go with option number three, and work around that browser limitation. (I will mention here, however, that this technique will work with rgb(), hsla(), or any other valid color format, even CSS named colors. But please don’t use CSS named colors in production code.)

Moving to maps

At this point, we only have three buttons fully declared. But I don’t like them just dumped in the code like this. If we needed to use those same icons elsewhere, we’d have to copy and paste the SVG markup, or else we could assign them to variables (either Sass or CSS custom properties), and reuse them that way. But I’m going to go for what’s behind door number three, and switch to using one of Sass’ greatest features: maps.

If you’re not familiar with Sass maps, they are, in essence, the Sass version of an associative array. Instead of a numerically-indexed array of items, we can assign a name (a key, if you will) so that we can retrieve them by something logical and easily remembered. So let’s build a Sass map of our three icons:

$icons: (
  'download':    '',
  'external':    '',
  'caret-right': '',

There are two things to note here: We didn’t include the data:image/svg xml;utf-8, string in any of those icons, only the SVG markup itself. That string is going to be the same every single time we need to use these icons, so why repeat ourselves and run the risk of making a mistake? Let’s instead define it as its own string and prepend it to the icon markup when needed:

$data-svg-prefix: 'data:image/svg xml;utf-8,';

The other thing to note is that we aren’t actually making our SVG any prettier; there’s no way to do that. What we are doing is pulling all that ugliness out of the code we’re working on a day-to-day basis so we don’t have to look at all that visual clutter as much. Heck, we could even put it in its own partial that we only have to touch when we need to add more icons. Out of sight, out of mind!

So now, let’s use our map. Going back to our button code, we can now replace those icon literals with pulling them from the icon map instead:

&--download {

  &::after {
    background-image: url( $data-svg-prefix   map-get( $icons, 'download' ) );

&--external {

  &::after {
    background-image: url( $data-svg-prefix   map-get( $icons, 'external' ) );

&--next {

  &::after {
    background-image: url( $data-svg-prefix   map-get( $icons, 'caret-right' ) );

Already, that’s looking much better. We’ve started abstracting out our icons in a way that keeps our code readable and maintainable. And if that were the only challenge, we’d be done. But in the real-world project that inspired this article, we had another wrinkle: different colors.

Our buttons are a solid color which turn to a darker version of that color on their hover state. But what if we want “ghost” buttons instead, that turn into solid colors on hover? In this case, white icons would be invisible for buttons that appear on white backgrounds (and probably look wrong on non-white backgrounds). What we’re going to need are two variations of each icon: the white one for the hover state, and one that matches button’s border and text color for the non-hover state.

Let’s update our button’s base CSS to turn it in from a solid button to a ghost button that turns solid on hover. And we’ll need to adjust the pseudo-elements for our icons, too, so we can swap them out on hover as well.

.button {
  appearance: none;
  background: none;
  border: 3px solid #d95a2b;
  border-radius: 100em;
  color: #d95a2b;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: bold;
  line-height: 1;
  padding: 1em calc( 1.5em   32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: 200ms ease-in-out;
  transition-property: background-color, color;

  &:active {
    background: #d95a2b;
    color: #fff;

Now we need to create our different-colored icons. One possible solution is to add the color variations directly to our map… somehow. We can either add new different-colored icons as additional items in our one-dimensional map, or make our map two-dimensional.

One-Dimensional Map:

$icons: (
  'download-white':  '',
  'download-orange': '',

Two-Dimensional Map:

$icons: (
  'download': (
    'white':  '',
    'orange': '',

Either way, this is problematic. By just adding one additional color, we’re going to double our maintenance efforts. Need to change the existing download icon with a different one? We need to manually create each color variation to add it to the map. Need a third color? Now you’ve just tripled your maintenance costs. I’m not even going to get into the code to retrieve values from a multi-dimensional Sass map because that’s not going to serve our ultimate goal here. Instead, we’re just going to move on.

Enter string replacement

Aside from maps, the utility of Sass in this article comes from how we can use it to make CSS behave more like a programming language. Sass has built-in functions (like map-get(), which we’ve already seen), and it allows us to write our own.

Sass also has a bunch of string functions built-in, but inexplicably, a string replacement function isn’t one of them. That’s too bad, as its usefulness is obvious. But all is not lost.

Hugo Giradel gave us a Sass version of str-replace() here on CSS-Tricks in 2014. We can use that here to create one version of our icons in our Sass map, using a placeholder for our color values. Let’s add that function to our own code:

@function str-replace( $string, $search, $replace: '' ) {

  $index: str-index( $string, $search );

  @if $index {
    @return str-slice( $string, 1, $index - 1 )   $replace   str-replace( str-slice( $string, $index   str-length( $search ) ), $search, $replace);

  @return $string;

Next, we’ll update our original Sass icon map (the one with only the white versions of our icons) to replace the white with a placeholder (%%COLOR%%) that we can swap out with whatever color we call for, on demand.

$icons: (
  'download':    '',
  'external':    '',
  'caret-right': '',

But if we were going to try and fetch these icons using just our new str-replace() function and Sass’ built-in map-get() function, we’d end with something big and ugly. I’d rather tie these two together with one more function that makes calling the icon we want in the color we want as simple as one function with two parameters (and because I’m particularly lazy, we’ll even make the color default to white, so we can omit that parameter if that’s the color icon we want).

Because we’re getting an icon, it’s a “getter” function, and so we’ll call it get-icon():

@function get-icon( $icon, $color: #fff ) {

  $icon:        map-get( $icons, $icon );
  $placeholder: '%%COLOR%%';

  $data-uri: str-replace( url( $data-svg-prefix   $icon ), $placeholder, $color );

  @return str-replace( $data-uri, '#', '#' );

Remember where we said that browsers won’t render data URLs that have octothorpes in them? Yeah, we’re str-replace()ing that too so we don’t have to remember to pass along “#” in our color hex codes.

Side note: I have a Sass function for abstracting colors too, but since that’s outside the scope of this article, I’ll refer you to my get-color() gist to peruse at your leisure.

The result

Now that we have our get-icon() function, let’s put it to use. Going back to our button code, we can replace our map-get() function with our new icon getter:

&--download {

  &::before {
    background-image: get-icon( 'download', #d95a2b );

  &::after {
    background-image: get-icon( 'download', #fff ); // The ", #fff" isn't strictly necessary, because white is already our default

&--external {

  &::before {
    background-image: get-icon( 'external', #d95a2b );

  &::after {
    background-image: get-icon( 'external' );

&--next {

  &::before {
    background-image: get-icon( 'arrow-right', #d95a2b );

  &::after {
    background-image: get-icon( 'arrow-right' );

So much easier, isn’t it? Now we can call any icon we’ve defined, with any color we need. All with simple, clean, logical code.

  • We only ever have to declare an SVG in one place.
  • We have a function that gets that icon in whatever color we give it.
  • Everything is abstracted out to a logical function that does exactly what it looks like it will do: get X icon in Y color.

Making it fool-proof

The one thing we’re lacking is error-checking. I’m a huge believer in failing silently… or at the very least, failing in a way that is invisible to the user yet clearly tells the developer what is wrong and how to fix it. (For that reason, I should be using unit tests way more than I do, but that’s a topic for another day.)

One way we have already reduced our function’s propensity for errors is by setting a default color (in this case, white). So if the developer using get-icon() forgets to add a color, no worries; the icon will be white, and if that’s not what the developer wanted, it’s obvious and easily fixed.

But wait, what if that second parameter isn’t a color? As if, the developer entered a color incorrectly, so that it was no longer being recognized as a color by the Sass processor?

Fortunately we can check for what type of value the $color variable is:

@function get-icon( $icon, $color: #fff ) {

  @if 'color' != type-of( $color ) {

    @warn 'The requested color - "'   $color   '" - was not recognized as a Sass color value.';
    @return null;

  $icon:        map-get( $icons, $icon );
  $placeholder: '%%COLOR%%';
  $data-uri:    str-replace( url( $data-svg-prefix   $icon ), $placeholder, $color );

  @return str-replace( $data-uri, '#', '#' );

Now if we tried to enter a nonsensical color value:

&--download {

  &::before {
    background-image: get-icon( 'download', ce-nest-pas-un-couleur );

…we get output explaining our error:

Line 25 CSS: The requested color - "ce-nest-pas-un-couleur" - was not recognized as a Sass color value.

…and the processing stops.

But what if the developer doesn’t declare the icon? Or, more likely, declares an icon that doesn’t exist in the Sass map? Serving a default icon doesn’t really make sense in this scenario, which is why the icon is a mandatory parameter in the first place. But just to make sure we are calling an icon, and it is valid, we’re going to add another check:

@function get-icon( $icon, $color: #fff ) {

  @if 'color' != type-of( $color ) {

    @warn 'The requested color - "'   $color   '" - was not recognized as a Sass color value.';
    @return null;

  @if map-has-key( $icons, $icon ) {

    $icon:        map-get( $icons, $icon );
    $placeholder: '%%COLOR%%';
    $data-uri:    str-replace( url( $data-svg-prefix   $icon ), $placeholder, $color );

    @return str-replace( $data-uri, '#', '#' );

  @warn 'The requested icon - "'   $icon   '" - is not defined in the $icons map.';
  @return null;

We’ve wrapped the meat of the function inside an @if statement that checks if the map has the key provided. If so (which is the situation we’re hoping for), the processed data URL is returned. The function stops right then and there — at the @return — which is why we don’t need an @else statement.

But if our icon isn’t found, then null is returned, along with a @warning in the console output identifying the problem request, plus the partial filename and line number. Now we know exactly what’s wrong, and when and what needs fixing.

So if we were to accidentally enter:

&--download {

  &::before {
    background-image: get-icon( 'ce-nest-pas-une-icône', #d95a2b );

…we would see the output in our console, where our Sass process was watching and running:

Line 32 CSS: The requested icon - "ce-nest-pas-une-icône" - is not defined in the $icons map.

As for the button itself, the area where the icon would be will be blank. Not as good as having our desired icon there, but soooo much better than a broken image graphic or some such.


After all of that, let’s take a look at our final, processed CSS:

.button {
  -webkit-appearance: none;
      -moz-appearance: none;
          appearance: none;
  background: none;
  border: 3px solid #d95a2b;
  border-radius: 100em;
  color: #d95a2b;
  cursor: pointer;
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  padding: 1em calc( 1.5em   32px ) 0.9em 1.5em;
  position: relative;
  text-align: center;
  text-transform: uppercase;
  transition: 200ms ease-in-out;
  transition-property: background-color, color;
.button:hover, .button:active, .button:focus {
  background: #d95a2b;
  color: #fff;
.button::before, .button::after {
  background: center / 24px 24px no-repeat;
  border-radius: 100em;
  bottom: 0;
  content: '';
  position: absolute;
  right: 0;
  top: 0;
  width: 48px;
.button::after {
  opacity: 0;
  transition: opacity 200ms ease-in-out;
.button:hover::after, .button:focus::after, .button:active::after {
  opacity: 1;
.button--download::before {
  background-image: url('data:image/svg xml;utf-8,');
.button--download::after {
  background-image: url('data:image/svg xml;utf-8,');
.button--external::before {
  background-image: url('data:image/svg xml;utf-8,');
.button--external::after {
  background-image: url('data:image/svg xml;utf-8,');
.button--next::before {
  background-image: url('data:image/svg xml;utf-8,');
.button--next::after {
  background-image: url('data:image/svg xml;utf-8,');

Yikes, still ugly, but it’s ugliness that becomes the browser’s problem, not ours.

I’ve put all this together in CodePen for you to fork and experiment. The long goal for this mini-project is to create a PostCSS plugin to do all of this. This would increase the availability of this technique to everyone regardless of whether they were using a CSS preprocessor or not, or which preprocessor they’re using.

“If I have seen further it is by standing on the shoulders of Giants.”

– Isaac Newton, 1675

Of course we can’t talk about Sass and string replacement and (especially) SVGs without gratefully acknowledging the contributions of the others who’ve inspired this technique.


When information architecture design is done well, your website will have a clean, organized feel that better represents your brand. As a result, your website will be positioned to rank higher on Google, increase traffic and convert more sales.

Designing mid-level feature pages is one of the hardest parts of building or revamping your website. How do you divide your business into neat, tidy sections? How many categories should you use? Does it make sense to combine this feature with that one? How do you find the optimal balance of text, images and graphics? What keywords should you target? Are you building these pages for humans to buy your products or search engine crawlers to rank you higher? 

I could go on, but it’s a safe bet that your head is already spinning. Developing the optimal information architecture design for your business can be a major struggle — but it’s definitely one worth putting the time, energy and resources into getting right.

Here are six tips to design better mid-level feature pages:

1. Organise Your Information Architecture into the Right Categories

Instead of rushing to get a new website up as fast as you can, spend some time thinking deeply about natural categories that align with your products, services, and core values. An hour’s brainstorm and quick sketch will probably not suffice here.

proper due-diligence before building out mid-level pages will save you countless headaches down the road

Discuss with your team. Look at your competitors’ sites. Consult an SEO consulting service. Think about providing your customer with the best possible browsing experience. Consider the most important keywords that you want to rank for. Doing proper due-diligence before building out mid-level pages will save you countless headaches down the road.

When we started building mid-level feature pages for our PDF SaaS app, we thought long and hard about how to organise mid-level feature pages so users could access the information that they were seeking quickly and intuitively. After a ton of research and countless conversations with people who brought different vantage points, we decided on three overarching feature page categories: “Create PDF,” “Edit PDF,” and “Convert PDF.” These broad categories covered the three main services that we provide and allowed the flexibility for us to build out 22 more specialised mid-level pages for specific features like compression, and format conversion.

2. Keep it Simple 

You are passionate about your business and want to share your whole story with prospective clients. It’s natural to try to include everything on your mid-level feature pages, but that is a recipe for clutter and confusion. Design a clean, consistent markup that includes a healthy balance of text, images and infographics. Readers tend to skim (or completely skip) text-heavy sections. Try to use bullets, integrate graphics and err on the side of brevity whenever possible.

Make sure that your layouts are consistent across all mid-level pages. People usually notice different layouts on similar level pages. Whether they note the differences consciously or not, the inconsistencies give off an unprofessional vibe and should be smoothed out before going live. We developed a template that was easy to replicate for each additional mid-level page. Each mid-level page follows the same blueprint which simplifies navigation:

  • a header
  • a drag-and-drop upload button
  • three steps of directions
  • two or three images
  • a brief explanation of the specific feature
  • options to explore other features near the bottom of the page

3. Match Each Mid-Level Page With a Primary Keyword That You’re Targeting

Keyword research is an essential step in constructing effective information architecture. Knowing the most important search terms that you want to rank for will help you develop site structure and content that drives the right traffic to your site and keeps it there. There are lots of good tools out there to research search volume and identify related keywords. We used Google Keyword Planner, SEMRush, CanIRank and a few other keyword research tools to figure out the most valuable keywords we should go after. One of the nice things about these three tools is that they give you search volume reports on related keywords as well. Oftentimes, the keyword you expected to be best actually isn’t, and a similar iteration turns out to be a better fit. 

Most of us seasoned website architects aim to match each mid-level feature page with a high-volume keyword that is valuable to our business. By targeting specific related keywords, you are signalling to Google and other search engines what your website is about, and building topical relevance for your site. As you build out multiple mid-level pages targeting specific keywords related to your business, you will boost both your page relevancy (for each individual page) and overall website relevancy. This will help you improve your SERP rankings for the keywords that matter most to your business and drive traffic into your conversion funnel.

Remember to have only one primary keyword per page. The primary keyword should be featured prominently in your H1 header and also used consistently throughout the page. In addition, you should include several “secondary” keywords that are related to the primary keywords. Using your favorite keyword research tool, identify a primary keyword, several secondary keywords, and other related keywords to build your page around. Be cognizant not to have separate mid-level pages targeting similar keywords. It’s easy to have two pages “cannibalisze” each other — which will drive both pages’ rankings down.

4. Include a Clear, Catchy, Concise Call-to-Action

You’ve designed the perfect information architecture, done your keyword research, matched each page to a high-volume keyword, and written stellar content that includes the right balance of keywords. Finally, it’s time to let out a big sigh of relief, right? Well, not exactly. After doing all this work, it would be a shame not to nail your call-to-action. 

After doing all this work, it would be a shame not to nail your call-to-action

After all, your goal is not merely to get traffic to visit and stay on your mid-level page; it’s to move them further down the conversion funnel and actually buy your product or service! Be strategic with your language and graphics to encourage users to try out your service, sign-up for your newsletter, enter a contest, share on social media or take whatever action you want them to take. We have a big “Click to upload” button at the top of the page for users who just want to take action quickly without reading in more detail about the feature. For more investigative readers, we have other “Upload your file” buttons conveniently positioned in the middle of the page content. Our goal is to make using our software so simple and intuitive that users always can easily navigate their way further down the conversion funnel.

5. Design For Your Audience(s)

So in this long haul of building mid-level pages, are search engines or humans our primary audience? Reasonable minds disagree on this one, but for our purposes today, I recommend targeting both — because you will not be successful targeting one but not the other. 

Think about the key steps in your customer’s journey. She hears about a cool new product that you sell and decides to check it out. She searches for it on Google. If your page isn’t optimized properly to rank highly and catch her eye, it really doesn’t matter how amazing or persuasive your content is. Conversely, if your page is optimized to rank atop page 1 but includes a lacklustre call-to-action, you will have a high bounce rate and fail to convert in ways that bring your business tangible value.

Always keep the big picture in mind and design your mid-level feature pages to meet the needs of search engines and humans.

Featured image via DepositPhotos.



Rachel Krause


August 4, 2019


Summary: A portfolio highlighting your design process and past work shows others who you are as a designer. The process of creating a UX-design portfolio allows you to reflect on your skills and achievements.

The word “designer” can mean many different things and a designer role comes with many possible skills and responsibilities. UX-design portfolios showcase who their owners are: the areas in which they specialize, their strengths, their processes, and their design styles.

In this article, I refer to a ‘designer’ as anyone who designs one or several components of the user experience — interaction flows, discrete interface elements, visuals, or omnichannel journeys, whether on a desktop, a touchscreen, or on some other device.

Many of our top 10 recommendations for UX-research portfolios also apply to design portfolios. A common misconception about design portfolios is that they are only made up of final UI designs and screenshots. This article will guide you through the steps of creating a UX-design portfolio that encompasses your entire UX process and not just the shiny artifacts.

What Hiring Managers Are Looking For

As part of our current research on user-experience careers, we surveyed 204 UX professionals in charge of hiring about what they look for in a portfolio. Here are some things they mentioned:

  • “Show me how you started with an opportunity and produced real value for a user and the organization.”
  • “I’m curious to know what isn’t in the design and why, just as much as I’d like to know why elements made it in.”
  • “Don’t just show me the finished product. I want to see the messy process and all the work and research that was put in to land on that shiny polished design. Tell me the problem you were trying to solve, your role, any constraints, project timeline, changes from iteration to iteration and how the research informed the design.”

The “users” of your portfolio will be hiring managers, recruiters, or fellow UX professionals, so your portfolio must appeal to these different groups of people. Think about which capabilities you want to showcase and how each group will understand this information. Very rarely will hiring managers take the time to read your entire portfolio word for word — which is one reason why your portfolio should be scannable and not contain unnecessary detail.

Before designing your portfolio, prioritize what you want to communicate. What are the top three things about you and your work that a reader of your portfolio should take away? Revisit this question once your portfolio is completed to make sure you achieved your goal.

Putting It Together

Step 1: Take Inventory of All Your Projects

UX professionals work on many types of projects and tasks. Therefore, it may be difficult to narrow down what to include in a portfolio. The first step is to take inventory of the projects you’ve worked on.

You’ll want to showcase your specialties through multiple types of work. To do this, consider all your projects and ask yourself the following questions:

  • What am I really good at?
  • Which UX activities do I really like to do?
  • What differentiates me from other designers?
  • On which projects did I bring the most value?
  • From which projects did I learn the most?
  • What interesting stories can I tell about the work that I did?

Prioritize projects that align to the work you’re looking for. When seeking a new job, tailor your project selection to the job duties you want to perform. For example, if you really enjoy prototyping, showcase projects where you created prototypes and how they benefited the ultimate outcome. You don’t want to promote work that you don’t like doing, so be sure to avoid adding in projects that don’t align with your future career goals.

Step 2: Choose 3–5 Projects as Detailed Case Studies

Quality over quantity is the best rule to follow when putting together your portfolio. Since hiring managers don’t have a lot of time to dedicate to each candidate’s portfolio, it’s best to choose a few of your best projects to showcase from your prioritized list you made in the previous step. The projects you choose should align to the work that is described in the job description.

The number of projects you include is not important per se. What’s important is that your portfolio showcases a wide variety of work and skills — so, if you had substantial, varied contributions to a small set of big projects, emphasize the many different activities that you were involved in. 

In addition to visuals for each project, create a case study that includes the following information:

  1. The problem(s) you had to solve or the hypothesis you came up with for solving it
    1. Example: Our application got negative reviews because users weren’t receiving alerts about new sales on the site. Based on the content of the reviews, we hypothesized that users were not aware that they could adjust notification settings in the application.
  2. Your specific role in the project and how you collaborated with others
    1. Example: I was the sole UX designer on an Agile team comprised of 3 developers, a product owner, a scrum master, and a quality engineer. I was responsible for determining the overall design direction of the project, while collaborating with the rest of the team on ideation.
  3. How you came to your proposed solution(s)
    1. Example: Usability testing showed that users did not realize that they could adjust their notification settings. We decided to design the notification settings to be more prominent in the site navigation.
  4. How your proposed solution(s) solved the problem
    1. Example: We conducted additional usability testing with the same tasks and our new design, which showed an increase in findability compared to the previous round of testing. Users were able to adjust their notification settings, which gave them access to new sales alerts.
  5. Challenges you faced, including design concepts that were ultimately not pursued
    1. Example: During ideation, we went through several different design concepts that ultimately did not completely satisfy user needs. One design concept that we prototyped displayed the notification settings in a modal when users logged in, but it caused frustration because people had to close it to complete their original task.
  6. How the project affected the users and the business
    1. Example: Because users were now able to turn on new-sales notifications, sales increased by 15%. Our application reviews have skewed positive and customer-satisfaction survey scores have increased.
  7. What you learned
    1. Example: Because of this project, we realized the importance of prototype testing for exploring new design concepts. It made us test new designs to make sure they were viable solutions before putting in development effort.

These case studies should be displayed in a way that is scannable and easy to follow. Include relevant photos and screenshots that tell the story, including early sketches, whiteboards, research documentation, or final images.

Remember, the final screenshots only tell part of the story. Hiring managers want to understand how you work and giving them a glimpse of your process will help them envision how you fit in with their teams.

Step 3: Choose Your Desired Format

Regardless of format, your portfolio should tell a story. Break up text with visuals and make a clear distinction between projects.

There are three common formats for designer portfolios:


A web-based portfolio is a website or online service that displays your work. Web-based portfolios are the most common medium for designers. Resist the urge to go overboard on a flashy template. Your content should be the main focus and your site should be easy to navigate and consume.

Web Portfolio
A typical web-based portfolio has some form of navigation along with a collection of projects. Users are able to click each project individually to learn more about it.

PDF / Slide Deck

Another popular medium for portfolios is a digital PDF or slide deck, which acts as a presentation of your projects. When creating a digital portfolio, keep a master PDF or slide deck with all of your projects included so you can hide projects depending on the job you’re applying for or the skills you want to highlight.

PDF or Slide Deck Portfolio
Keeping a PDF portfolio based on a slide deck allows you to tailor the projects you show to different audiences. Simply hide the projects that are not relevant to the job.

Physical Artifacts

Physical portfolios are more common with print designers, but you can bring physical artifacts that you use during your design process — such as sketches or paper prototypes — into an interview. Couple your physical pieces with either a web-based or PDF portfolio so that hiring managers can see your work prior to an interview.

Physical Artifacts
Physical artifacts allow you to show rough sketches and process diagrams, including design options that didn’t make it to the final product.

When deciding between formats, ask yourself the following questions:

  • Did the hiring manager specify a format?
  • What costs are associated with this format?
  • How much knowledge do I have of the software that I’ll be using?

If the answer still isn’t obvious, below are some pros and cons of the possible formats.





Easy for hiring managers and UX professionals to find and view organically

Many options for setup that don’t require coding knowledge

More difficult to tailor your portfolio to different job types

May force you to adapt your info to a predefined template

PDF / slide deck

Allows you to have multiple unique, job-tailored portfolios 

Harder to access (e.g., may have to be explicitly shared with the hiring manager)

Physical artifacts

Can be brought along to interviews to help you talk through your process

Very limited access, hard to share with hiring managers

Step 4: Create Your Portfolio

Now that you have a plan for your projects and format, you can start putting everything together. Regardless of which format you’ve chosen, create a basic template that you’ll follow so that all of your projects look cohesive.

Example templates for the 5 basic components of a UX-design portfolio: title page, project introduction, images with captions, and results
Example templates for the 5 basic components of a UX-design portfolio: title page, project introduction, images with captions, and results

Step 5: Get Feedback and Iterate

Once your portfolio is created, send it to others to provide feedback. Another set of eyes on your portfolio will catch spelling or grammar errors, confusion about content, and the overall usability of your format.

As you interview with hiring managers, make note of what resonates with them and what is unclear. Then iterate on your portfolio.

As you work on new projects going forward, save any artifacts or process documents to use as future case studies in your portfolio. Your portfolio will always be a work in progress and having an efficient method for keeping track of projects will make updates simple.

Navigating Roadblocks

“My work is under a nondisclosure agreement (NDA).”

Nondisclosure agreements are common when doing UX work — and even more so if you’re doing government work. These contracts prohibit you from displaying identifying information about the company, its users, or the project details. Restrictions can be frustrating when you’re crafting your portfolio, but there are ways to show your work without violating the NDA.

Show process images. Rather than showing the polished UI and visual designs that display company-specific information, showcase your process for these projects. Highlight communication skills like workshop facilitation or early design concepts through sketches or black-and-white wireframes.

Workshop whiteboard
Workshop photos give potential employers insight into your design process.

Redact or blur out information. If you have wireframes or prototypes that you’d like to show, blur out identifying information. (Blurring is especially important for applications displaying financial or medical information.)

Redacted information
Redacting personal information like names and contact information protects privacy of individuals while still allowing you to show your designs.

Make it generic. Recreate your designs using different styles. While this approach is time-consuming, it ensures that you are not using brand colors or styles that would identify the client.

Generic design elements
Remove company logos, change branding, and update copy in order to show the layout and design without compromising company information.

“I don’t have a lot of time. What should I focus on?”

If you’re short on time but still want to make a big impact, focus on a project or two where you had to incorporate a wide variety of UX and design skills. These case studies will be in depth and will show off your versatility.

“I had great ideas/designs, but they were never implemented.”

As designers we often generate many candidate solutions for a single problem and ultimately choose only one. In this process, many great ideas are left on the cutting-room floor. As you’re writing your case studies, include candidate solutions and explain the thought process behind the designs. Hiring managers want to know that you’ll be able to navigate challenges and constraints, and you can show them that your work lives in reality instead of an ideal world.

“I’m a student.”

The first job is the hardest to get. It’s difficult to present a compelling design portfolio if all you can show for yourself is student projects. We strongly recommend having an internship in a company, so that you have at least one real-world project to show.

A key aspect of any design is the ability to deal with constraints. Student projects often have made-up constraints — including made-up users and personas — which make them uncompelling proof of your ability to design for the real world. If you’re still working on your student projects, select problems that have business relevance (i.e., will make money) and realistic constraints. If you’re already done, at least acknowledge any unrealistic elements of your student projects in your portfolio description, so that managers don’t conclude that you don’t know any better.


Portfolios will always be a part of a designer’s process. Creating a portfolio that showcases your strengths in a way that appeals to your audience will help you land your next UX-design job. The act of creating a portfolio allows you to identify your skills and achievements while reflecting on the work you want to do in the future.

When creating your UX design portfolio, remember these tips:

  • Curate, curate, curate
  • Show real work, even if it’s messy
  • Highlight collaboration with teams
  • Reflect on who you are as a designer and where you want to be


Last week I posted my new logo animation on twitter.

Amongst everyone saying a ton of lovely things, (thankyou) there was a resounding cry of “tutorial”. So I’m going to try and break it down for you. Hope this helps someone, I had a ton of fun making it!

There’s a few things going on in this logo animation. We’ve got –

I won’t dive too much into Greensock for this article. But as Sara Soueidan has said

Greensock is the best thing that happened to SVG animations since SVG animations.

Greensock provides better cross browser support for SVG animation than we get with CSS. It also, crucially, gives you the ability to chain animations and group animations on timelines. This is invaluable for longer and more complex animation.

They also have a bunch of fun plugins, some of which have been made specifically for SVG, like Draw SVG and Morph SVG.

I’ve been side-eying their custom bounce plugin for a while, so when I saw an chance to use it to give the little dot some character I jumped (bounced? 😬) at the chance.

Although I love Greensock, you don’t need to learn a whole Javascript animation library to do SVG path animations.

We can do them with CSS too. So I’ll run through a couple of different ways to create the same effect.

Let’s get going. First up…

SVG stroke-dasharray permalink

stroke-dasharray is a SVG presentation attribute (which we can use as a CSS property) to make our SVG paths dashed instead of solid. The higher the number is, the the bigger the gap between dashes.

<path stroke-dasharray="10" ... />
.dashedPath {

stroke-dasharray: 10;


You can play around with what these values look like in this pen.

See the Pen SVG stroke dasharray demo by Cassie Evans (@cassie-codes) on CodePen.

As well as making the dashes different lengths with stroke-dasharray, we can also offset the stroke position with stroke-dashoffset. If we change this property it looks like our dashes are moving along the path.

Like so…

See the Pen SVG stroke dashoffset demo by Cassie Evans (@cassie-codes) on CodePen.

If we make the gap between the dashes big enough and then change the offset we can create a path “drawing” effect.

See the Pen SVG stroke dashoffset demo – animating by Cassie Evans (@cassie-codes) on CodePen.

Up until now we’ve been changing the value using a range input, but dashoffset and dasharray are animatable properties, so we can animate them with CSS like so –

See the Pen SVG stroke dashoffset demo – animated with CSS by Cassie Evans (@cassie-codes) on CodePen.

We can also use Greensock’s draw svg plugin to animate the stroke.

See the Pen SVG stroke dashoffset demo – animated with GSAP by Cassie Evans (@cassie-codes) on CodePen.

Under the hood, this is how my logo animation works, but rather than having one continuous line I’ve broken the path up into nine separate sections. This gives me more control over the timing and helps to avoid any clipping overlaps, which we’ll get to in a minute.

See the Pen Cassie! – without clip paths – break down by Cassie Evans (@cassie-codes) on CodePen.

Chaining animations in CSS is a bit of a nightmare as we have to do it with animation-delay. With Greensock, you can line these animations (or tweens) up on a timeline and easily tweak the timings of each tween in relation to the others.

You may have noticed that this version of my logo looks a little… messy though? SVG paths are a consistant width the whole way along. We can change the overall stroke-width and the shape of the stroke-linecap but we can’t do much more than that.

Enter , we can use clip path to “cut out” a more stylised shape.

SVG permalink

SVG clip path can be used to clip (or hide) parts of SVG elements according to a certain path. The parts of the shape inside the are visible, and the parts outside are hidden.

This is a great CSS tricks article if you want to know more.

Lets go back to our little line animation.

In illustrator I drew out the path that we animated (purple), and then I drew a shape over the top (black). This will be used as a clip path.

This is what the syntax for a clip path looks like in SVG


<clipPath id="myClipPath">

<circle cx="40" cy="35" r="35" />


<path clip-path="url(#myClipPath)"... />


Anything you put inside the clip path element will be used as a clipping object. You reference a clip path on the clipping target using an ID

You can also reference a clip path in CSS like this:

.element {

clip-path: url("#myClipPath");


This is what the line animation looks like with a clip path applied. Much Nicer!

See the Pen SVG stroke dashoffset demo – clip-path by Cassie Evans (@cassie-codes) on CodePen.

For my logo animation I created a clip path for each stroke.

So the end result looks like this!

See the Pen Cassie! – with clip paths – break down by Cassie Evans (@cassie-codes) on CodePen.

The duotone effect permalink

I’ve created the duotone effect by animating two paths instead of one along each section, one purple and one green.

These paths are grouped together in a element which is the target for the clipping area.

See the Pen SVG stroke dashoffset demo – Stagger – animated with GSAP by Cassie Evans (@cassie-codes) on CodePen.

Squash and stretch. permalink

The final cherry on top is the little dot on the i. 💚

In order to make a realistic bounce, the element needs to abide by the squash and stretch animation principle.

This helps make the movement feel more lifelike. The i should squash and stick to the ground at the bottom of the bounce and stretch out at the top. You can definitely achieve this with some really fine tuned keyframes or individual, overlapping tweens. But Greensock make it easier for us with their Custom Bounce plugin.

Using the plugin you set a few parameters and it creates an ease for the bounce and for the squash and stretch.

strength determines how bouncy the ease is and squash determines how long the squash should last.

CustomBounce.create("myBounce", {strength:0.7, squash:3, squashID:"myBounce-squash"});

You can then use that bounce ease the same way you would use a normal ease in a Greensock tween, by referring to it in your tween parameters. The squash ease ID will be whatever the ID of the bounce is plus -squash appended to the end, for example, ease:"myBounce-squash""#ball", duration, {y:550, ease:"myBounce"})

.to("#ball", duration, {scaleY:0.5, scaleX:1.3, ease:"myBounce-squash"}, 0)

See the Pen Video: CustomBounce from GreenSock by Cassie Evans (@cassie-codes) on CodePen.

The final animation permalink

Put all of that together along with a bit of timing finetuning and easing tweaks and we have our final logo animation!

See the Pen Cassie! by Cassie Evans (@cassie-codes) on CodePen.

If you make an SVG path animation I would love to see it! My twitter DM’s are also always open if you get stuck.

Just pop me a message!