6. Mobile First Web Design

Who's still using a computer? And your websites should respond to this question.

Overview / Preparation work / Responsive menus / CSS flex box / Touch events / For the next unit...

Overview

Source: Brad Frost

The world is changing, and so is Web Design. There are just so many digital devices coming out each year, and they all get a different screen resolution, and a notable difference driven by this revolutionary progress is the contrast between the portrait view and the landscape view. In the former, the screen width is longer than the screen height, whereas it is the other way around in the latter. If your websites are designed to be best viewed with the portrait view, then its layout will inevitably a mess in the portrait view. Since this course website is not designed specifically to be viewed in the portrait mode, it won't look decent in your smart phone, albeit not a total disater.

This is why you need to make your websites responsive to different screen resolutions, so when somebody interested in you and check your websites with their mobile devices, they don't feel frustrated and instead recognize you as a professional Web designer.

Preparation work

The first step in making your websites responsive is to tell Web browsers that your websites are responsive with the metadata added to <head> below.

<meta name="viewport" content="width=device-width, initial-scale=1.0">

In this meta element, we first include name="viewport" to tell mobile browsers to refer to a specific initial size of the viewport (that is, the visible area of Web contents). Why? When a website is not designed to be responsive, the width of an HTML document is almost certainly longer than the width of a mobile device in its portrait view. A mobile Web browser may thus use a virtual viewport to shrink the webpage and render the shrinked version to the actual viewport. Since a virtual viewport is always used by a mobile browser, it is not possible for a webpage to use a different layout based on a different device width. Setting name="viewport" stops a mobile Web browser from using a virtual viewport in all cases and allows the browser to adopt a designed layout for a viewport smaller than a specific size (with recourse to media queries. See below). This is relevant to the setting in content="width=device-width, initial-scale=1.0". In the value of the content attribute, we define two separate subvalues. The first subvalue width=device-width means that the actual viewport width used by the Web browser varies by the width of different devices. This is the most crucial part in setting up a responsive website since users' devices have all different screen widths as discussed in the previous section. The second subvalue tells the browser to initially scale the viewport size to the original one (1.0 = 100%), so the layout is always adjusted based on the actual viewport width of the device. This line of metadata would work in most responsive website projects, but if you want to know a bit more about this part, see this page in MDN Web Docs

Media query

After you set up the metadata for a responsive webpage properly, you can then define different layout in a CSS file with media queries using @media rules as in the following example:

@media only screen and (max-width: 800px) { /* some CSS styles */ }

The above example defines a block of CSS styles that applies only when the media, which is set to be the device screen, has a width of 800 pixels at maximum. Following this logic, we can have CSS styles that apply by default except in some cases:

/* Default DIV style */
div { background-color: red;
width: 30%;
}
@media only screen and (max-width: 800px) { /* DIV style applied in a narrower device */
div { width: 100%; }
}

In this example, the width of a <div> element is by default 30% of the width of its immediate ancestor, but is adjusted into 100% in a device with a screen width of 800 pixels or lower. Since the property background-color is not set to a different value in the block inside the media query, it would remain unchanged and still embellish a <div> element with a red background color.

Mobile first

Source: Dickson Phua

Mobile devices are dominating the world. Period. So when you start a responsive website project, the first thing you need to think is the layout optimized for mobile devices by default, and then adjust this mobile-based layout for some desktop devices. Thus, you should set media query rules specifically for the desktop devices, rather than for mobile devices. As in the following example, we would define CSS styles that apply only to devices with a screen width of 1,200 pixels or higher (using min-width). The number 1,200 is going to be a safe break point, as most mobile devices have a viewport width shorter than 1,200 pixels (see this Adobe page). About twenty five years ago, a desktop computer screen with a 1024 x 768 resolution would be considered a "large" screen, but I guess most of them are dead by now, so we should be fine.

@media only screen and (min-width: 1200px) { /* CSS styles applied in a wider device */
}

Mobile Safari sucks

Source: TJ Van Toll

As we proceed to develop a mobile-friendly website, we would try different things to make it easier to navigate via a Web browser. Then, at some point of time, you will almost necessarily blame mobile Safari for producing unexpected bugs in your website. Since there is a vast body of iPhone users, you don't really want to exclude this user base that would greatly contribute to the number of page views of your website, which can lead to a higher profit if you work on a commercial project. This means that you always need to look for solutions that are specific to mobile Safari, which is really agitating. But if profit is not your primary concern, maybe you could choose not to give a damn, just like me. I am not the only person whining about how poor mobile Safari is (see this page or this one, for example), and Apple has been ignoring this issue for years. Let's hope Apple can fix all the issues in the coming years, rather than just focus on how expensive a new iPhone should be.

