An Introduction to mo.js

mo.js is a JavaScript library devoted to motion for the web. It offers a declarative syntax motion and the creation of elements for animation. Even though mo.js is still in beta, there is already a host of amazing features to play with. Its author, Oleg Solomoka (otherwise known as @legomushroom) creates incredibly impressive demos and tutorials for the library's offerings that you should check out, but in this article we’ll run through a really quick overview of features and tutorials to get you started.

Base premises

mo.js basically offers two ways to make something that moves. You can do what other libraries do and reach inside the DOM or SVG DOM and move it, or you can create a special mo.js object, which has some really unique offerings. There are fundamental things available to both ways of working, such as custom path easing or timelines. The path easing and timelines also have pretty impressive working tools to make them easier to adjust while you’re working.

Shape

Depending on what you’re animating, the Shapes and other objects that mo.js allows you to make might simplify your workflow. mo.js offers a declarative syntax that makes it very easy to create something on the fly.

Here is a really basic example:

See the Pen mo.js shape 1 by Sarah Drasner (@sdras) on CodePen.

Here are some of the base things you need to know:

  • The shapes that are available to you include: circle, rect, cross, equal, zigzag, and polygon. (Default is a circle)
  • You define a fill, a stroke, and a stroke-width (Default is fill with no stroke or stroke-width. Equal and cross don’t have space to fill, so they will not appear unless a stroke is specified.)
  • You define a radius for the shape, and adjust it on an axis with an additional radiusX or radiusY. (Default is 50)
  • You let it know if you want to show the shape isShowStart, this is a boolean- true or false. This allows you to see it if you’re not going to animate the shape. (Default is false)
  • Polygons, zigzag and equal allow you to pick a number of points so that you can create different types of shape (Default is three)
  • All shapes will be placed into the middle of the screen using absolute positioning, unless you specify top, left etc.

Here is an example of all the shapes:

See the Pen mo.js shape 2 by Sarah Drasner (@sdras) on CodePen.

You might notice if you look into the DOM that these are SVG shapes placed inside of a div for positioning. You can also pass a parent, like parent: '#id-to-be-placed-under' if you’d like to put the shape somewhere within the DOM. You can also pass any DOM node as a parent, so `parent: someEl` would work as well. At some point, you’ll also be able to choose between using a div or SVG, which will be awesome, because it makes it much easier to create a scaling animation for mobile if you can place it with an SVG viewBox.

We can also create custom shapes to animate as well, and add them in as the shape object.

//custom shape
class OneNote extends mojs.CustomShape {
  getShape () { return '<path d="M18.709
	...
"/>'; }
}
mojs.addShape( 'oneNote', OneNote ); 

const note1 = new mojs.ShapeSwirl({
  shape:    'oneNote',
  ...
});

Shape Motion

To create an animation with a shape in mo.js, we’d pass in an object, with the key and value expressing what we’d like to tween from and to. We can use transform properties like scale, and angle (known in CSS as rotate), opacity, and we can interpolate colors as well, as shown here with fill.

scale:        { 0 : 1.5 },
angle:        { 0 : 180 },
fill:         { '#721e5f' : '#a5efce' },

See the Pen mo.js shape 3 by Sarah Drasner (@sdras) on CodePen.

We can also specify a few other parameters:

  • duration
  • delay
  • repeat
  • speed- 1 is the default speed, so 0.5 would be half speed and 1.5 would be 1.5 faster
  • isYoyo- whether or not it tweens back and forth
  • easing- written as an object like ease.in, ease.out, or ease.inout
  • backwardEasing - a configuration if you want the the backswing of the yoyo to be different. This only works if you're using isYoyo: true. If you want the way that it eases back to be different than the way that it eases forward, you would specify that with this method. It defaults to easing if not specified
  • isSoftHide- whether it hides the Shape with transforms rather than display (boolean that defaults to true)

Random

We can also pass in random values pretty easily, by writing this string- property : 'rand(min, max)', for instance, angle: ‘rand(0, 360)’

Chaining

If we’d like to chain two animations on a Shape, we can call .then() on the initial tween like so:

const polygon = new mojs.Shape({
  shape:        'polygon',
  points:       5,
  stroke:       '#A8CABA',
  scale:        { 0 : 1.5 },
  angle:        { 0 : 180 },
  fill:         { '#721e5f' : '#a5efce' },
  radius:       25,
  duration:     1200,
  easing:       'sin.out'
}).then ({
  stroke:       '#000',
  angle:        [-360],
  scale:        0,
  easing:       'sin.in'
});

See the Pen mo.js shape 4 by Sarah Drasner (@sdras) on CodePen.

Swirl

Things like swirl and blast are pretty interesting parts of mo.js, they’re pretty beautiful out of the box. Swirl is very similar to a regular shape object, but the movement is pretty much how it sounds- the shape swirls around. You have a few parameters to work with for a swirl, and they are all based on the sine that the swirl works with.

