Throwing around text - Kinetic typography part 2: Tricking gravity thanks to matter.js! 📃🛫🤯
Tags: webdev, design, javascript, tutorial
Published: 2022-01-30 17:22:56

> Part 2 of my series about kinetic typography! Let's move some text around with HTML, CSS and JS! If you missed how I came about throwing around text and deforming it with only web stuff, be sure [to check out part 1](https://dev.to/thormeier/lets-build-some-kinetic-typography-part-1-a-chilly-warm-up-36g8)!

> 🎉🎉 **I also want to celebrate 2500 followers! Thank you for your support everyone! It means so much to me and keeps me inspired to write even more!** 🎉🎉

Ok, so, this time we're using JS, so much I can tell. Part 1 was CSS only, but believe me, once we get the mathematical options in that JS offers us, we can go _wild_. Let's get going!

## Text falling... up?

I consulted A Popular Search Engine™ again to find some good examples. Some that are a little more complex than the one we already did. In the [Creatopy blog](https://www.creatopy.com/blog/kinetic-typography/) I found this little Gif by [Siddhart Mate](https://www.behance.net/syddharth):

![The word "rise" falls to the top, the word "fall" falls to the bottom of the image, the animation resets and they fall again to oposite sides. The letters behave like rigid bodies.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2v3ur4seynycmc4gev1f.gif)

Now, what I like about this animation is the way the letters behave. It's not necessarily the falling itself, but the way they fall into each other once they reach the top/bottom of the image. It detaches the letters from the word, essentially making them independant from the meaning they represent when they are in the correct order and position. It has some very artistic touch to it.

And that's exactly the reason why we're rebuilding this today.

## Rigid bodies obeying physics

So, if half the image defies gravity, how would one make text obey the laws of physics in the first place? Physics engines are nothing new (see a ton of computer games) and there sure as heck is one for JS, namely [matter.js](https://brm.io/matter-js/). Let's download that first and do some boilerplating:

```bash
npm i matter-js pathseg poly-decomp
```

```html
<!DOCTYPE html>
<html>
<head>
</head>
<body>

  <div id="left"></div>
  <div id="right"></div>
  
  <script src="./node_modules/matter-js/build/matter.js"></script>
  <script src="./node_modules/pathseg/pathseg.js"></script>
  <script src="example2.js"></script>

</body>
</html>
```

I also installed pathseg and poly-decomp to allow for concave shapes (such as the letter `U`) and to be able to convert SVG paths to coordinates for bodies.

Now, let's get going right away with matter.js.

Matter.js offers a _lot_ of things. First we deconstruct the `Matter` object first for simpler access and to see what we'll actually need:

```js
const {
  Engine,
  Render,
  Runner,
  Composite,
  Bodies,
  Body,
  Svg,
  Vertices,
  World
} = window.Matter
```

Then, instead of creating all the letters from scratch, let's use an existing font instead and convert that to SVG. I had to actually retrace the letters, but I'm sure there's some sophisticated tool out there that can do exactly that. Apparently matter.js doesn't really like hollow bodies when converting SVG paths to vertices.

```js
const A = 'M 18 114 46 114 70 37 81 74 57 74 51 94 87 94 93 114 121 114 81 7 57 7 z'
const U = 'M 16 7 16 82 C 17 125 101 125 99 82 L 99 82 99 7 74 7 74 82 C 73 100 41 99 41 82 L 41 82 41 7 16 7 z'
const W = 'M 6 7 29 114 56 114 70 53 84 114 111 114 134 7 108 7 96 74 81 7 59 7 44 74 32 7 6 7 z'
const N = 'M 16 114 16 7 42 7 80 74 80 7 106 7 106 114 80 114 42 48 42 114 16 114 z'
const P = 'M 20 8 20 114 46 114 46 28 66 28 C 83 28 83 59 66 58 L 66 58 46 58 46 78 67 78 C 116 78 116 7 65 8 L 65 8 z'
const D = 'M 19 7 57 7 C 120 13 120 109 57 114 L 57 114 45 114 45 94 56 94 C 85 93 86 30 56 27 L 56 27 45 27 45 114 19 114 19 7 z'
const O = 'M 13 59 C 9 -12 109 -12 107 59 L 107 59 80 59 C 84 14 34 14 39 59 L 39 59 C 33 107 86 107 80 59 L 80 59 107 59 C 109 133 9 133 13 59 L 13 59 z'
const R = 'M 21 114 21 7 64 7 C 122 8 105 67 85 69 L 85 69 107 113 80 113 61 76 47 76 47 56 65 56 C 84 57 84 26 65 27 L 65 27 47 27 47 114 z'
```

Ok, given, they still look a bit pants when rendered, but I'm sure there's some way to make them render correctly...

**Challenge:** Can anyone figure out what font I used? Hint: It's a Google Font.

To convert these paths to actual bodies, we create a function to transform paths to vertices and then vertices to bodies:

```js
const toVertices = path => {
  const pathEl = document.createElementNS(
    'http://www.w3.org/2000/svg',
    'path'
  )
  pathEl.setAttribute('d', path)
  return Svg.pathToVertices(pathEl, 1)
}

const toBody = function (letter) {
  const vertices = toVertices(letter)

  return Bodies.fromVertices(0, 0, vertices, {
    render: {
      fillStyle: '#fff',
      strokeStyle: '#fff',
      lineWidth: 1,
    }
  })
}
```

Those functions can then be used to create the words as arrays of bodies:

```js
const bodiesUpward = [
  toBody(U),
  toBody(P),
  toBody(W),
  toBody(A),
  toBody(R),
  toBody(D),
]

const bodiesDownward = [
  toBody(D),
  toBody(O),
  toBody(W),
  toBody(N),
  toBody(W),
  toBody(A),
  toBody(R),
  toBody(D),
]
```

Awesome. Now we need to create two worlds: One for the left side and one for the right side.

```js
// Create the engines
const leftEngine = Engine.create()
const rightEngine = Engine.create()

// Get both worlds
const leftWorld = leftEngine.world
const rightWorld = rightEngine.world

// Create the render instances with the same options
const options = {
  wireframes: false,
  width: 400,
  height: 600,
  background: '#000'
}

const leftRender = Render.create({
  element: document.querySelector('#left'),
  engine: leftEngine,
  options
})
const rightRender = Render.create({
  element: document.querySelector('#right'),
  engine: leftEngine,
  options
})

Render.run(leftRender)
const leftRunner = Runner.create()
Runner.run(leftRunner, leftEngine)

Render.run(rightRender)
const rightRunner = Runner.create()
Runner.run(rightRunner, rightEngine)
```

These are now two different worlds we can render stuff on. A world doesn't have any boundaries by default, so we need to add a floor and a ceiling to the left and the right world respectively. We also adjust the gravity to make things "fall" up and down:

```js
// Stuff falls down
leftEngine.gravity.y = 1

// Stuff goes up
rightEngine.gravity.y = -1

// The floor and ceiling are rectangles
World.add(leftWorld, Bodies.rectangle(0, -1, 800, 1, { isStatic: true }))
World.add(rightWorld, Bodies.rectangle(0, 601, 800, 1, { isStatic: true }))
```

Then we add all the letters to their respective worlds:

```js
bodiesUpward.forEach(body =>{
  World.add(leftWorld, body)
})

bodiesDownward.forEach(body =>{
  World.add(rightWorld, body)
})
```

Now comes the fun part: Positioning the letters, rotating them and letting them fall. Since we want this to happen over and over, we introduce an interval that repositions all of the letters and lets them fall again:

```js
const positionLeftBodies = () =>{
  let leftY = 0
  bodiesUpward.forEach(body => {
    Body.setPosition(body, {
      x: 200,
      y: leftY,
    })
    Body.setAngle(body, -Math.PI / 2) // 90deg in Radians

    // Important to not have any "left-over" movement.
    Body.setVelocity(body, { x: 0, y: 0 })

    leftY -= 100
    console.log(leftY)
  })
}

const positionRightBodies = () => {
  let rightY = 600
  bodiesDownward.forEach(body => {
    Body.setPosition(body, {
      x: 200,
      y: rightY,
    })
    Body.setAngle(body, -Math.PI / 2) // 90deg in Radians

    // Important to not have any "left-over" movement.
    Body.setVelocity(body, { x: 0, y: 0 })

    rightY += 120
  })
}

positionLeftBodies()
positionRightBodies()

setInterval(() => {
  positionLeftBodies()
  positionRightBodies()
}, 6000)
```

And this is what it looks like in action:

{% codepen https://codepen.io/thormeier/pen/abVOpPY %}

And that's it for the second part of the series!

---

*I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a* ❤️ *or a* 🦄*! Also, consider following me if you want to see how the other examples turn out! I write tech articles in my free time and like to drink coffee every once in a while.*

*If you want to support my efforts,* [*you can offer me a coffee* ☕](http://buymeacoffee.com/pthormeier) *or* [*follow me on Twitter* 🐦](https://twitter.com/pthormeier)*!* *You can also support me directly via [Paypal](https://www.paypal.me/pthormeier)!*

[![Buy me a coffee button](https://dev-to-uploads.s3.amazonaws.com/i/x55hp6jopwyy161d8e2u.png)](http://buymeacoffee.com/pthormeier)