Jonno Witts

Welcome.

Star Wars Battlefront inspired CSS achievement badge

Today we are going to be building a CSS achievement badge from Star Wars Battlefront. Before I get into the build I wanted to advise this is fairly advanced CSS. I’ve tried to describe each step and put references to more information on certain subjects. Feel free to leave a comment below if you have any questions or even a different approach. After all, there’s more than one way to skin a cat.

From the graphics to the choice of typeface, everything in Battlefront looks stunning. I don’t think that words can do justice to how good this game looks. It also is really fun so you should probably just go and play it right now. When that first achievement unlocked for me I knew straight away that Battlefront would be making an entry into my gaming UI project. Let’s check out some examples!

Unfortunately I couldn’t manage to get an achievement and stay alive long enough to record it.

Before we get to the exciting animation development I’m going to build out the achievement in a static form.

Breaking the notification up into manageable pieces, I’ve identified the following areas that I would consider to be tasks for this build, some of these have been broken down into smaller items.

  • Layout and positioning as a whole
    • Identify the yellow and background opacity
  • The Text area
    • Widths, paddings and margins
    • Identify the typeface
    • Font sizes
  • The achievement badge
    • The symbol/icon for the achievement (in this case the Rebel Alliance badge)
    • The two borders that will be animated

Let’s write some HTML:

<div class="container">
  <div class="challenge">
    <div class="badge">
      <svg>
        <circle cx="50" cy="50" r="45" />
        <circle cx="50" cy="50" r="40" />
      </svg>
      
    </div>
    <div class="text-container">
      <div class="text">
        <h1>Blaster pistol streak</h1>
        <h2>Get 10 Kills with blaster pistols</h2>
      </div>
    </div>
  </div>
</div>

It’s pretty light-weight on the markup front, moving on to the styling. Using Adobe Illustrator I got the general layout and colours pretty easily. I checked out the Star Wars Battlefront website to see what typeface they used on there which turned out to be Brandon Grotesque. A modern typeface that has grown rapidly in popularity over the past few years. I own this font but it is a premium so I want to have a good fallback in place for those that don’t have it installed. I settled on Futura and just the basic sans-serif after this (if this was a client project then I’d have bought the font files required but as it’s just a personal project this is fine).

I’ve put everything in a container that is the size of the screen (in order to accomplish this I used the vh measurement on the height property).

.container {
  position: relative;
  display: flex;
  align-items: flex-end;
  height: 100vh;
}

Aligning items with flex-end will place the child element at the bottom of the container.

I’ve found that Font Awesome has the factions from Star Wars as glyphs so I’m going to use this for the icons on the badge.

After looking at a couple of the achievements I decided that the top line is actually the secondary heading – all unlocks have the larger yellow text and so this is the primary text. Thankfully we can have the markup make sense and just change the ordering with flex-box layout!

.text {
  display: flex;
}

h1 {
  order: 2;
}

h2 {
  order: 1;
}

I’ve been using flex-box a lot in recent projects, and I’ve found CSS-Tricks’ guide to be invaluable.

Add this with the rest of the styling and we end up with the following result:

See the Pen OMQrwp by JonnoW (@jonnowitts) on CodePen.

Animating the achievement

The real beauty in this achievement notification comes from the animation on it. Let’s break down what is happening:

  1. Text container grows vertically from its middle
  2. Primary heading appears from the top down
  3. Secondary heading appears from the bottom up
  4. The background to the badge starts to grow from its middle point
  5. Badge icon fades in and reduces in size – as if it is being placed on top
  6. Yellow borders, starting at the top, are rotating in opposite directions and growing in circumference
Star Wars Battlefront achievement animation in slow motion

Side note: As we want to keep the final states of everything we animate we need to set the animation-fill-mode property to forwards.

The Text Container

To get the text container working we need to update our original CSS, don’t worry though, because we can use the initial values for our final state.

The text box grows in height from 0 to it’s end value of 122px. We’ll give it an initial value of 0 and in our keyframe animate it to a max-height of something sensible like 175px/17.5rem. Using max-height rather than the fixed height property gives us breathing space if we have one or three lines of text. Additionally, we’ll move the padding top and bottom values into our keyframe and set the initial values to 0. Finally add an overflow of hidden to hide the child elements before the container gets to its resting height.

