The No Game No Life text effect with CSS

This astounding header was created by Fu-Reiji

Last year, I wrote a post about recreating a part Sword Art Online's design with pure CSS. Ever since, I've been meaning to write the next one in the series, but could hardly find time or motivation. That's over, let's start!


I recreated the text appear animation from the opening of No Game No Life - A light novel turned anime, about two siblings taking over the world of Disboard.

Click here to view the final project, or continue reading at your leisure!
This is what it looks like in the show (hover or tap to view):

Static image of the animation GIF of the animation

Analysing the visuals

Looking at the animation frame by frame, we can see:

  • The text enters centred with a left to right appear animation
  • Two corners appear in the centre that expand past the text width
  • Both the corners and the text have the same pink glow
  • When there are multiple lines, they appear with a small delay

Building the wrapping elements

Since my last post, I've got into Vue and Nuxt as a front-end framework. It's got excellent developer experience with live reloading, so I created the boilerplate in a Vue component. Otherwise, I wrote all the CSS code without a framework, in SCSS.

This time, I was looking for a simple background. I gave the design of the “app” a little more thought, which resulted in getting some colours from Adobe Color CC.

I admit, I was a bit overzealous when selecting the palette. I ended up cherry picking from two different swatches:

Screenshot_20180402_230006

Since I didn't have a framework, I decided to go with the simplest design, which entails a top bar for controls and a large main area with a centred box which is going to hold the actual text with its animations. I also selected this background by the awesome minimalist artist TheKorNk: https://thekornk.deviantart.com/art/Jibril-No-Game-No-Life-639024930

Screenshot_20180402_230433

For the sake of trying to keep it simple, I won't go into detail about the boilerplate code that takes care of the layout. If you're curious, please check the GitHub repo!


Onto the CSS!

You might remember that this title also exists in my previous post about the Sword Art Online modal. Much of the sentiment is similar, in that the focus of this project is animations.
Animations are awesome!

Just like with any CSS code, we have to get some things out of the way first. The colour list from Color CC come into play now, as I defined a $colours variable that will hold all colour information. When the CSS is compiled, calls to this variable will be replaced with the appropriate value.

// _variables.scss

$colours: (
  "pink":      #DA146B,
  "red":       #BF314B,
  "purple":    #894BBF,
  "white":     #FDF7E8,
  "lightblue": #DDBBF5,
  "yellow":    #FFEE64
)

An artist who went by the name of JapanYoshi has created a free TrueType font face. Here, I import that and set responsive font-sizes with the rucksackcss set of PostCSS plugins.

// _reboot.scss

@font-face {
	font-family: "ngnl";
	src: url("../fonts/ngnl.ttf") format("truetype");
}

@for $i from 1 through 6 {
  h#{$i} {
    font-size: responsive;
    max-font-size: ((10px - $i * 4) + 30px);
    min-font-size: ((11px - $i * 4) + 25px);
  }
}

body {
  font-family: "ngnl";
}

At the beginning, I mentioned that the text enters from left to right while being centred, so we might as well start with that. I chose to use flexbox as the sole layout solution. This makes it easy to centre things.

View markup (click to expand)

Pug:

.box.bg-white
  .content.text-pink
    .ngnl-text-wrapper
      .ngnl-text-content
        h3 The only universal justice in this world is cuteness!

HTML:

<div class="box bg-white">
  <div class="content text-pink">
    <div class="ngnl-text-wrapper">
      <div class="ngnl-text-content">
        <h3>The only universal justice in this world is cuteness!</h3>
      </div>
    </div>
  </div>
</div>

.ngnl-text-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.ngnl-text-content {
    color: map-get($colours, 'pink');
}

It took me a while to get flexbox centreing stuck in my mind. If you have trouble remembering this method, bookmark How to Center in CSS.

With not much code, we already have a good base layer with the correct pink colour we're so familiar with from the show.

Centred, pink text

I decided to start with the two corners. My first idea was to create them using pure CSS with borders and box-shadow. Here's how that tragedy went down.

I created the two ::before and ::after pseudo-elements, laid them down with flexbox and just gave them a red background to be visible while I work on them.

.ngnl-text-content {
  &::before,
  &::after {
    display: block;
    width: 2rem;
    height: 2rem;
    background-color: red;
    content: '';
  }

  &::before {
    align-self: flex-start;
  }

  &::after {
    align-self: flex-end;
  }
}

The result with two red boxes on the sides

So far, so good. The next logical step is to experiment with ways of creating the corners. At first glance, it looked like I could achieve them by specifying two borders and a box-shadow.

This is how it's supposed to look like (ignore the colours for now). Hover over or tap the image to see how this solution ended up.

How it's supposed to look like How it actually looked like

