Recreating Twitter’s `Like` effect with mo.js

Brendan McIlhenny
6 min readDec 21, 2017

--

Back when I started Flatiron in October, I never thought of myself as a backend warrior. Nowadays, I tend to gravitate towards the data-heavy server-side stuff rather than trudge through the muck and the nitty gritty mire that is the ‘frontend’. For this week’s blog post, I decided to challenge myself by trying to to learn a skill that 1) is front-end related and b) makes my heart sing like a sweet canary over the tree tops: animation.

Think Facebook’s ‘like’ button, the Twitter ‘like’ effect above. Even Medium has gotten in on the animated button click train with their ‘clap’ feature. You might take these micro interactions for granted when you’re scrolling through your news feed but they are sure as hell there. and make for quite the satisfying experience given the other option: a standard button click effect you get with <button></button> HTML elements.

Most of these micro interactions are powered by a nifty piece of Software known as mo.js, which is 100% open source and free and apparently brand new because the documentation section on its website is minimal at best. The only thing required to start programming in mo.js is your HTML file needs a link to the mo.js library, which I have linked to here. As the late Fela Kuti once said, “‘let’s start what we’ve come into the room to do.”

Step 1: Include the CDN in your HTML file as a Javascript script and create a Javascript file for your Mo.Js code

<script src=”https://cdnjs.cloudflare.com/ajax/libs/mo-js/0.288.2/mo.js"></script>
<script src=”scripts.js”></script>

Step 2: Add a button to your HTML page

I grabbed a prebuilt button from fontawesome.com, they’re basically the bootstraps for sweet buttons. Then I added some custom css to remove the border and outline of the button and change it’s position so it appears in the middle of the page.

<head>
<meta charset="utf-8">
<link href="https://use.fontawesome.com/releases/v5.0.1/css/all.css" rel="stylesheet">
<title></title>
<style media="screen">
</head>
<body> <button class =”gear-button” name=”button”><i class=”fas fa-cog”></i></button></body>

Step 3: Create a shape

I used Sketch to grab the SVG code
class Gear extends mojs.CustomShape {
getShape() {
return `<path d="M94.4141353,42.2928251 L84.7064014,40.656782 C84.0008437,37.7426872 82.9720923,34.9570937 81.6322875,32.3590037 L87.846904,24.5746781 C89.1686625,22.9295747 89.1086174,20.5625218 87.7151109,18.9747633 L83.8541761,14.5850587 C82.4546542,12.9989576 80.1337841,12.6635063 78.3634917,13.7933892 L70.0615138,19.0655871 C66.410026,16.4882695 62.2738023,14.5850587 57.8118693,13.4806992 L56.1876977,3.6210174 C55.8468951,1.53328628 54.0585563,0.00143635584 51.9592735,0.00143635584 L46.1409111,0.00143635584 C44.0458938,0.00143635584 42.2515396,1.53328628 41.9182836,3.6210174 L40.2852529,13.4837929 C36.5962506,14.3962292 33.1374759,15.8690768 29.981005,17.8088602 L22.0856716,12.1165749 C20.3840647,10.8884652 18.0468982,11.0804989 16.5619569,12.5774337 L12.4514356,16.7330075 C10.9696661,18.2331464 10.7795778,20.5942328 11.9984175,22.3132548 L17.6461598,30.3046409 C15.7440744,33.4693297 14.3011319,36.9436149 13.3980487,40.637225 L3.58269291,42.2929356 C1.51928404,42.6372261 0,44.4439781 0,46.5648566 L0,52.4381266 C0,54.5587841 1.51928404,56.3657571 3.58269291,56.7100476 L13.3980487,58.3657582 C14.1396991,61.40515 15.2163553,64.3084166 16.6473763,67.0003136 L10.4610871,74.7406637 C9.14381279,76.3842203 9.19915494,78.75282 10.5926614,80.3391421 L14.4506432,84.7289573 C15.8502744,86.3180416 18.1723477,86.6474159 19.9428588,85.5204057 L28.3644894,80.1759466 C31.9279329,82.6382429 35.959269,84.4479781 40.2852529,85.5204057 L41.9182836,95.3817448 C42.2516489,97.4695864 44.0458938,99.0014364 46.1409111,99.0014364 L51.9592735,99.0014364 C54.0585563,99.0014364 55.8468951,97.4696969 56.1876977,95.3818553 L57.8146036,85.5205162 C61.4575604,84.6203444 64.8724769,83.1731307 68.0008393,81.2666052 L76.2251631,87.2005348 C77.9237076,88.4330641 80.2626241,88.2397045 81.7460342,86.7380187 L85.8581961,82.5838812 C87.3382156,81.0882724 87.5386942,78.7285119 86.3095736,77.0063962 L80.4542435,68.7130376 C82.3617975,65.5377416 83.8123961,62.057269 84.7097919,58.3471957 L94.4174165,56.7097162 C96.4868408,56.3654256 98,54.5585631 98,52.4377951 L98,46.5645252 C97.9969376,44.4439781 96.4837784,42.6372261 94.4141353,42.2928251 Z M49.1538862,68.0640123 C39.0081181,68.0640123 30.7794193,59.7495499 30.7794193,49.5014916 C30.7794193,39.2518864 39.0081181,30.9389709 49.1538862,30.9389709 C59.2979043,30.9389709 67.5281342,39.2518864 67.5281342,49.5014916 C67.5281342,59.7495499 59.2979043,68.0640123 49.1538862,68.0640123 Z" id="settings_1_"></path>`
}
}