Responsive menus

With media queries, there are two ways to present different navigation panes in different contexts. One is to have only one <nav> in your HTML document, which is transformed by different CSS styles in various browsers. The other is to have two <nav> elements that are organized and styled differently in a CSS file, and simply use media queries to decide when to display which one. The first transforming approach is more complicated on the CSS side, as you need to try hard to make sure the CSS styles applied in different contexts can adjust same navigation pane properly, but it is useful if your menu has a huge number of items so duplicating the entire menu may increase the size of a webpage unnecessarily. The second hide-and-seek approach is easier to implement, but only when your menu has only a few item. I'll do a very simple demonstration below, and it's up to you to choose the one that is most appropriate in your case. You can also create just one mobile-based drop-down menu for all contexts, although it could confuse the users a little bit when they are using their laptop or desktop computer.

The transforming approach

Let's first create a navigation pane with just two items with a hamburger menu Unicode symbol:

<nav> <span>☰</span>
<a href="#">Item 1>/a>
<a href="#">Item 1>/a>
</nav>

Now let's give the nagivation pane a decent look in a mobile-browsing context (remember the mobile first principle?). The main aspects in the following CSS styles are as follows. First, the <nav> element is set to take the full width of the viewport, and it is fixed to the top of the viewport. Second, the height of the menu is set to the 5% of the viewport height, which is also the line height of the element to help align texts vertically. Since this mobile-based menu is created as a drop-down menu, its items are hidden by default, and they will only be presented when the hamburger icon is clicked.

nav { position: fixed;
top: 0;
width: 100%;
background-color: black;
color: white;
height: 5vh;
line-height: 5vh;
opacity: 0.95;
}

nav > a { display: none;
color: white;
text-align: center;
text-decoration: none;
}

The rest of the tricks relies on jQuery - we add a listener to the hamburger menu, and show its <a> siblings when it's clicked.

window.onload = function() { $('nav > span').on('click', function() { $(this).siblings().css('display', 'inline'); }); };

The above codes help present <a> elements as they were - namely inline elements. But we need a drop-down menu, and this can be done by changing inline to block in the above JS codes. Do you know why?

$(this).siblings().css('display', 'block');

Now only do <a> elements appear automatically below the menu bar, these elements also take up the full viewport width. Why? Once an inline element is turned into a block element, it takes width and height settings, which, in the current case, are cascaded from their parent <nav>.

OK, it seems like we have had a great start. Now, how can we hide the menu items by clicking the hamburger symbol again? Easy. We can get it done with the if...else... statement - if the items are hidden, show them, or hide them if they are present. In this first line inside the event listener below, we first check if the first sibling <a> element has a none value for its display property, and store the boolean value into the variable hidden. The presentation of all sibling <a> elements is then determined by this variable.

$('nav > span').on('click', function() { //Check if the first <a> is hidden
var hidden = ($('nav > span').siblings().eq(0).css('display') === 'none');
if(hidden) { $(this).siblings().css('display', 'block'); }
else { $(this).siblings().css('display', 'none'); }
});

It's time to transform this drop-down menu to the one with a horizontal layout with media queries. Let's also set the break point to a viewport width of 1,200 pixels in the CSS file, and our first change is to hide the hamburger symbol. Once the symbol is hidden, you don't need to be worried about clicking the symbol and trigger unexpected actions by accident. Second, let's set the <a> siblings to be inline-block elements. The reason is that we want the elements to be treated like a normal text flow but at the time made flexible in terms of their height and width. Now adjust the width of your Web browser, and see how the website responds to the change!

@media only screen and (min-width: 1200px) { nav > span { display: none; }

nav > a { display: inline-block;
height: 5vh;
width: 10%;
}
}

The hide-and-seek approach

As mentioned above, when you adopt a hide-and-seek approach, you need to have two <nav> elements in your HTML document, which are styled differently and displayed in different contexts. Let's start with the HTML part first:

<nav id="desktopMenu"> <a href="#">Item 1>/a>
<a href="#">Item 1>/a>
</nav>
<nav id="mobileMenu"> <span>☰</span>
<a href="#">Item 1>/a>
<a href="#">Item 1>/a>
</nav>