Yeah, not ideal. Even with a transparent background, box-shadow will respect the element boundary. I realised that almost the exact same thing happened when I was working on the SAO modal a year and a half ago, with the exact same solution. Just create an image.

So I opened up Krita and quickly created the pink corner image you're seeing above, inserted and positioned that one instead of the nightmare that's that red thing.

.ngnl-text-content {
  &::before,
  &::after {
    background-image: url('/nogamenolife-text/corner.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: top left;
    display: block;
    min-height: 2rem;
    min-width: 2rem;
    content: '';
  }

  &::before {
    transform: translateY(-.5rem);
    align-self: flex-start;
  }

  &::after {
    transform: rotate(180deg) translateY(-.5rem);
    align-self: flex-end;
  }
}

Constructing transitions

Transitions are one of the most easy and rewarding things to do in CSS in my opinion. All you need to do is specify transition: width .3s ease-in-out, for example, and any time you change the width of the element, it will smoothly transition there instead of jumping. Even rapid state changes are taken care of.

I added CSS rules that define an extended width of 45rem (720px) and a retracted length of 0. This means that the corners will not align to the width of the text. Unfortunately, CSS3 has no transitions when auto is used, so we're stuck with a static width.

.ngnl-text-content {
  > * {
    line-height: 40px;
    width: 0;
  }
  
  &.showing {
    > * {
      transition-duration: 1s;
      transition-property: all;
      transition-timing-function: cubic-bezier(.19,.84,.53,1);

      width: 45rem;
    }
  }
}

Check it out (hover or tap to view):

Static image of the animation GIF of the animation

The next minute though, I had my head in my hands. Namely, how in the world will I synchronise the text clip-path to the corners? I started having flashbacks to my beginning weeks, when I had to develop animations with jQuery. Skip this block below if you don't like cringing about the many things wrong here:

setInterval(function () {
    $('.thing').width($('.thing').width() + 10);
}, 1000 / 60);

But luckily, in my great despair over this clearly devastating problem, I made some half-minded attempts. Turns out that applying any clip-path makes the element clip to its counterparts. I wanted to stand up and dance, were it not for the fact that it was about 2am with people sleeping somewhat close. So I added a clip-path that doesn't actually clip anything, it just frames the element (by the way, if you're having trouble remembering the syntax, you'll find Clippy useful).

While at it, I also added an opacity to the corners. The cubic-bezier timing function I added above makes sure that the majority of the transition happens at the end, so the corners only disappear once the text expanded.

.ngnl-text-content {
  > * {
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
  }
  
  &.showing {
    &::before,
    &::after {
      transition-duration: 1s;
      transition-property: all;
      transition-timing-function: cubic-bezier(.19,.84,.53,1);

      opacity: 0;
    }
  }
}

At this point, the corners are just sitting there in the closed state, because the transition is only applied while in motion. It has a simple fix; I just had to set visibility to when the dynamic class isn't there.

.ngnl-text-content {
  &::before,
  &::after {
    visibility: hidden;
  }

  &.showing {
    &::before,
    &::after {
      visibility: visible;
    }
  }
}

Finally, I merged all the CSS together, after which my text looked like this (hover or tap to view):

Static image of the animation GIF of the animation

Finalising

Now, you might have noticed that I completely forgot about the actual style. The text totally doesn't look like this. It's white and the text shape is revealed by a glow.
This glow is not straightforward to create in CSS, but I think applying two layers of box-shadow above each other gets close enough.

.ngnl-text-content {
  color: map-get($colours, 'white');
  text-shadow:
    0 0 8px map-get($colours, 'pink'),
    0 0 12px map-get($colours, 'pink');
}

After this, I used Vue.js to add some additional functionality to the "app":

  • An input field to let any text be entered
  • A check for this post to display a link once I publish it
  • A button to disable the font
  • A counter that limits how many lines are displayed out of the six quotes from the show
  • A has-delay class that increases with every line, so that the CSS can delay the line to match the multi-line effect
@for $i from 1 through 5 {
  .has-delay-#{$i} {
    transition-delay: $i / 15 + 0s;

    &::before,
    &::after,
    * {
      transition-delay: $i / 15 + 0s;
    }
  }
}

Wrapping up

All of this work was to create a pretty accurate effect (hover or tap to view):

Static image of the animation GIF of the animation

All in all, I'm glad I was finally able to continue my first post and work on a small project for a change.
If you want to recommend an effect I should remake in CSS, let me know here.
An effect that's best for a post...

  • Can be clearly defined with simple vectors
  • Is visible in the show it's from multiple times
  • Has a prominent place (not in the background, etc.)

Check out the source code I just talked about here
Or click here to see the app I just described making

Finally, you can check what I'm doing right now here