Adam Kuhn has made several wonderful forms that do all kinds of wild things.

Here’s one that takes your email address, sets each letter into a separate seat on a roller coaster, and sends your email on a nice little ride. This one turns the letters into a gang of NES Marios and sends it down a green warp pipe. And here’s a form Adam made for the CSS-Tricks newsletter!

So when Adam said he wanted to make a form on Component Carousel, I was thrilled. The month’s theme was Bouncy Castle Architects so of course, he was going to create a form that turned into a bouncy castle on submit.

Let’s break down the main techniques Adam used.

The Form Trick

Adam’s using a bait and switch trick on all these forms. As expected, there’s a text input field that the user clicks and types their email into. However, the letters don’t actually end up inside the input. Adam is using the input only to catch the keyboard events, putting the letters we see in another element entirely.

The issue with the input element is that you can’t manipulate the characters easily. They don’t appear in the DOM the same way normal text does, so it’s much harder to animate.

Adam’s solution is to take each character and place it inside a span. Then he places all those spans inside a div. Now the letters are individual elements, not a value in a textbox, making them much easier to animate.

Let’s look at the the HTML for this part.

<form id="bounce" autocomplete="off">
  <input type="email" id="email" maxlength="25" required />
  <input type="submit" id="submit" value="SIGN UP" />
</form>

<div id="fakeform">
  <p id="wrapper">
    <span class="cursor"></span>
  </p>
</div>

As the user starts to type into the #email input, Adam has an event listener that deletes the content of the input and adds it instead to a div with the id of fakeForm.

function appendChar() {
  let formVal = email.value
  let result = ""
  let inputData = document.createElement("p")
  inputData.setAttribute("id", "fieldtext")
  inputData.innerHTML = formVal
  fakeForm.innerHTML = '<span class="cursor"></span>'
  let splitText = inputData.textContent.split("")
  splitText.forEach(function (char) {
    result +=
      char.trim() === ""
        ? ""
        : "<span><span data-char='" + char + "'>" + char + "</span></span>"
  })
  fakeForm.innerHTML += result
}

As I type my email, the DOM starts to populate the #wrapper with these span elements.

<div id="fakeform">
  <p id="wrapper">
    <span class="cursor"></span>
    <span>
      <span data-char="A">A</span>
    </span>
    <span>
      <span data-char="l">l</span>
    </span>
    <span>
      <span data-char="e">e</span>
    </span>
    <span>
      <span data-char="x">x</span>
    </span>
    <span>
      <span data-char="@">@</span>
    </span>
    <!-- ... -->
  </p>
</div>

In fact, Adam visually hides the input element altogether. This means he has to create his own flashing cursor with a span and some CSS. He explains it at in the video.

It should be said that Adam doesn’t recommend you drop this trick on your site without thinking through all the accessibility concerns this would present.

Sliding the Letters

Adam gets the letters to slide off and down by setting the spans on a path-offset. This lets you put objects on a path, then control where on that path they are (that’s the offset part). Here’s a quick demo.

The range slider is changes the path-offset value from 0% to 100%. As it changes, “Howdy!” slides along the path.

So when the time comes for your email to leave the bouncy castle, the path-offset changes and they slide across and down into the shark’s mouth. It’s so much smoother and easier than if you were using transforms to move the text.

SVG Transformations

To make the form transform into the bouncy castle, Adam hides some of the elements in plain sight. Here’s a slow-motion version that should make it clear where the castle parts are coming from. I’ve removed the text parts so just hit the toggle at the bottom to trigger the animation.

I recommend toggling back and forth rapidly to get a better sense of it.

Waving Animation

What seems like the blue border of the input box isn’t that at all. Remember, Adam’s hiding the input field entirely, so this is just made to look that way. It’s actually part of an SVG in front of everything else where much of the animation takes place.

Let’s take a look at the changes this blue box goes through. The stroke width increases and the color changes from blue to red. More dramatically, the shape changes as the box opens at the top and the paths wiggle out. Check out this isolated demo where I’ve added red circles at each vertex along the path.

The way the ends of the path wiggle back and forth before going straight is a wonderful detail. The way Adam achieves this effect with such little code is pretty surprising.

Let’s look at the CSS that gets applied to this shape to trigger the transformation.

#top {
  stroke: $red;
  d: path(
    "M 60 130 C 90 170 90 220 90 270 L 90 470 L 710 470 L 710 270 C 710 220 710 170 740 130  "
  );
  stroke-width: 100;
  animation: waving 0.35s ease-in-out 2.5 alternate forwards 0.3s;
  @keyframes waving {
    to {
      d: path(
        "M 120 120 C 90 170 90 220 90 270 L 90 470 L 710 470 L 710 270 C 710 220 710 170 680 120 "
      );
    }
  }
}

The d:path("M 60 ...") declaration changes the path’s shape to this:

And the path within the keyframe animation changes it to this:

So now we need to ask: At the end of the animation, how does the castle have straight walls?

Adam is using the shorthand for the animation property, so let’s rewrite it so it’s a bit easier to read.

animation: waving 0.35s ease-in-out 2.5 alternate forwards 0.3s;

// Can also be written as
animation-name: waving;
animation-duration: 0.35s;
animation-timing-function: ease-in-out;
animation-iteration-count: 2.5;
animation-direction: alternate;
animation-fill-mode: forwards;
animation-delay: 0.3s;

The key here is animation-iteration-count, or the number of times the waving animation will occur. This animation will run twice, then stop halfway between each state (.5), putting the end of the path right in the middle.

It’s a clever trick that keeps the animation smooth and only uses two paths to create three shapes.

Marker Elements

The walls of the bouncy castle have yellow toppers that add a nice flourish to the illustration. Notice how they’re part of the animation as the castle springs to life.

It’d be rather tricky to track those shapes onto the end of a path and have them move and rotate exactly how they should. That’s why Adam’s using the built-in SVG marker element.

See the arrow caps and the red dots in this MDN example?

The arrows here are placed on the marker-start and marker-end properties, which is precisely what Adam’s doing for the castle. It’s great to know that you can also place markers at marker-mid, which is all ‘interior’ vertices (not start or end).

The great thing about SVG markers is that they don’t need to be simple shapes like circles or triangles. They can get as complex and detailed as you’d like.

Wrap Up

Adam packed so many little details and techniques into this pen, that I really could have written this article to be twice as long.

If you need to place shapes along a path, you can always reach for the SVG marker element. It’s great for adding some decoration to an illustration, or points for something like a line graph.

The offset-path CSS property makes animating along a path wonderfully smooth, and it’s how Adam sends the email sliding into the shark’s mouth.

It’s a subtle technique, but using an animation-iteration-count of .5 is a fantastic way to stop right in the middle of two animation states.

Of course, the prominent technique here is Adam’s input bait and switch, where each character typed gets put inside a span instead. It solves the issue of animating text inside an input, and opens the door to endless possibilities.

Be sure to check out Adam’s CodePen profile and spend some time digging through his creations. There’s so much to learn from his demos. Also take a look at the first article I wrote on Adam’s work which breaks down an amazing webcam filter.