With two <nav> elements that are specified with a different id attribute, their appearance can be made very distinct, and one could be hidden when the other is present:

/* All <a> in <nav> share the same text styles and layout */
nav > a { display: block;
background-color: grey;
color: black;
text-align: center;
}
/* Mobile first - mobile menu is visible by default */
nav#mobileMenu { display: block; background-color: black;
color: white;
height: 5vh;
width: 100%;
line-height: 5vh;
}
/* Desktop menu is hidden by default */
nav#desktopMenu { display: none; }
/* Applied only when viewport width > 1200 px */
@media only screen and (min-width: 1200px) { nav#mobileMenu { display: none; }
/* Define the desktopMenu style here */
nav#desktopMenu { display:block;
position: fixed;
top: 5vh;
left: 10vw;
height: 5vh;
width: 10vh;
line-height: 5vh;
}
}

Without the need to transform one <nav> layout into another, we can easily move a navigation pane away from the top/left edge in the desktop landscape view and define its menu items separately. These two logics may be applied to other elements in a webpage, but of course you don't want to duplicate everything - it's just a poor practice in Web design.

CSS flex box

Source: Wikipedia

As noted earlier, you can't just keep being lazy and duplicate everything, let along the elements that includes the main contents of a website, since a Web browser will be loading too much trivial stuff. For instance, if someone is using a desktop computer, then there's little chance that s/he will turn the Web browser into a portrait view, and the duplicated parts for the portrait view are loaded for nothing and only slow your website down.

In this regard, it is almost necessary to adopt the transforming approach for the contents of a responsive website project, and you need an approach to swiftly interchange between the layouts for different contexts. No problem, CSS flex(ible) box is here at your disposal. CSS flex box is a powerful tool that allows you to align and order block elements in a container designed to be a flex box. We will go through a simple demonstration, and apply the idea to responsive Web design.

We can start with the following HTML content, in which the <article> block is set as a flexbox, and the three <section> elements are the content boxes to be arranged vertically inside the flex box and takes the full width of the viewport (mobile first, remember?).

<article> <section>Section 1</section>
<section>Section 2</section>
<section>Section 3</section>
</article>

Next, you need to style the <article> element as a flex container with the display property, and we set its height to 100% to manipulate the flexbox layout right inside the viewport.

article { display: flex;
height: 100%;
}

Once these are done, you can manipulate how the block elements inside are aligned following with the following crucial properties (see also this nice and complete CSS-Tricks tutorial):

  • flex-direction - It can be row, column, row-reverse, or column-reverse, so you can display the block elements horizontally/vertically in their original/reversed order.
  • flex-wrap - By default, all the block elements inside a flex box will be squeezed into the same line, but you can set the property as wrap so block elements that cannot fit into the current line (due to their wide dimension, for instance) will be thrown into a new line.
  • justify-content - If this is set as center, then all the block elements are centered in the same line vertically/horizontally, depending the direction of alignment indicated with flex-direction. The value space-evenly not only gives you the aforementioned centering effect, but also set the same spacing around the block elements, which thus fill the flex box evenly. There are also other values like flex-start, flex-end, space-between, space-around, and space-evenly which you can play around yourself.
  • align-content - While it is not entirely straightforward, this property controls how each row/column in a flex box takes up the space in the box. The value center helps you juxtapose all the rows/columns at the center of a screen. If you choose to use the value space-around, an even space is inserted between lines and edges. The default value strech helps divide the entire space evenly based on the number of lines in the flex box.
  • flex-grow - When set to a number larger than the default value 0, the remaining space inside a flex box is distributed to each item proportionally based on the value assigned to this property for it. For example, if an item is set to have a flex-grow of 1, and the other has a value of 2, the remaining space distributed to the latter would be twice as much as that distributed to the former. The end result is that the latter is twice as large as the first in the flex box. Of course, if all items in the same flex box is set to have the same value, then the remaining space will be distributed evenly to each item.

So far, we would see our <section> elements squeezing in the top-left corner from left to right in our webpage, since flex-direction is by default row. In this case, the flexbox only controls how items are positioned horizontally in the same line, and the height of these elements simply takes up the remaining space of the flexbox on the vertical axis. Thus, the first thing we need to do is to change flex-direction to column for the article element. With this change, now the <section> elements are ordered from top to bottom in a vertical line, and the flexbox does not control the space on the horizontal axis and allows the <section> elements to take to the full width (do you know why?). Now we want the <section> elements to be separated from each other and from the top/bottom edge with an equal spacing, so what do we need? Yes, just add justify-content and use the value space-evenly (for a narrower spacing toward the top/bottom edge, use space-around).