All of this mumbo jumbo is doing is adding a custom shape to my program. Since mo.js doesn’t have a ‘gear’ shape I am adding one. The path I am returning is the path I got from an .SVG image of a gear. and is basically writing my shape out for me in coordinates. With my shape added, let’s add a button that will trigger this shape to appear.

Step 4: add an event listener to that button

When clicked make that button disappear.

Bye
var elem = document.querySelector('.gear-button');
//define motion elements and timeline
elem.addEventListener('click', function(e) {
this.classList.add('hidden');

Step 5: add your snazzy effects

I wanted to bounce in the gear first, then add a ring and some cool dots that explode from the center of the gear to the animation, which I have defined below. The scale means what size it starts from and how it finishes. Now I don’t want everything to happen immediately so I am setting a delay on both the circle and the burst. I want the gear to bounce into the screen (that bounce effect is known as ‘easing’).

const gear = new mojs.Shape({
parent: elem,
shape: 'gear',
fill: '#c4341d',
scale: {0 : 1.0},
easing: 'elastic.out',
fill: {'yellow' : 'orange'},
duration: 1000,
delay: 300,
radius: 20
//isShowStart: true,
//that shape is going to be displayed on your page
//can change the size of the shape by the radius property and can also give it a fill, a border with stroke property and stroke width
})
const circle = new mojs.Shape({
parent: elem,
shape: 'circle',
stroke: '#c4341d',
strokeWidth: {10 : 0},
fill: 'none',
scale: {0 : 1},
radius: 40,
duration: 400,
easing: 'cubic.out',
delay: 300
})
const burst = new mojs.Burst({
parent: elem,
radius: { 4: 40},
angle: 45,
count: 14,
//delay: 500,
timeline: {delay: 400},
children: {
radius: 3,
fill: '#FD7932',
scale: {1 : 0, easing: 'quad.in'},
pathScale: [.8, null],
degreeShift: [300, null],
duration: [500, 700],
easing: 'quint.out'
}
})

Step 6: put your shapes on a timeline

Think of the timeline as a slingshot. You need to put your rocks and marbles in the slingshot before you can launch them at your neighbor’s house, otherwise you’d just be slinging air at your neighbor’s house and that’s no fun.

let timeline = new mojs.Timeline();timeline.add(gear, circle, burst)

Step 7: run it

Now think of .replay( ) here as you letting your finger go on the slingshot and launching some awesome debris into the sky.

elem.addEventListener('click', function(e) {
this.classList.add('hidden');
timeline.replay()
})

If you’ve reached this far in the code along you should have something that resembles this:

//my HTML file<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8">
<link href=”https://use.fontawesome.com/releases/v5.0.1/css/all.css" rel=”stylesheet”>
<title></title>
<style media=”screen”>
.gear-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 40px;
border: none;
outline:none;
background: transparent;
cursor: pointer;
color: #999;
transition: all, .2s ease-out;
}
.hidden {
font-size: 0;
border: none;
outline:none;
}
</style>
</head>
<body>
<button class =”gear-button” name=”button”><i class=”fas fa-cog”></i></button>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/mo-js/0.288.2/mo.js"></script>
<script src=”scripts.js”></script>
</body>
</html>

and here’s my Javascript file:

//my Javascript file var myBurst = new mojs.Burst({
count: 15,
radius: { 0 : 100},
angle: {0 : 100},
children: {
fill: [ 'red', 'blue', 'yellow', 'purple', 'green'],
duration: 2000,
radius: 10,
shape: 'circle',
delay: 'stagger(50)'
}
})
class Gear extends mojs.CustomShape {
getShape() {
return `<path d="M94.4141353,42.2928251 L84.7064014,40.656782 C84.0008437,37.7426872 82.9720923,34.9570937 81.6322875,32.3590037 L87.846904,24.5746781 C89.1686625,22.9295747 89.1086174,20.5625218 87.7151109,18.9747633 L83.8541761,14.5850587 C82.4546542,12.9989576 80.1337841,12.6635063 78.3634917,13.7933892 L70.0615138,19.0655871 C66.410026,16.4882695 62.2738023,14.5850587 57.8118693,13.4806992 L56.1876977,3.6210174 C55.8468951,1.53328628 54.0585563,0.00143635584 51.9592735,0.00143635584 L46.1409111,0.00143635584 C44.0458938,0.00143635584 42.2515396,1.53328628 41.9182836,3.6210174 L40.2852529,13.4837929 C36.5962506,14.3962292 33.1374759,15.8690768 29.981005,17.8088602 L22.0856716,12.1165749 C20.3840647,10.8884652 18.0468982,11.0804989 16.5619569,12.5774337 L12.4514356,16.7330075 C10.9696661,18.2331464 10.7795778,20.5942328 11.9984175,22.3132548 L17.6461598,30.3046409 C15.7440744,33.4693297 14.3011319,36.9436149 13.3980487,40.637225 L3.58269291,42.2929356 C1.51928404,42.6372261 0,44.4439781 0,46.5648566 L0,52.4381266 C0,54.5587841 1.51928404,56.3657571 3.58269291,56.7100476 L13.3980487,58.3657582 C14.1396991,61.40515 15.2163553,64.3084166 16.6473763,67.0003136 L10.4610871,74.7406637 C9.14381279,76.3842203 9.19915494,78.75282 10.5926614,80.3391421 L14.4506432,84.7289573 C15.8502744,86.3180416 18.1723477,86.6474159 19.9428588,85.5204057 L28.3644894,80.1759466 C31.9279329,82.6382429 35.959269,84.4479781 40.2852529,85.5204057 L41.9182836,95.3817448 C42.2516489,97.4695864 44.0458938,99.0014364 46.1409111,99.0014364 L51.9592735,99.0014364 C54.0585563,99.0014364 55.8468951,97.4696969 56.1876977,95.3818553 L57.8146036,85.5205162 C61.4575604,84.6203444 64.8724769,83.1731307 68.0008393,81.2666052 L76.2251631,87.2005348 C77.9237076,88.4330641 80.2626241,88.2397045 81.7460342,86.7380187 L85.8581961,82.5838812 C87.3382156,81.0882724 87.5386942,78.7285119 86.3095736,77.0063962 L80.4542435,68.7130376 C82.3617975,65.5377416 83.8123961,62.057269 84.7097919,58.3471957 L94.4174165,56.7097162 C96.4868408,56.3654256 98,54.5585631 98,52.4377951 L98,46.5645252 C97.9969376,44.4439781 96.4837784,42.6372261 94.4141353,42.2928251 Z M49.1538862,68.0640123 C39.0081181,68.0640123 30.7794193,59.7495499 30.7794193,49.5014916 C30.7794193,39.2518864 39.0081181,30.9389709 49.1538862,30.9389709 C59.2979043,30.9389709 67.5281342,39.2518864 67.5281342,49.5014916 C67.5281342,59.7495499 59.2979043,68.0640123 49.1538862,68.0640123 Z" id="settings_1_"></path>`
}
}
mojs.addShape('gear', Gear);var elem = document.querySelector('.gear-button');
//define motion elements and timeline
const gear = new mojs.Shape({
parent: elem,
shape: 'gear',
fill: '#c4341d',
scale: {0 : 1.0},
easing: 'elastic.out',
fill: {'yellow' : 'orange'},
duration: 1000,
delay: 300,
radius: 20
//isShowStart: true,
//that shape is going to be displayed on youyr page
//can change the size of the shape by the radius property and can alse give it a fill, a border with stroke property and dtrokewidrth [rp[erty
})
const circle = new mojs.Shape({
parent: elem,
shape: 'circle',
stroke: '#c4341d',
strokeWidth: {10 : 0},
fill: 'none',
scale: {0 : 1},
radius: 40,
duration: 400,
easing: 'cubic.out',
delay: 300
})
const burst = new mojs.Burst({
parent: elem,
radius: { 4: 40},
angle: 45,
count: 14,
//delay: 500,
timeline: {delay: 400},
children: {
radius: 3,
fill: '#FD7932',
scale: {1 : 0, easing: 'quad.in'},
pathScale: [.8, null],
degreeShift: [300, null],
duration: [500, 700],
easing: 'quint.out'
}
})
let timeline = new mojs.Timeline();//add event listener
elem.addEventListener('click', function(e) {
this.classList.add('hidden');
timeline.replay()
})

Mo JS documentation:

Github documentation: https://github.com/legomushroom/mojs/blob/master/api/readme.md

--

--

Brendan McIlhenny

Beijing儿, 冲浪er, Full Stack Developer, I’d rather be in New Orleans-er