Google Certified Mobile Web Specialist Study Notes
Basic Website Layout and Styling
Mobile Emulation
Viewport
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
device-width = DIP
initial-scale: DIP:CSS Pixel = 1:1
-->
- Mobile assume all sites with a width of 980px unless viewport defined
- Device Independent Pixels(DIP): relate pixels to real distance
- Device Pixel Ratio(DPR) = Hardware Pixels / Device Independent Pixels
- Font boosting: scale up auto-detected primary content
Relative Element Width
disable overflow
img, embed, object, video {
max-width: 100%;
}
- Not overflow or Absolutely smaller than any viewport(320px)
Tap/Touch Target Sizes
Minimum size 48px X 48px
Rooms in between 40px:
.list-item a {
padding: 1.5em inherit
}
.nav-item a {
padding: 1.5em
}
Design and Prioritising Content
Start Small
Selectively apply CSS
More HTTP requests
<link rel="stylesheet" media="screen and (min-width:500px)" href="over500.css">
Fewer HTTP requests, larger files
@media screen and (min-width: 500px) {
body {}
}
Expensive method (should be avoid)
@import url("over500.css") only screen and (min-width: 500px);
Based on browser width
min-width: apply when larger than the defined value
max-width: apply when smaller than the defined value
Based on devide width
min-device-width:
max-device-width:
Breakpoints
Content Based Breakpoint Selection: Add a breakpoint when the content start to look weird
Complex Media Queries
Apply when viewpoint is in between 300px and 600px
@media screen and (min-width: 300px) and (max-width: 600px) {
...
}
Grids
- Bootstrap
Flexbox
- display:flex define flex container
- flex-wrap:wrap define whether flex container wrap inside items
- order:n use with flex item for flex order inside a flex container
- width use with order to achieve a full-width layout with multiple element
Responsive Pattern
Column Drop: (Single Column)Stack elements -> Multiple Columns
Mostly Fluid:
- More grid-like, More Columns fit in different way
- Margin in widest viewport
flex-container {
width: 700px;
margin-left: auto;
margin-right: auto;
}
Layout Shifter:
- Most responsive
- Require more planning
- Use the flex item order attribute
Off Canvas
- Place content off screen
- Only display if screen large enough
For example, navigation menu
nav {
width: 300px;
background-color: #ffffff;
z-index: 10;
position: absolute;
/* This trasform moves the drawer off canvas. */
-webkit-transform: translate(-300px, 0);
transform: translate(-300px, 0);
/* Optionally, we animate the drawer. */
transition: transform 0.3s ease;
}
nav.open {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
Javescript to toggle the menu
var menu = document.querySelector('#menu');
var main = document.querySelector('main');
var drawer = document.querySelector('.nav');
menu.addEventListener('click', function(e) {
drawer.classList.toggle('open');
e.stopPropagation();
});
main.addEventListener('click', function() {
drawer.classList.remove('open');
});
Responsive Table
- hide columns
- position header off screen
- pull data from content
- content: attr(data-th);
td {
position: relative;
padding-left: 50%;
}
td:before {
position: absolute;
left: 6px;
content: attr(data-th);
font-weight: bold;
}
Tontained Scrolling Table
div {
width: 100%;
overflow-x: auto;
}
Vertical Tables (When Applicable)
- disable table layout with display:block
- hide table header thead by push it off screen so that it can still be read
- place header in front of each data with custom data attribute
- bold to highlight 'headers'
@media screen and (max-width: 500px) {
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
td {
position: relative;
padding-left: 50%;
}
td:before {
position: absolute;
left: 6px;
content: attr(data-th);
font-weight: bold;
}
td:first-of-type {
font-weight: bold;
}
}
Fonts
Base font
font-size: 16px
line-height: 1.2em
Ideal Measure of Lines
- 45-90 cpl
- 65 for the web
Minor Breakpoints
Long Text Ellipsis
Reponsive Images
- max-width:100%;
50% width with 10px space in between
img {
margin-right: 10px;
max-width: 426px;
width: calc((100% - 10px)/2);
}
img:last-of-type {
margin-right: 0;
}
calc((100% - 20px)/3);
- A space is required around + and - operators
- with * and /, spaces are not required
- the problem is an ambiguity around negation
Landscape and Portrait
- responsively cover the whole height of the viewport: 100vh => 100% height
- 100vw => 100% width
- 100vmin: 100% shorter side of the viewport
- 100vmax: 100% longer side of the viewport
Raster and Vector
- PNG v SVG
Automation Tools
- Grunt: Task Runner
- ImageMagick: Image Processing Tool
Text & Images
- Font over images
- Avoid using images but a decent layout and font
- Use Images sparingly
CSS Background Techniques
- background:cover
- CSS gradient
- CSS elaborate
- Conditional image display
@media screen and (max-width: 500px)
div.photo {
background-image: none;
height: 0;
margin: 0;
width: 0;
}
div.photo {
background-size: cover;
float: left;
margin: 0 2vw 1vw 0;
height: 50vw;
position: relative;
top: 3px;
transition: all 0.5s;
width: 50vw;
}
- Alternative images
@media screen and (max-width: 500px)
div {
background-image: url(koala_crop.jpg);
}
div {
background-image: url(koala.jpg);
background-size: cover;
height: 50vw;
transition: background-image 2s;
width: 50vw;
}
- image-set(): ideal for icon display
div {
background-image: url(icon1x.png);
background-image: -webkit-image-set(url(icon1x.png) 1x, url(icon2x.png) 2x);
background-image: image-set(url(icon1x.png) 1x, url(icon2x.png) 2x);
height: 128px;
width: 128px;
}
Contain & Cover
- background-size:contain => shorter dimension of the background image might be shorter than the container
- background-size:cover => longer dimention may be covered by the container
Unicode to replace image
<meta charset="utf-8" >
ICON Fonts
Inline SVG & Data URI
- Data URI (to inline images, less HTTP request)
- SVG Optimiser
- SVG Animation
srcset
- assume images display full viewport width
- 1x & 2x: Pixel Density Descriptor
- w: Tells browser about image width dimensions
size Attribute: indicate display size of an image
- sizes="50vw"
- sizes="(max-width: 250px) 100vw, 50vw"
picture & source element
- picture: container for sources
- source: alternative image sources
- img: fallback but compulsory, the actual element display the images
- WebP
<picture>
<source srcset="kittens.webp" type="image/webp">
<source srcset="kittens.jpeg" type="image/jpeg">
<img src="kittens.jpg" alt="">
</picture>
- source with media attribute
<picture>
<source media="(min-width: 1000px)" srcset="kookaburra_large_1x.jpg 1x, kookaburra_large_2x.jpg 2x">
<source media="(min-width: 500px)" srcset="kookaburra_medium_1x.jpg 1x, kookaburra_medium_2x.jpg 2x">
<img src="kookaburra_small.jpg" alt="The kookaburra: a terrestrial tree kingfisher native to Australia and New Guinea (according to Wikipedia)">
</picture>
Final Code
<figure>
<picture>
<source media="(min-width: 750px)" srcset="images/volt-1600_large_2x.jpg 2x, images/volt-800_large_1x.jpg" />
<source media="(min-width: 500px)" srcset="images/volt_medium.jpg" />
<img src="images/volt_small.jpg" alt="Volt sign in old Berlin power station">
</picture>
<figcaption>Sign in old Berlin power station</figcaption>
</figure>
Flexbox
Container
- display
- flex-direction: row | row-reverse | column | column-reverse;
- row ltr
- column top to bottom
- flex-wrap
- flex-flow: flex-direction flex-wrap
- justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly;
- align-items: flex-start | flex-end | center | baseline | stretch;
- align-content: flex-start | flex-end | center | space-between | space-around | stretch;
Items
- order:
; - flex-grow:
; - flex-shrink:
; - flex-basis:
| auto; - flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
- align-self: auto | flex-start | flex-end | center | baseline | stretch;
Autoprefixer
npm install grunt-cli grunt-contrib-watch grunt-autoprefixer
grunt
module.exports = function (grunt) {
grunt.initConfig({
autoprefixer: {
dist: {
files: {
'build/style.css': 'style.css'
}
}
},
watch: {
styles: {
files: ['style.css'],
tasks: ['autoprefixer']
}
}
});
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.loadNpmTasks('grunt-contrib-watch');
};
Media Queries
@media speech and (min-width: 30em) and (orientation: landscape) { ... }
@media (min-height: 680px), screen and (orientation: portrait) { ... }
Media types
- all
- screen
- speech
Media Features
- width&height
- aspect-ratio
- orientation
- resolution
- hover
- light-level
- monochrome
Logical Operations
- and combines multiple media features
- not [Media Type]
- only [Media Type]
- , combines multiple media queries ( || )
not: is evaluated last
@media not all and (monochrome) { ... }
is the same as
@media not (all and (monochrome)) { ... }
Media Query Level 4
@media (width <= 30em) { ... }
@media (30em <= width <= 50em ) { ... }
@media (not(hover)) { ... }
Audio and Video
Video
<video controls>
<source src="rabbit320.mp4" type="video/mp4">
<source src="rabbit320.webm" type="video/webm">
<p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
</video>
- width & height
- autoplay
- loop: re-play automatically
- muted
- poster: display before the video is played
- preload: none | auto | metadata
Audio
<audio controls>
<source src="viper.mp3" type="audio/mp3">
<source src="viper.ogg" type="audio/ogg">
<p>Your browser doesn't support HTML5 audio. Here is a <a href="viper.mp3">link to the audio</a> instead.</p>
</audio>
Video Text Tracks
- Subtitles
- Captions
- Timed descriptions
WEBVTT
Chapter 1
00:00:01.000 --> 00:00:02.000
Introduction to HTML5
Chapter 2
00:00:03.001 --> 00:00:04.000
Introduction to Chapter 2
...
<video controls>
<source src="example.mp4" type="video/mp4">
<source src="example.webm" type="video/webm">
<track kind="subtitles" src="subtitles_en.vtt" srclang="en">
</video>
srt to vtt
Touch Event
// [concurrent "touch AND mouse/keyboard" event binding](https://w3c.github.io/touch-events/#mouse-events)
// set up event listeners for touch
target.addEventListener('touchend', function(e) {
// prevent compatibility mouse events and click
e.preventDefault();
...
});
...
// set up event listeners for mouse/keyboard
target.addEventListener('click', ...);
...
- touchstart
- Zero or more touchmove
- touchend
- touchcancel
- mousemove
- mousedown
- mouseup
- click
TouchEvent: an event that occurs when the state of touches on the surface changes
Touch: a single point of contact
TouchList: a group of touches
Front End Networking
fetch() & Promises
fetch('./api/some.json')
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
Response Types
- basic: a resource on the same origin
- cors: a resource on another origin which returns the CORs headers
- restricts the headers you can view to
Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
, andPragma
.
- restricts the headers you can view to
- opaque: a request made for a resource on a different origin that doesn't return CORS headers
Defined fetch request mode
- same-origin: only same origin request succeeds
- cors: allow same origin and also other origins with CORs headers
- cors-with-forced-preflight: always perform a preflight check
- no-cors: make requests to other origins with no CORS headers in return (Not possible yet July 2, 2018)
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
.then(function(response) {
return response.text();
})
.then(function(text) {
console.log('Request successful', text);
})
.catch(function(error) {
log('Request failed', error)
});
Chaining Promises
benefit: share logic across fetch requests
function status(response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
function json(response) {
return response.json()
}
fetch('users.json')
.then(status)
.then(json)
.then(function(data) {
console.log('Request succeeded with JSON response', data);
}).catch(function(error) {
console.log('Request failed', error);
});
Post Request
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
})
.then(json)
.then(function (data) {
console.log('Request succeeded with JSON response', data);
})
.catch(function (error) {
console.log('Request failed', error);
});
Credential
fetch('https://example.com', {
credentials: 'include'
})
- 'same-origin': only send on the same origin
- 'omit': not to include credentials
Second Parameter, an init object
fetch(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
mode: "cors", // no-cors, cors, *same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json; charset=utf-8",
// "Content-Type": "application/x-www-form-urlencoded",
},
redirect: "follow", // manual, *follow, error
referrer: "no-referrer", // no-referrer, *client
body: JSON.stringify(data), // body data type must match "Content-Type" header
})
.then(response => response.json()); // parses response to JSON
Uploading JSON data
var url = 'https://example.com/profile';
var data = {username: 'example'};
fetch(url, {
method: 'POST', // or 'PUT'
body: JSON.stringify(data), // data can be `string` or {object}!
headers:{
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
Uploading a file
var formData = new FormData();
var fileField = document.querySelector("input[type='file']");
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', JSON.stringify(response)));
Multiple Files
var formData = new FormData();
var photos = document.querySelector("input[type='file'][multiple]");
formData.append('title', 'My Vegas Vacation');
formData.append('photos', photos.files);
fetch('https://example.com/posts', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
Request Object
var myHeaders = new Headers();
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg', myInit);
fetch(myRequest).then(function(response) {
return response.blob();
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
Headers interface
- Content-Type
- Content-Length
- X-Custom-Header
- Set-Cookie
- Origin
Check Content Type
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); });
Guard
- none
- request => may not set 'Content-Length'
- request-no-cors: for request created with Request.mode=no-cors
- response => not allow to insert 'Set-Cookie'
- immutable
Response Objects
body
- Blob
- BufferSource: a typedef
- FormData
- ReadableStream: a readable stream of byte data
- URLSearchParams
- USVString: the set of all possible sequences of unicode scalar values
Common Properties
- Response.status: An integer(default 200)
- Response.statusText: A string(default 'OK')
- Response.ok: Boolean (check if status is between 200-299)
Body: an instance of any of the following types
- ArrayBuffer: represent a generic, fixed-length raw binary data buffer
- ArrayBUfferView: representing any of a group of JavaScript TypedArray
- Blob/File: a file-like object of immutable, raw data
- string
- URLSearchParams: defines utility methods to work with the query string of a URL
- FormData: a way to easily construct a set of key/value pairs representing form fields and their values
Extract Methods
- arrayBuffer()
- blob()
- json()
- text()
- formData()
Feature Detection
if (self.fetch) {
// run my fetch request here
} else {
// do something with XMLHttpRequest?
}
polyfill
Stream
Cross-Origin Resource Sharing (CORS)
Accessibility
Web Fundamentals – Accessibility
Web Content Accessibility
- Preceivable
- Operable
- Understadable
- Robust
Focus
- Built-in interactive HTML elements are implicity focusable
- automatically inserted into the tab order
- have built-in keyboard event handling
Semantics
- Assistive technology
- Affordances
- Screen readers
- The element's role or type, if it is specified (it should be).
- The element's name, if it has one (it should).
- The element's value, if it has one (it may or may not).
- The element's state, e.g., whether it is enabled or disabled (if applicable).
Styling
- styles for focus and various ARIA states
- Styling focus
-Input modality: show button when input by keyboard/* At a minimum you can add a focus style that matches your hover style */ :hover, :focus { background: #c0ffee; }
fake-button { display: inline-block; padding: 10px; border: 1px solid black; cursor: pointer; user-select: none; } fake-button:focus { outline: none; background: pink; }
- Styling states with ARIA
.toggle[aria-pressed="true"] { ... }
- can be zoomed or scaled to accommodate users
- Multi-device responsive design
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- A minimum recommended touch target size is around 48 device independent pixels
- Multi-device responsive design
- Choosing the right colors and contrast
- A (minimum) contrast ratio of 4.5:1
- Color Contrast Checker