article { display: flex;
flex-direction: column;
justify-content: space-evenly;
height: 100%;
}

And you certainly want each of the three <section> elements to extend their height a little bit so they do not just form a line. Easy! Just specify the height property for these elements, and the flexbox will still handle the positioning automatically for you!

section { height: 30%; }

Alternatively, you could leave out justify-content for the flexbox and height for its items and set flex-grow to 1 for all <section> elements to make it possible to distribute the space in the flexbox to each of them evenly. However, if you don't set a limit on the height, an item will expand indefinitely as more contents are put into it. So, let's set a reasonable max-height of 33%. If you need some space between the elements and the edges, guess what? Just add some margins! Note that margin collapsing does not take place here, so adding both top and bottom margins gives you the effect similar to space-around of the justify-content property. This approach would be superior to the previous one when the number of items in a flexbox is unknown, as in this approach the space distributed to each item depends on the number of items in the flexbox. In the previous approach, however, the height of an item must be set explicitly, and the position of these items in the flexbox could go wrong if you miscalculate the height.

section { flex-grow: 1;
max-height: 33%
margin: 1vh 0;
}

We have now set the stage for transforming this mobile-based design into a desktop design. As usual, we set the break point to 1200 px of the screen width in a media query. In this media query, we first re-define flex-direction of the <article> element from column to row. Again, we don't need to specify other CSS properties whose value does not change in different contexts. Then, instead of the top/bottom margins in the mobile-based design, we need the left/right margins in the desktop-based design. We also have to add a limit on the width of <section> for the same reason - to avoid over-expansion as more contents are put into the element. Finally, we also need to specify a different max-width of 100%. Note that styles outside the media query are applied by default. This means that if you don't set a different max-width for the desktop-based design, you will get a max-height of 33% here, too.

@media only screen and (min-width: 1200px) { article { flex-direction: row; }

section { max-width: 33%;
max-height: 100%;
margin: 0 1vw; /* Note the change in the length unit as well*/
}
}

We're done! Now change the size of your browser windows and see how the layout is switched between the two designs seamlessly! Mobile-first is never as difficult as you imagined.

Touch events

Source: Tomomi Imura

In the world of mobile-first Web design, you eventually will have the chance to deal with the events fired by interactions with the touchscreen. In this course, you're not asked to design a mobile Web app, so I will not pepper you with lots of techniques for it (well, let along the fact I am neither a master in this field). But we will talk about three events that you might need to further optimize your simple websites in mobile browsers:

  • touchstart - fired when the touchscreen detects a touching motion (e.g., a finger touches the touchscreen)
  • touchmove - fired when the touching motion moves from one point to another point on the touchscreen without any interruption (e.g., a finger touches and moves across the touchscreen)
  • touchend - fired when the touching motion ends (e.g., no finger touches the touchscreen)

These are the three basic events involved in a mobile Web browsing content. A user might hit a button or a link in your website, which fires touchstart and then touchend in sequential order. The user might also scroll up and down in your website, which fires touchstart, touchmove, and eventually touchend. Wait, can't we just design our websites to listen to a click event in the former case? If you hit something on a touchscreen with your finger, how is it essentially different from clicking on something on a regular wide screen of your desktop computer with a mouse cursor? Well, most of mobile Web browser echo your question, and thus would fire a click event on an element even if the source is in fact a touching motion. But there's one Web browser doesn't agree with you. Guess what? It's our old pal mobile Safari!!! I cannot accuse Apple of being wrong here, though, because by definition touching and clicking are indeed different, but sometimes being so nit-picking doesn't really help, as I will explain below.

touchmove vs. touchstart/touchend

Now, to get elements listening to a click event working properly in mobile Safari as well, which force us to re-design the elements to listen to a touching event, too, such as touchstart or touchend. But as explained above, a touching event simulating a clicking event is confounded with a swiping event that involves touchmove. When you touch the screen and fire touchstart, it could be that you try to click on something or that you're about to scroll up/down. Likewise, when your fingers leave the screen and fire touchend, it could be that you complete clicking on something or that you are coming off a swiping event. The key to simulating a clicking event correctly thus lies in a successful distinction between a purely touching event and a swiping event. And this is how we are going to do in this section: Identify a touchend that does NOT follow a touchmove event as a parallel to a clicking event.

