DEV Community

Cover image for Throwing around text - Kinetic typography part 3: The Walking Text that follows you 🧟🧟
Pascal Thormeier
Pascal Thormeier

Posted on

Throwing around text - Kinetic typography part 3: The Walking Text that follows you 🧟🧟

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 and part 2!
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:

<!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>
Enter fullscreen mode Exit fullscreen mode

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:

<svg>
  <path 
    stroke="black" 
    stroke-width="2" 
    fill="none" 
    d="M 0 0 0 9999" 
    id="drawnpath"
  ></path>
</svg>
Enter fullscreen mode Exit fullscreen mode

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:

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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.
}
Enter fullscreen mode Exit fullscreen mode

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:

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()
    }
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

Let's see how this works:

Acursor drawing a path on a white SVG.

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

<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>
Enter fullscreen mode Exit fullscreen mode

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:

const path = new Path(
  document.querySelector('text').textLength.baseVal.value
)
Enter fullscreen mode Exit fullscreen mode

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

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 β˜• or follow me on Twitter 🐦! You can also support me directly via Paypal!

Buy me a coffee button

Top comments (3)

Collapse
 
grahamthedev profile image
GrahamTheDev

OK so that is really fun!

Collapse
 
thormeier profile image
Pascal Thormeier

Thank you! I must admit, I played with that thing for a good 30 minutes, giggling like a kid :D

Collapse
 
tr11 profile image
Tiago Rangel

Oh no walking text πŸ˜‚: dev-to-uploads.s3.amazonaws.com/up...