Heading image for post: Responsifying with Viewport Units

Design

Responsifying with Viewport Units

Profile picture of Rye Mason

Not long ago, I designed what seemed like a tricky heading to implement. For our upcoming Ancient City Ruby conference, I placed two lines of text on either side of “450” (the number of years St. Augustine has been a city), taking up the full width of the containing column. Oh, yeah, and I wanted this heading to be responsive.

CSS all the way

If you’re not using Javascript to determine browser width — which I wasn’t, for the sake of finding a CSS-only solution — you might think you could use percentages or rems to make this work. But those solutions don’t cause the text to resize correctly, because percentages and rems are relative to their parents and can’t calculate the size of the browser window.

Viewport units are nice because they let us create elements relative to the viewport instead of a container. That’s why, within a container that has a max-width, you can still set elements to take up space according to the viewport size, something you can’t do with percentages or rems.

This layout was a good test case for viewport units, which I’d heard of but never used before. Viewport units — vw (viewport width), vh (viewport height), vmax (the larger of the two), and vmin (the smaller of the two) — represent 1% of the viewport size, and are fully supported by the big three (Firefox, Chrome, and Safari). IE 9+ has partial support (because it uses vm instead of vmin), and all modern mobile browsers except Opera support viewport units. There’s some bugginess in iOS 7 related to vh, but there are workarounds if you need them. We won’t be using vh in this example, though.

Let’s get started

First I created a containing element called .column, with a span for each of the elements inside:

.column
    %span.opening St. Augustine celebrates
    %span.number 450
    %span.closing years of history in 2015

I positioned .column relatively, with a width of 80% and max-width of 1000px. (Again, this wrapper won’t affect child elements that have viewport units. This is because viewport units remain relative to the viewport instead of parent containers.)

.column
    text-align: center
    position: relative
    width: 80%
    max-width: 1000px

I absolutely positioned .first and .last on the left and right edges of .column and added a transition effect to smooth resizing motion.

span
    transition: .5s
    &.opening
         left: 0        
    &.closing
         right: 0
    &.opening, &.closing
         position: absolute

I styled each of the span elements, and added a tiny bit of left margin to .number because it isn’t actually dead center. “St. Augustine celebrates,” the preceding span, is a bit longer than the last, “years of history in 2015,” so .number needed to be nudged a bit.

span
    transition: .5s
    &.opening
          left: 0
    &.closing
          right: 0
    &.opening, &.closing
          border-top: 1px solid $red
          border-bottom: 1px solid $red
          display: inline-block
          position: absolute
          top: 28%
          padding: .8rem
    &.number
          color: $red
          font-weight: 700
          letter-spacing: -2px
          margin: 0 0 0 3%

Add some magic

Finally, I implemented vw units for the text, with rem sizes for fallbacks (looking at you, IE 8). The font size is determined by the current viewport width, so as you resize your browser, the letters fill the amount of space specified — and at most sizes, this works really well.

span
    transition: .5s
    &.opening
        left: 0
    &.closing
        right: 0
    &.opening, &.closing
         font-size: 1.3rem
         font-size: 2vw
         border-top: 1px solid $red
         border-bottom: 1px solid $red
         display: inline-block
         position: absolute
         top: 28%
         padding: .8rem
    &.number
         color: $red
         font-size: 3rem
         font-size: 8.5vw
         font-weight: 700
         letter-spacing: -2px
         text-align: center
         margin: 0 0 0 3%

Up and down

Of course, I wanted this layout to work at different breakpoints. I wrote rules to create a pixel-based font size when the viewport reaches the largest comfortable width, because I didn’t want the text to grow to infinity, and since the wrapper has a max-width the text could eventually outgrow it. To account for this, I set the font size for .open and .close to 24px and .number to 129px beyond a browser width of 1300px.

When sizing down, there wasn’t a whole lot to change. I dragged my browser window back and forth to eyeball top margins for .opening and .closing, which I changed at different breakpoints to keep the content vertically centered on either side of .number.

I set rules at max-widths of 480px and 680px to keep the text at a smaller viewport width on mobile devices. For viewports under 680px, the text stacks for readability instead of centering.

span
    transition: .5s
    &.opening
         left: 0
    &.closing
         right: 0
    &.opening, &.closing
         font-size: 1.3rem
         font-size: 2vw
         border-top: 1px solid $red
         border-bottom: 1px solid $red
         display: inline-block
         position: absolute
         top: 28%
         padding: .8rem
        +min-width(1300)
            font-size: 24px
            top: 32%
        +max-width(840)
            top: 24%
        +max-width(700)
            top: 18%
        +max-width(680)
            font-size: 1.8rem
            font-size: 4.3vw
            display: block
            position: relative
            top: 0
    &.number
         color: $red
         font-size: 3rem
         font-size: 8.5vw
         font-weight: 700
         letter-spacing: -2px
         text-align: center
         margin: 0 0 0 3%
        +min-width(1500)
            font-size: 129px
        +max-width(480)
            font-size: 4rem
            font-size: 12vw

For those of you following along at home, below are the mixins I’m referencing to set my breakpoints:

= min-width($width: 900)
  @media screen and (min-width: #{$width}px)
    @content
    transition: all .5s ease-in-out
= max-width($width: 900)
  @media screen and (max-width: #{$width}px)
    @content
    transition: all .5s ease-in-out

Done!

I was pretty happy with how the final heading turned out. (The live version is no longer available, but try it for yourself!)

You can use viewport units on anything, not just text. For my purposes, I found viewport units to be really helpful with font-size, and knowing it’s pretty cross-browser friendly is a compelling reason to use it in the future. Try implementing viewport units in your next project and see where it takes you.

More posts about Design CSS SASS

  • Adobe logo
  • Barnes and noble logo
  • Aetna logo
  • Vanderbilt university logo
  • Ericsson logo

We're proud to have launched hundreds of products for clients such as LensRentals.com, Engine Yard, Verisign, ParkWhiz, and Regions Bank, to name a few.

Let's talk about your project