Get in on an action

Let's begin with the following HTML document. We have a vertically/horizontally centered <div> element serving as a button-like object. We also have another <div> element in which the fired event type will appear.

<div id="touchingEvent">Nothing's happening</div>
<div style="position: fixed; top: 50vh; left: 50vw;"> <div style="position: relative; width: 200px; height: 200px; transform: translate(-100px, -100px);">
</div>
</div>

Next, let's first write JS codes for the inner <div> to listen to a touchend event, which will change the content of the <div> with touchingEvent in the id attribute.

$(document).ready(function() { $('div > div').on('touchend', function(event) { var eventType = event.type; // Get the event type, which should be 'touchend'
$('#touchingEvent').html(eventType); // Set the event type as the content of #touchingEvent
});
});

When you try to touch that large and big green square at the center of the screen and go, you should see touchend appearing at the top of your screen. However, if you start your touching motion somewhere else, swipe to that big green square, and release, you will also see the same message! This is what we expected - there's no distinction between a purely touching event and a swiping event.

To make this disction, we need to take two steps. First, we add a listener to the entire HTML document for a touchmove event and detect if there's a swiping motion. Second, if a swiping motion is detected before a touchend event is fired, when a touchend is fired on the big green square, nothing will happen. To achieve this goal, we need to first add a global boolean variable swipeEvent with a default value set to false. Then, we have to modify the event listener for the square to take actions only when swipeEvent is false.

var swipeEvent = false;
... if(!swipeEvent) { // TRUE only when swipeEvent = false var eventType = event.type; // Get the event type, which should be 'touchend'
$('#touchingEvent').html(eventType); // Set the event type as the content of #touchingEvent
}

Now, we need the following JS codes to allow for global swiping motion detection, so we make the entire HTML document to listen to the touchmove event and set the global swipeEvent variable as true. Therefore, even if you release your fingers on the big green square, it's event listener will do nothing. We can also set the message at the top of the page to indicate that the swiping event is happening.

$(document).on('touchmove', function() { swipeEvent = true; // Set the boolean 'detector' as true
$('#touchingEvent').html('Swiiiiiping!'); // Show the swiping message
});

If we stop here, then we can do all these for just one time since from now on, swipeEvent is always true so you will never be able to get the big green square to work once again. So, we need another resetting event listener that reset swipeEvent back to false after the touchmove event is over and touchend is fired. We can add this event listener to the entire HTML document as well so it detects touchend anywhere on a touchscreen.

$(document).on('touchend', function() { swipeEvent = false; // Reset the boolean 'detector' as false
$('#touchingEvent').html('Nothing's happening.'); // Reset to the default message as well
});

Now, it should work, right? But you just found that clicking on the big green square no longer changes the default message! The culprit is event propagation we have discussed in previous units. Currently the entire HTML document is listening to a touchend event, and the big green square is also listening to the same event. Since the big green square is part of the HTML document, a touchend event fired on it also propagates to the entire HTML document, so the event listener we just added above is also executed to reset the message at the top back to its default message. Consequently, it looks like nothing has ever happened because the resetting part just keeps running whether the touchend event is fired on the big green square or not. To solve this issue, we need to stop the event fired on the big green square from propagating with the following code:

event.stopPropagation();

With this line, the touchend event fired on the big green square will only run the part checking if swipeEvent is false, which means a purely touching event.

Web design could have been a lot easier, but thanks to Apple, your life is made more challenging and...perhaps more meaningful. And look at you! You have never been so confident, right? Your effort in learning all these will help expand your knowledge in this field exponetially!

Concluding remarks

Source: doodleguy@OPENCLIPART

Now that your journey of Web design has come to an end in this course, and I hope you have enjoyed everything you learned. As I kept reminding you in this course, it was just impossible to cover every single bit in the ever-changing world of Web design, but I've paved the road for you to walk into the future, and you must continue your journey on your own. Start a new website project, polish your personal website, read a book about Web design to get the most updated information, etc. If you stop right after this course, your poor memory will abandon this knowledge very rapidly - and you will spend a great amount of time just to re-gain everything. So, keep pushing yourself to be a Web design master. Godspeed!

All Copyright Reserved; Tsung-Ying Chen 2021