.text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: rgba(0,0,0,.4);
  font-size: 2rem;
  line-height: 135%;
  text-align: center;
  text-transform: uppercase;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  padding-top: 0;
  padding-bottom: 0;
  max-height: 0;
  overflow: hidden;
  animation: textBox 2s;
}

@keyframes textBox {
  to {
    padding-top: 2.5rem;
    padding-bottom: 2.5rem;
    max-height: 17.5rem;
  }
}

The Text

The text slides in – bottom to top for the secondary/top line, top to bottom for the primary/bottom line.

On their initial values, set the margin-top on the primary heading to 6rem and the secondary heading -3rem. Create another set of keyframes to animate both to 0:

h2 {
  order: 1;
  color: #ffffff;
  letter-spacing: 1px;
  margin-top: 3rem;
}

h1, h2 {
  animation: textMove 1s;
}

@keyframes textMove {
  to {
    margin-top: 0;
  }
}

FYI: I haven’t just plucked these numbers for margins, paddings and timings out of thin air! It took some playing around before it looked correct!

The Badge

I looked into using divs and pseudo elements but between the transparency and the animations that will be increasing in size and rotating, I’ve settled using SVGs. It’s been a little while since I did SVG animation so I refreshed my memory on some work that used animated SVGs for validation feedback.

We’re going to call on flex-box again to get the text div to grow from the middle. Give the div.text-container the following CSS:

.text-container {
  display: flex;
  align-items: center;
  height: 150px;
}

Align-items will place the inner div.text in the text-container’s middle. Set this inner div.text height to 0 and we can add some animation to the height a vertical padding to make it grow.

SVGs make what could have been, to be honest, pretty messy, totally easy. In the HTML we have two SVG circles:

<div class="badge">
  <svg>
    <circle cx="50" cy="50" r="45" />
    <circle cx="50" cy="50" r="45" />
    <circle cx="50" cy="50" r="40" />
  </svg>
</div>

.badge {
  position: relative;
  width: 100px;
  height: 100px;
  margin: 0 auto .5rem;
}

svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 100px;
  height: 100px;
  transform: rotate(-90deg);
}

circle {
  &:nth-child(1) {
    r: 0;
    stroke: none;
    fill: rgba(0,0,0,0);
    animation: backgroundFade 1s forwards,
               backgroundCircle 1.3s forwards;
    animation-delay: .25s;
  }
  
  &:nth-child(2), &:nth-child(3) {
    stroke: $battlefront-yellow;
    stroke-width: 3;
    fill: none;
  }
}

@keyframes backgroundCircle {
  to {
    r: 45;
  }
}

Great! Now we’re cooking with gas!

Summarising what we’ve got so far – We’ve created three SVG circles. Circle 1 has no stroke (border) and is going to grow from a radius of 0 to 45 using the keyframe animation we’ve called backgroundCircle. Circle 2 and 3 have a stroke width of 3. And this is sweet because we can set their strokes to be dashed using the CSS property stroke-dasharray. If we set this value high we will end up strokes so large we can only effectively see one stroke:

Increasing the stroke-dasharry CSS value

Increasing the stroke-dasharray CSS value from 0 to ~280

When we’re happy with the stroke-dasharray value we can offset this (the outer circle will need a slightly higher value). This will effectively push the stroke that we made look like a single, huge stroke, out of sight. We can do this using the stroke-dashoffset CSS property:

Reducing the stroke-dashoffset CSS value

Reducing the stroke-dashoffset CSS value from 282 to 0

Put it all together and we have the following for our initial values:

circle {
  ...
  &:nth-child(2) {
    stroke-dasharray: 282;
    stroke-dashoffset: -282;
    animation: innerCircle 2s;
  }

  &:nth-child(3) {
    stroke-dasharray: 251;
    stroke-dashoffset: 251;
    animation: outerCircle 2s;
  }
}

