Throwing around text - Kinetic typography part 3: The Walking Text that follows you 🧟🧟
Tags: webdev, javascript, tutorial, design
Published: 2022-05-02 20:33:14

> Part 3 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) and [part 2](https://dev.to/thormeier/throwing-around-text-kinetic-typography-part-2-it-defies-gravity-itself-thanks-to-matterjs-239e)!
It's been a while since we've thrown around some text. This tutorial will also use some JS, but not too much. Today we'll make text follow the user's cursor!

## What we'll need and what it'll look like

For this example of kinetic typography, we'll need an SVG. This is because SVG can animate text really well. So we're going to build a path from the user's cursor movement that won't exceed a certain length, and we'll then animate the text along that path.

Unfortunately, smooth animations will be too much effort to implement in this tutorial, but the clunkiness of the cursor movement has its own charm. However, we can smooth things out a little bit. More on that later. First, it's time for some...

## Boilerplating!

The ol' reliable:

```html
<!DOCTYPE html>
<html>
<head>
  <style>
    body, html {
      padding: 0;
      margin: 0;
      overflow: hidden;
    }
    svg {
        width: 100%;
        height: 100vh;
    }
    /* More styling goes here */
  </style>
</head>
<body>

<svg>
 <!-- Text goes here -->
</svg>

<script>
// Fun stuff goes here.
</script>
</body>
</html>
```

Awesome. We already added an empty script tag, an empty SVG and some default styling to make things easier. 

## Implementing The Walking Text™

Let's start out with parts of the SVG:

```html
<svg>
  <path 
    stroke="black" 
    stroke-width="2" 
    fill="none" 
    d="M 0 0 0 9999" 
    id="drawnpath"
  ></path>
</svg>
```

This is the path we're going to build. Each cursor movement will add another point to the path and expand it until it reaches a given length. Then we'll remove nodes from the other end until it is just under the given size again. We're using a stroke width of 2 to show where the path is.

Next, we'll need some JS. A path consists of several coordinates. To get the entire length of the path, it's helpful to calculate the distance between two coordinates. We'll create a class for that:

```js
class Coords {
  constructor(x, y) {
    this.x = x
    this.y = y
  }

  get coordString() {
    return this.x + ' ' + this.y
  }

  distanceTo(other) {
    return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2)
  }
}
```

In a second class called `Path`, we can then do the length calculation:

```js
class Path {
  constructor(pathLength) {
    this.path = []
    this.pathLength = pathLength
  }

  get totalPathLength() {
    let length = 0

    // Calculate the lengths between all points and add them up.
    for (let i = 0; i < this.path.length - 2; i++) {
      length += this.path[i].distanceTo(this.path[i + 1])
    }

    return length
  }

  get svgPath() {
    return `M ${this.path.map(c => c.coordString).join(' ')}`
  }

  // More fun stuff goes here.
}
```

Now comes the complex part: We need to add coordinates to the path. Since the `Path` class simply keeps an array of all coordinates, we can push the new coordinates to that array. However, we also want to smooth it out, so we need to skip any coordinates whose distance to the last ones is shorter than a given distance. We also want to keep the path at a constant length, so we need to remove any nodes from the beginning of the path until it reaches the desired length:

```js
class Path {
  // ...
  add(coord) {
    if (this.path.length > 0) {
      const dist = this.path[this.path.length - 1].distanceTo(coord)

      // Ignore all coords that are too close, they make stuff clunky.
      if (dist < 10) {
        return
      }
    }

    this.path.push(coord)

    // Cut the path to reach a desired length.
    while(this.totalPathLength > this.pathLength) {
      this.path.shift()
    }
  }
  // ...
}
```

So far, so good. Let's try this. Let's create a function that pushes the current coordinates to the path and adjusts our SVG to display the path. We call this function on `mousemove` and `touchmove` to support mobile devices, too.

```js
const path = new Path(300)

const adjustPath = e => {
  let x = 0
  let y = 0

  // Figure out x/y coordinates of both mouse and touch events
  if(e.type == 'touchmove'){
    const evt = (typeof e.originalEvent === 'undefined') ? e : e.originalEvent
    const touch = evt.touches[0] || evt.changedTouches[0]
    x = touch.pageX
    y = touch.pageY
  } else {
    x = e.clientX
    y = e.clientY
  }

  const coords = new Coords(x, y)
  path.add(coords)

  document.querySelector('path').setAttribute('d', path.svgPath)
}

window.addEventListener('mousemove', adjustPath)
window.addEventListener('touchmove', adjustPath)
```

Let's see how this works:

![Acursor drawing a path on a white SVG.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8idbzatyre1fgl9sg094.gif)

Amazing! Now we need to add the text. For that, we'll use an altered version of the SVG:

```html
<svg>
  <path stroke="none" fill="none" stroke-width="2" d="M 0 0 0 9000" id="drawnpath"></path>

  <text text-anchor="middle">
    <textPath
        class="my-text"
        font-size="80"
        href="#drawnpath"
        startOffset="50%"
    >
      THE WALKING TEXT
    </textPath>
  </text>
</svg>
```

We see a few things here: A `path` that goes from top left to wherever, a `text` and a `textPath`. `textPath` is an element that allows us to use a `path` element to draw the text on. Then, it will follow that path. People use bezier curves for smooth animations or boxes the text can go along, but we create the actual path ourselves with JS, so there's no need for an extra animation. Note how we also made the path invisible, so we only see the text.

The last thing we need to do now is to figure out how long the path should be. For this, we will use SVG's `textLength` and pass that as the length to the `Path` instance:

```js
const path = new Path(
  document.querySelector('text').textLength.baseVal.value
)
```

And we're done! We've now got some text following the cursor everywhere it goes. See it in action:

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

Stay tuned for the next shenanigans we can do with text!

---

*I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a* ❤️ *or a* 🦄*! 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)