  • swirlSize- this is the deviation- so the amount it swirls horizontally
  • swirlFrequency- Frequency of sine
  • pathScale- sine length scales
  • degreeShift- this can shift the direction of sine, if you’d like to make it move towards a different direction on a 360 degree, especially useful for using swirl with a burst
  • direction- direction of the sine, either -1 or 1 (good for setting something in the other direction if you want it to look a little random)
  • isSwirl: If shape should follow sinusoidal path (boolean- true or false)

These can be a little confusing to read and grok, so I’ve made a demo so you can play with the values to understand them a little better:

See the Pen Mo.js ShapeSwirl Options by Sarah Drasner (@sdras) on CodePen.

Also, you can use a few base configurations like a custom shape or an object with some configurations to reuse for a few different SwirlShape options (or any other shape) with an ES6 spread operator like this, which is really nice if you have a few similar objects:

const note_opts_two = {
  shape:    'twoNote',
  scale:    { 5 : 20 },
  y:        { 20: -10 },
  duration:  3000,
  easing: 'sin.out'
};

const note1 = new mojs.ShapeSwirl({
  ...note_opts_two,
  fill:     { 'cyan' : color2 },
  swirlSize:      15, 
  swirlFrequency: 20
}).then({
  opacity:  0,
  duration: 200,
  easing: 'sin.in'
});

See the Pen Little Bouncy Radio by Sarah Drasner (@sdras) on CodePen.

Burst

A burst is also really quite lovely out of the box. If you use the default configuration, you would say this:

const burst = new mojs.Burst().play();

And it would return this (hit the Rerun button):

See the Pen Simplest Mo.js Burst by Sarah Drasner (@sdras) on CodePen.

To configure a burst, you have a few options:

  • count- the number of children in the burst (default is 5)
  • degree- the number of degrees around the center that the children come from
  • Radius- the radius that the children spread out to (radiusX and radiusY applies here as well)
  • isSoftHide- whether it hides the children with transforms rather than display (boolean that defaults to true)- this applies to all shapes, but I bring it up again because it's particularly useful for a burst with several children.

All of the same rules for shape also apply to burst, and we can apply them to the nodes themselves as a separate object using children, like this:

const burst = new mojs.Burst({ 
  radius: { 0: 100 },
  count: 12,
  children: {
    shape: 'polygon',
    ...
  }
});

See the Pen Playing with mo.js burst by Sarah Drasner (@sdras) on CodePen.

Timeline

In a timeline, you can either add a bunch of objects or tweens that you have previously declared as a variable, or you can append them, and have them fall in order.

const timeline = new mojs.Timeline({
  .add( tween ) {}
  .append( tween ) {}
});
  • Add- allows you to add any of the objects or shapes to the above- they’ll all fire at once (but you can still use delays or stagger to adjust their timing)
  • Append- adds objects but staggers them in the order they are added

There are a few things you can do in a timeline that are worth noting: you can add repeats, delays, and speed, just like the objects themselves, as an object parameter like this:

new mojs.Timeline({
  repeat: 3,
  isYoyo: true
});

You can also nest a timeline inside another timeline, you can even nest them infinitely:

const subTimeline = new mojs.Timeline();

const master = new mojs.Timeline()
.add( subTimeline );

Tween

With all of these constructors, we haven’t even really spoken too much about tweening (animating) what already exists. All of the same repeat, durations, and easing are also available for tweens. To use a tween, we'd update styles or attributes (whatever you're trying to change) along a progress, here's a simple example:

var thingtoselect = document.querySelector('#thingtoselect');
new mojs.Tween({
  duration:    2000,
  onUpdate: function (progress) {
    square.style.transform = 'translateY(' + 200*progress + 'px)';
  }
}).play();