@keyframes innerCircle {
  10% {
    stroke-dashoffset: 221;
  }
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes outerCircle {
  10% {
    stroke-dashoffset: -252;
  }
  to {
    stroke-dashoffset: 0;
  }
}

And here we’ve also added in the animation keyframes that will bring the circles into view.

SVG line animation can be really confusing so I would recommend Chris Coyier’s post about it for an in-depth explanation.

To handle the icon dropping we need activate a 3D space for the badge, achieved with the transform: perspective() property. This is the distance along the Z plane from the origin (0) and the viewer. There is an exclusive perspective property that will set the perspective for a group of elements, as we’re only dealing with a single element we’re using the former. With the 3D space active we can move the badge along the Z-axis with the translate3d property (use this over translateZ to push the animation to the graphics card rather than the computer processor). Add an initial opacity of 0 that is set to 1 with some CSS animation and we complete our work on the badge!

i {
  transform: perspective(20px) translate3d(0,0,10px);
  opacity: 0;
  animation: visibleIcon 2s, dropIcon 2s;
}

@keyframes dropIcon {
  to {
    transform: translate3d(0,0,0);
  }
}

@keyframes visibleIcon {
  to {
    opacity: 1;
  }
}

Just for good measure we’ll put a delay on all the badge animations as the text animation finishes before the badge starts.

circle, i {
  delay-animation: 3s;
}

The final step: Rotating the borders. We made it, we’re at the last step. I’m going to have the borders start from the approximately 25deg from the top on each side points and rotate, the long way round, so they meet at the bottom. We will need to set a transform: rotate(XXdeg) property on each of the circles and create an animation keyframes of the following:

@keyframes innerCircle {
  10% {
    stroke-dashoffset: 221;
  }
  to {
    stroke-dashoffset: 0;
    transform: rotate(450deg);
  }
}

@keyframes outerCircle {
  10% {
    stroke-dashoffset: -252;
  }
  to {
    stroke-dashoffset: 0;
    transform: rotate(-270deg);
  }
}

At the same time the strokes are growing they’re now are spinning in opposite directions and….well, it’s just easier to take a look at the demo!

A Note on Blurring

If you take a close look at the achievement you’ll notice there is a blur sitting on the black background. This can be accomplished with backdrop-filter: blur(10px);. At the time of writing this is currently only supported by the WebKit Nightly browser or Safari 9+ with the -webkit- prefix and is under consideration by the W3C.

See the Pen Star Wars Battlefront inspired achievement by JonnoW (@jonnowitts) on CodePen.


Congratulations! We just built a sweet Star Wars Battlefront inspired achievement! There was a lot to this build but once you break things down, things start to become simpler.

Building a responsive photography portfolio

I’m in the process of helping Suzy Harrison rebuild her photography portfolio. Suzy has been using an out of the box solution up to now. However it has a clunky back end and does not offer up any responsive options and so I’m going to give her a hand building a nice looking and functioning website. Suzy also wants to protect her photography from people stealing the images. I’ve got a couple of ideas to hinder people which I’m going to try out along the way.

First things first; I’m building this website with an adaptive approach (by which I mean starting with the bare minimum and building up) – I’m going mobile first, JavaScript last and the whole thing will be fluidly responsive. I have a tendency to go desktop first and then build down to mobile, this is out of habit more than anything else, so this time I’m really forcing myself to go for the lowest common denominator of screen size and work up (think you’re old iPhone 3GS – small screen, no retina display and on a low bandwidth connection).

Responsive imagery is going to allow me to serve a site visitor a different image depending on the device they are viewing the website on. This is good because imagery can be huge in file size, and so the user will only make one request to the server and will receive an appropriately sized image – a mobile phone user will get a smaller image than someone viewing on a 60” 4K television. The image will look just as crisp for both users and will, in theory, download faster. I say in theory as currently we can’t target imagery based on the connection speed.

It turns out responsive imagery is not the confused nightmare it first seemed like many years ago. Reading around the subject has led to my discovery of the srcset attribute. I had heard of this but never really given it a proper look. A few links I can highly recommend are CSS-Tricks’ article and the Smashing Magazine’s Article on the subject and the W3C specification.

Approach A: Pixel density

The first approach available to us is to handle different pixel densities. These are the different pixels-per-inch that different screens have. The higher the pixel density, the higher the quality. So a standard PC monitor will have 1x pixel density, an Apple Retina display will have 2x and so on.

I’ve gathered a few images to use from Suzy’s portfolio, I’ve got my basic html in place and I’ve put in a few srcset attributes to handle different pixel densities.

A screenshot of the images used in this example inside a Windows folder.

&lt;img src="img/large/graduation_large.jpg
srcset="img/large/jen_large.jpg 1x,
img/large/marques_large.jpg 1.5x,
img/large/moose_large.jpg 2x,
img/large/skints_large.jpg 3x"
alt="Students jumping happily into the air on their graduation day" /&gt;

As you can see, we’re using the basic image tag. All we’ve done is include the srcset attribute with a few ‘image candidate strings’ (each entry basically: img/large/jen_large.jpg 1x).

We include the src attribute with our fallback image for older browsers (at the time of writing, no version of Internet Explorer supports srcset. the new Edge does).

So that covers pixel density descriptors….but I’m sat wondering what about if I have a 1980 x 1080 sized screen with 1x pixel density and an iPhone with 1x pixel density?

Approach B: Width Descriptors

In the case above we’re going to get served the same image, and in reality I might want a 320px image for the phone and a 1980px wide image for the desktop. In this case we can use the width descriptor with each entry:

&lt;img src="img/large/graduation_large.jpg"
srcset="img/large/jen_large.jpg 320vw,
img/large/marques_large.jpg 480vw,
img/large/moose_large.jpg 720vw,
img/large/skints_large.jpg 1200vw"
alt="Students jumping happily into the air on their graduation day" /&gt;

In this code I’ve swapped out the pixel density values for the width descriptors. The widths are the widths of the viewport (not the container the image is sitting inside of – think CSS media queries). If I resize my browser to 980px I’m being served moose_large.jpg.


And if I increase the pixel ratio to 2x (through Chrome’s developer tools) and refresh I’m now being served the skints_large.jpg:

It took me a while to get my head around this. I had tried putting both the pixel density and the width descriptor in one entry, reading through the spec informed me you have to use one or the other. I was also having a couple of issues to begin with as the image would load the largest image then when I downsized the browser the image wasn’t changing. I think this is because the largest image has been downloaded so the browser thinks this will do and there’s no need to download another, smaller image. So I started small and increased the browser size which had the same effect.

After reading through the W3C spec (something I and I’m sure a lot of other front-end developers should do more) I discovered that the browsers are ‘encouraged’ to update the image when the view port changes – which was happening when going from small to large. But I couldn’t find anything about going from large to small. It makes sense that it wouldn’t decrease the image, unless you’re changing the image for the ‘art direction’ scenario – check out the <picture> element for this situation.

Note: If you’re testing this or developing you will want to turn off your cache as this was giving me similar issues.

The final piece of the puzzle is the sizes attribute. The sizes attribute must be used in conjunction with the srcset attribute, it can’t be used on its own. The attribute tells the browser how wide to render the image in relation to the view port. We can put media queries in here to render images at different sizes, we can resize the image in CSS, from what I understand, this attribute works a bit like the width attribute but for different media query sizes.

Chris Coyier’s sums this up nicely:

“If this media query matches, render the one that you’ve chose at that exact size”

&lt;img src="img/large/graduation_large.jpg"
srcset="img/large/jen_large.jpg 320vw,
img/large/marques_large.jpg 480vw,
img/large/moose_large.jpg 720vw,
img/large/skints_large.jpg 1200vw"
size=”(min-width: 800px) 700px”
alt="Students jumping happily into the air on their graduation day" /&gt;

However, in Chris’s example he says you can use 100%, however the W3 spec may have changed as it states percentage values are invalid. Instead we can use 100vw (viewport width), and this will have the same meaning [source – See Example 4: Using the srcset and sizes attributes].

So that covers responsive imagery for the site, stay tuned for the next part coming soon!

Corporate Identity Manuals

I’m a big fan of Aaron Draplin and the Draplin Design Co. I managed to track down this gem of a book all about Corporate Identity Manuals Draplin mentions in one of his videos.
Corporate Identity Manuals front cover on a wood desk surrounded by Field Notes paraphernalia The book, by David E Carter, is full of excellent examples of “historic” identity manuals from various companies back in the 70s – the concepts from them still apply today: Showing context. It’s always so much easier to sell an idea when it is in context.

Corporate Identity Manuals open on the branding page for D C Turner Construction

While I might not be designing up trucks, hard hats, and tool kits (yet) I can still show context. I recently was working on a project that was designed with the idea that the application would be used on an iPad. While I didn’t have access to an iPad I presented it inside an image of an iPad within the browser.

Check out the work I did a while back on the Centre for Reading and Language for a fully functional contextual example!