|
From CSS in Depth by Keith J. Grant This article takes a look at the three principles of responsive design. We will start building a responsive page, and I’ll unpack each of them as we go. After that, we’ll take a look at images, which require some special considerations on responsive sites.
|
Save 37% on CSS in Depth. Just enter code learncss into the discount code box at checkout at manning.com.
In our modern world, the web is everywhere. We use it on our desktop in our office. We lie in bed surfing on our tablet. It’s even on some of our television screens in the living room. And we carry it everywhere with us on our smartphones. The web platform of HTML, CSS, and JavaScript’s a universal ecosystem unlike anything that has come before.
This poses an interesting problem for us as web developers: how do we design our site to make it usable and appealing on any device our users might use to access it? Initially, many developers approached this problem by creating two websites: “desktop” and “mobile.” The server would redirect mobile devices from http://www.wombatcoffee.com to http://m.wombatcoffee.com. This mobile website would usually offer a more minimal experience and a streamlined design for small screens.
This approach began to break down as more and more devices emerged on the market. Do you direct a tablet to the mobile website or the desktop? What about a large “phablet” phone? An iPad Mini? What if a mobile user wants to perform an action you only have available on the desktop version of your site? In the end, a forced dichotomy between desktop and mobile causes more problems than it solves. Plus, you must maintain an extra website to make it work.
A far better approach is to serve the same HTML and CSS to all your users. By using a few key techniques, you can make this content render differently based on their browser’s viewport size (or, occasionally, based on screen resolution). This way, you don’t need two distinct websites. You create one website that works on a smartphone, a tablet, or anything else you throw at it. This approach, popularized by Web Designer Ethan Marcotte, is called responsive design.
As you browse the web, make note of the responsive designs you come across. See how websites respond to different browser widths. Newspaper sites are particularly interesting because they’ve a lot of content crammed onto the page. At the time of writing, http://www.bostonglobe.com/ is a great example, offering a one-, two-, or three-column layout depending on the width of your browser window. Typically, you can resize the width of the browser window and see the page layout respond immediately. This is responsive design at work.
Three key principles make responsive design possible. First is a “mobile first” approach to design. This means not building a desktop layout until you’ve got the mobile version built. Second is the use of the @media
at-rule to tailor our styles for viewports of different sizes. This syntax (often called “media queries”) allows us to write styles that only apply under certain conditions. Third is the use of fluid layouts. This is an approach that allows containers to scale to different widths based on the width of the viewport.
Mobile first
The first principle of responsive design is called mobile first. This means exactly what it sounds like: you should build your mobile layout before you build your “desktop” design. This is the best way to ensure both versions work.
Developing for mobile is an exercise in constraints. You have limited screen space to work with. Bandwidth is limited. The user on a mobile device has a different set of interaction options. Typing is possible, but cumbersome. The user can’t hover over elements to trigger effects. If you begin by designing a fully interactive website, then try to scale it down into these constraints, you’ll often fail.
Instead, the mobile first approach dictates you design your site with these constraints in mind from the beginning. Once you have the mobile experience working (or at least planned out), you can use progressive enhancement to augment the experience for large screen users.
The page we’re building is shown in figure 1. You guessed it; it’s the mobile design.
Figure 1 Mobile page design
The page has three main components: a header, the “hero” image with a bit of text superimposed over it, and the main content. A hidden menu can be shown by tapping or clicking on the icon on the top right (figure 2). This icon, with three horizontal lines, is often called a “hamburger” icon, because it resembles the buns and patty of a burger.
Figure 2 Mobile page with menu opened
A mobile design is mostly a no-frills design. Apart from the interactive menu, this one is highly focused on the content. On larger screens, we can afford to dedicate a lot of space to things like the header, the hero image, and the menu. But on mobile, users are often more task-oriented. They might be out with their friends and want to quickly find store hours, or another specific piece of information like a price or address.
A mobile design is about the content. Consider a desktop design that has an article on one side and sidebar on the other, where the sidebar has links and less important items. You want to ensure the article appears first on the mobile design. This means you want the most important content to appear first in the HTML. Conveniently, this coincides with concerns of accessibility: a screen reader gets right to the “good stuff” or a user navigating via keyboard gets to the links in the article before those in in the sidebar.
That said, this isn’t always a hard and fast rule. You could probably make the argument that our hero image isn’t as important as the content below it. But it’s a striking part of the design, and in this case, I think it’s worth having close to the top. It also contains little content, which means it takes little effort to navigate past it.
Now let’s consider our design for larger breakpoints. We’ll code the mobile layout first, but keeping the overall plan in mind helps guide our decisions as we do that. We’ll build a medium and large breakpoint. The medium layout is shown in figure 3.
Figure 3 Page on medium viewport
At this viewport size, we have a little more space to work with. The header and hero can afford more padding. The menu items fit beside each other on one line, and they no longer need to be hidden. The hamburger icon is gone because we don’t need it to open the menu. And the main content can be arranged into three equal width columns. Most of the elements fill to within 1em of the sides of the viewport.
The larger viewport is the same. We’ll increase the margins on the sides of the page and make the hero image even larger. This design is shown in figure 4.
Figure 4 Page on large viewport
We’ll build the mobile design first, but it’s important to have an idea of how the larger viewports looks as well, because it might influence how we structure the HTML.
Create a new webpage and stylesheet. Link up the stylesheet and add listing 1 to the HTML’s <body>
. The markup looks pretty much the same as that for a non-responsive design. I’ve made a couple of considerations for the mobile design, which I’ll highlight momentarily.
Listing 1 Page markup
<header id="header" class="page-header"> <div class="title"> <h1>Wombat Coffee Roasters</h1> <div class="slogan">We love coffee</div> </div> </header> <nav class="menu" id="main-menu"> <button class="menu-toggle" id="toggle-menu"> ❶ toggle menu </button> <div class="menu-dropdown"> ❷ <ul class="nav-menu"> <li><a href="/about.html">About</a></li> <li><a href="/shop.html">Shop</a></li> <li><a href="/menu.html">Menu</a></li> <li><a href="/brew.html">Brew</a></li> </ul> </div> </nav> <aside id="hero" class="hero"> Welcome to Wombat Coffee Roasters! We are passionate about our craft, striving to bring you the best hand-crafted coffee in the city. </aside> <main id="main"> <div class="row"> ❸ <section class="column"> ❸ <h2 class="subtitle">Single-origin</h2> <p>We have built partnerships with small farms around the world to hand-select beans at the peak of season. We then careful roast in <a href="/batch-size.html">small batches</a> to maximize their potential.</p> </section> <section class="column"> ❸ <h2 class="subtitle">Blends</h2> <p>Our tasters have put together a selection of carefully balanced blends. Our famous <a href="/house-blend.html">house blend</a> is available year round.</p> </section> <section class="column"> ❸ <h2 class="subtitle">Brewing Equipment</h2> <p>We offer our favorite kettles, French presses, and pour-over cones. Come to one of our <a href="/classes.html">brewing classes</a> to learn how to brew the perfect pour-over cup.</p> </section> </div> </main>
❶ “Hamburger” button for mobile menu
❷ Main menu which is hidden by default on mobile
❸ Add row and columns for medium and large viewports
When writing the HTML for a responsive design, it’s important to ensure it has everything you need for each screen size. We’ll apply differing CSS to each, but they must all share the same HTML.
In this markup, we have the button to toggle the menu for mobile inside the nav. The “nav-menu” is in a place where it can meet our needs for both mobile and desktop designs. And the “row” and “column” classes are in place to allow for the desktop design. You may not know that up front, which is okay.
Let’s begin styling the page. First, we’ll add some of the simpler styles like the font, headings, and colors. This makes the page look like figure 5. Because we’re concerned with “mobile” styles, make sure your browser is resized to a narrow size. This helps show you what the page looks like on a small screen.
Figure 5 First set of styles applied
These styles are shown in listing 2. Add this to your stylesheet. This establishes border-box sizing, font, and link colors. It also adds the responsive viewport-based font size. Then it defines styles for the header and the main body of the page.
Listing 2 Page initial styles
:root { box-sizing: border-box; font-size: calc(1vw + 0.6em); ❶ } *, *::before, *::after { box-sizing: inherit; } body { margin: 0; font-family: Helvetica, Arial, sans-serif; } a:link { color: #1476b8; font-weight: bold; text-decoration: none; } a:visited { color: #1430b8; } a:hover { text-decoration: underline; } a:active { color: #b81414; } .page-header { ❷ padding: 0.4em 1em; background-color: #fff; } .title > h1 { ❷ color: #333; text-transform: uppercase; font-size: 1.5rem; margin: 0.2em 0; } .slogan { ❷ color: #888; font-size: 0.875em; margin: 0; } .hero { padding: 2em 1em; text-align: center; background-image: url(coffee-beans.jpg); ❸ background-size: 100%; color: #fff; text-shadow: 0.1em 0.1em 0.3em #000; ❹ } main { ❺ padding: 1em; } .subtitle { margin-top: 1.5em; margin-bottom: 1.5em; font-size: 0.875rem; text-transform: uppercase; }
❶ Base font size scales slightly with the viewport
❷ Page header and title
❸ Use a background-image to add the hero image to the page
❹ Dark text shadow helps light text remain readable in front of complex background
❺ Main content
These styles are mostly straightforward. They transform the page title and the subtitles in the body to all caps. They add some margins and padding, and adjust font-sizes for the various components of the page.
The text-shadow
property in the hero image element might be new to you. This consists of several values which together define a shadow to add behind the text. The first two are Cartesian coordinates, indicating how far the shadow should shift from the text’s position. The values 0.1em 0.1em
shift the shadow slightly right and down. The third value (0.3em
) indicates how much to blur the shadow. Finally, #000
defines the color of the shadow.
A mobile menu
At this point, we’re left with the most complicated part of the page: the menu. Let’s build that now. When we’re done, it’ll look like figure 6.
Figure 6 Opened nav menu on mobile device
Sometimes it takes a few passes over certain parts of the HTML to get it right. On this page, the menu took some careful consideration. I originally tried to place the <nav>
inside the <header>
, because this is where I wanted the hamburger button to appear. But after I started on the CSS, I realized I should keep them as siblings, because this allows the elements to stack naturally in the desktop layout. Writing code in any language is often an iterative process, and CSS is no different.
Initially, the “menu-dropdown” is hidden. Instead of using a hover effect, we’ll add some proper JavaScript functionality. When the user clicks (or taps) the “menu-toggle,” the dropdown appears. Clicking a second time again hides the menu. We’ll add some JavaScript to enable this functionality.
Tip Screen readers use certain HTML5 elements, such as <form>
, <main>
, <nav>
, and <aside>,
as landmarks. This helps users with low vision to quickly navigate the page. It’s important that we place the button to reveal the menu within the <nav>
, making it quickly discoverable when the user navigates there. Otherwise, the user jumps to the nav only to find it empty (as the screen reader ignores the menu-dropdown while display: none
is applied).
You’ll notice that the <nav>
appears after the <header>
, as a sibling element. This means it’ll flow to the space beneath the header. We’ll have to do one unusual thing here to match our design: use absolute positioning to pull the “menu-toggle” button up to make it appear inside the header element. Add listing 3 to your stylesheet to style the menu.
Listing 3 Menu styles
.menu { position: relative; ❶ } .menu-toggle { position: absolute; top: -1.2em; ❷ right: 0.1em; border: 0; ❸ background-color: transparent; font-size: 3em; width: 1em; height: 1em; line-height: 0.4; text-indent: 5em; ❹ white-space: nowrap; overflow: hidden; } .menu-toggle::after { position: absolute; top: 0.2em; left: 0.2em; display: block; content: "\2261"; ❺ text-indent: 0; } .menu-dropdown { display: none; position: absolute; right: 0; left: 0; margin: 0; } .menu.is-open .menu-dropdown { display: block; ❻ }
❶ Establish containing block for both absolutely-positioned children
❷ A negative top pulls the button up outside its containing block
❸ Override button user agent styles
❹ Hide the text content of the button and fix its size at 1em
❺ Overlay the button with a unicode symbol, our “hamburger” icon
❻ Display the dropdown when the class “is-open” is added to the menu
A lot is going on here, but it’s mostly a series of techniques we’ve already seen. The menu is relatively positioned to establish a containing block for both its child elements: the toggle button and the dropdown. The toggle button is pulled upwards with a negative top
, and a right
positions it on the right side of the screen. This makes it appear in the header, to the right of the page title.
We then do a replacement trick to the button: a constrained width, a large text-indent, and hidden overflow all work together to hide the text of the button (“toggle menu”). Then the button’s ::after
pseudo-element has a unicode character for its content. This character, “identical to” is a mathematical symbol with three horizontal lines. If you want to tailor the icon further, you could instead use a background image on the pseudo-element.
If you’re unsure why any of these particular styles are there, comment them out and look at the effect on the page. The page looks a little funny on a large viewport; resize your browser window to a narrow size for a better approximation of the mobile look.
The “is-open” class is a new trick. When this class is present, the final selector (.menu.is-open .menu-dropdown
) targets the dropdown. When this class is absent, the selector won’t. This enables the dropdown’s functionality. Figure 7 shows the dropdown menu open before the rest of styling is applied (note the four links in front of the hero image on the left).
Figure 7 Hamburger button working
The JavaScript in listing 4 adds and removes the is-open
class when the toggle button is pressed. Add this to your page before the closing </body>
tag.
Listing 4 JavaScript for dropdown functionality
<script type="text/javascript"> (function () { var button = document.getElementById('toggle-menu'); button.addEventListener('click', function(event) { ❶ event.preventDefault(); var menu = document.getElementById('main-menu'); menu.classList.toggle('is-open'); ❷ }); })(); </script>
❶ Click event listener (also fires on touchscreen tap event)
❷ Toggle “is-open” class on the menu
Now when you click the hamburger icon, it should open the dropdown. You can see the text of the menu in front of the content behind it. Click the hamburger again to close it. This way, the CSS does the work of showing and hiding the correct elements; the JavaScript only needs to change one class name.
The dropdown works, but the “nav-menu” inside needs some styling. Add listing 5 to your stylesheet.
Listing 5 Styling the nav menu
.nav-menu { margin: 0; padding-left: 0; border: 1px solid #ccc; list-style: none; background-color: #000; color: #fff; } .nav-menu > li + li { ❶ border-top: 1px solid #ccc; } .nav-menu > li > a { display: block; padding: 0.8em 1em; ❷ color: #fff; font-weight: normal; }
❶ Apply a border between each menu item
❷ Use a healthy amount of padding to ensure large clickable area
Again, this is nothing new. The menu is a list (<ul>
), and we override the user agent left padding and remove list bullets. The adjacent sibling combinator targets every menu item but the first, adding a border between each item.
An important thing to note here is the padding on the menu item links. We’re designing for mobile devices, which are typically touchscreen. Key clickable areas should be large and easy to tap with a finger.
Tip When designing for mobile touchscreen devices, be sure to make all the key action items large enough to easily tap with a finger. Don’t make your users zoom in in order to tap precisely on a tiny button or link.
The viewport meta tag
At this stage, our mobile design is complete, but there’s one important detail missing: the viewport meta tag. This is an HTML tag that tells mobile devices that you’ve intentionally designed for small screens. Without it, a mobile browser assumes your page isn’t responsive, and it attempts to emulate a desktop browser. All your hard work on a mobile design will be for nothing!
We don’t want that. Update the <head>
of your HTML to include it, as shown in listing 6.
Listing 6 Add the viewport meta tag
<head> <meta charset="UTF-8"> <meta name="viewport" ❶ content="width=device-width, initial-scale=1"> ❶ <title>Wombat Coffee Roasters</title> <link href="styles.css" /> </head>
❶ Viewport meta tag
The meta tag is content
attribute indicates two things. First, it tells the browser to use the actual device width as the assumed width when interpreting the CSS, instead of pretending to be a full-size desktop browser. Second, it uses initial-scale
to set the zoom level at 100% when the page loads.
Tip The DevTools in modern browsers provide the ability to emulate a mobile browser, including smaller viewport size and the behavior of viewport meta tag. These are helpful tools for testing out your responsive design. For more information on these modes, see https://developers.google.com/web/tools/chrome-devtools/device-mode/ (Chrome) or https://developer.mozilla.org/en-US/docs/Tools/Responsive_Design_Mode (Firefox).
Other options are available, but these are most likely the settings you’ll want. For instance, you could explicitly set width=320
. This makes the browser assume a viewport width of 320 CSS pixels. This is generally not preferable, though, as mobile devices come in a wide array of sizes. Using device-width
allows all to render at the most appropriate size.
A third common option that many developers add to the content attribute is user-scalable=no
. This prohibits the user from using two fingers to zoom in and out on their mobile device. This is generally a bad practice, and I discourage its use. If a link is too small to tap, or the user wants to take a closer look at an image, this prevents them from using zoom to assist.
For more information on the meta viewport tag, see the MDN documentation at https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag.
If you’re interested in really learning CSS, check out CSS in Depth on liveBook and see this Slideshare presentation.