In the pen below, I used this kind of tween to create the effect on the far left side of the little zigzags drawing themselves on repeatedly (using the SVG line drawing trick with stroke-dashoffset). I'm also using the path easing available, which is discussed in the next section. The water in the tanks appears to move by updating SVG path attributes as well.

new mojs.Tween({
  repeat:   999,
  duration: 2000,
  isYoyo: true,
  isShowEnd: false,
  onUpdate: function (progress) {
    var laser1EProgress = laser1E(progress);
    for (var i = 0; i < allSideL.length; i++) {
      allSideL[i].style.strokeDashoffset = 20*laser1EProgress + '%';
      allSideL[i].style.opacity = Math.abs(0.8*laser1EProgress);
    }
  }
}).play();

See the Pen Raygun with Mo.js by Sarah Drasner (@sdras) on CodePen.

Tweens have pretty rich callbacks available that take into account fine-tuning that can sometimes make all the difference. Some examples of this are onStart vs. onRepeatStart, onComplete vs onRepeatComplete, and onPlaybackStart vs onPlaybackPause. A full list is available in the docs here.

Path Easing

A very cool feature of mo.js is that aside from the other built-in easing values, you can also pass in an SVG path as an easing value. I use this feature in almost every other demo in this post, but to be honest, I could never do path easing justice like the gorgeous tutorial Legomushroom has prepared on this page. I’ll simply explain the base premises to get you started and show you how it works, but really I highly recommend going through his post.

Before we go through all of path easing, it's important to establish that if you'd like to work with something out of the box, the base functions will get you very far. The syntax for built-in easing is written like so:

easing: 'cubic.in'

Mastering easing really can be the key ingredient to bringing your animations to life, so being able to fine tune your motion with custom paths is helpful. If you're comfortable animating in CSS, you might like the Mo.js bezier easing, which accepts the same curve values (without some of the same restrictions, as CSS's cubic bezier. Here's an example of this kind of easing:

easing: 'bezier( 0.910, 0.000, 0.110, 1.005 )'

If you'd like more refined control than what bezier easing allows, path easing is really nice. You pass in an SVG path, and your shape is updated as work with it. Let's look back again at the example I pulled out earlier from the raygun. We used a path easing to interpolate the values as it updated:

const laser1E = mojs.easing.path('M0,400S58,111.1,80.5,175.1s43,286.4,63,110.4,46.3-214.8,70.8-71.8S264.5,369,285,225.5s16.6-209.7,35.1-118.2S349.5,258.5,357,210,400,0,400,0');

new mojs.Tween({
  repeat:   999,
  duration: 2000,
  isYoyo: true,
  isShowEnd: false,
  onUpdate: function (progress) {
    var laser1EProgress = laser1E(progress);
    for (var i = 0; i < allSideL.length; i++) {
      allSideL[i].style.strokeDashoffset = 20*laser1EProgress + '%';
      allSideL[i].style.opacity = Math.abs(0.8*laser1EProgress);
    }
  }
}).play();

To really get a sense of how a path ease can affect the movement and behavior of an animation, check out the example CodePen demo below in the tools section. The curve editor tool that Mo.js offers really helps to visualize and immediately get a sense of how an ease can refine what you create.

Mo.js Tools

One of the most impressive things about mo.js is the tooling. To get your palette wet, check out this vimeo:

I made a really quick pen to show both the player tool and the curve editor so that you can play around with them. Feel free to either fork it or just adjust it live in CodePen, it's fun to try out. The curve tool is on the left side and the timeline is tucked at the bottom with a little arrow.

See the Pen mo.js shape 5- curve editor by Sarah Drasner (@sdras) on CodePen.

Here’s the coolest part: legomushroom isn’t done. He’s working on new tooling for the timeline now. Check out the awesome design for this tool here, and check out the broader label ‘help wanted’. If you’re interested in contributing to open source, here’s an opportunity to dig in and help make a really useful tool!

The other finished tools are available in their own repos:

There is also a Slack channel you can join if you’re interested in contributing or learning.

If you’re the kind of person who likes playing with things more than reading, all of the CodePen demos from this post are available in this Collection.

There are, of course, things in the library that I didn’t cover in this article. I went over some of the most useful features of mo.js in my opinion, but there are more things to discover. Check out the docs for more information. Special thanks to legomushroom for proofreading this article prior to release.