Your content should be designed to work on any device because it will be viewed on every device, everywhere. The website we’re building today with the goal of displaying it on a desktop, smartphone, and tablet may be viewed on a 52-inch TV screen or 3 × 5-inch GPS LCD screen. By starting with a flexible foundation, your site should be able to grow or shrink gracefully no matter the hardware that loads it.
For your website to adapt to any screen size, you want to make it as flexible as possible. Using percents and rems, instead of pixels for widths and font size, will bring you 90% of the way there. Add in some media queries, and you’re 95% of the way there.
There are several CSS features, other than media queries and other CSS3 features we’ve covered so far, that are helpful in developing responsive websites and will bring you up to the 99% mark. Why 99% and not 100%? There is always more you can do to make a site more responsive, more accessible, prettier, faster, etc., but at some point, you have to say “this is good enough” or “this book is way too long.”
I mentioned this before, but it bears repeating: don’t create layouts for specific phone sizes. Rather, slowly expand (or shrink) your site in a browser. When the layout starts looking less than optimal, that is where you should alter your design for the next set of devices. You may need eight layouts for tiny, xx-small, small, medium, large, x-large, xx-large, and huge screens, or you may have a single layout that works well across all devices. You won’t know until you view the layout in varying sizes, but do view your layout in a plethora of sizes to make sure your layout works well everywhere.
As you change the size of your browser and decide that a new layout is needed, the width at which the layout changes is called a breakpoint. Don’t just select 320, 480, 640, and 960 as breakpoints because that is what everyone else is doing. Instead, do what makes sense for your site.
When you determine that you need a breakpoint, use media queries to target a span of viewport sizes with a specific layout. You also don’t have to choose a single breakpoint. You can alter the layout of your header, footer, navigation, and main content at different breakpoints if that makes the most sense. There is no right or wrong, there is just better and not as good.
Once you’ve determined where the breakpoints are for your design or for the usability of your application, you can target the layout changes and feature highlighting with media queries. You can also target high pixel-density displays with larger images, remembering that larger images mean larger file sizes. You can use JavaScript with media queries to send high DPI images only to high pixel-density displays with good bandwidth, and perhaps one day we’ll be able to match media queries to bandwidth—but we’re not there yet.
We covered media queries in Chapters 2 and 7, so you should already understand the syntax.
The CSS columns
property
enables us to create multiple columns for laying out text
like a newspaper. The columns
property
is shorthand for column-count
and
column-width
.
The column-width
property
is the optimal column width, as if you were declaring a
min-column-width
, which is not an actual property. The column-count
is the maximum number of columns, as if you were declaring max-column-count
, which is also not an actual
property.
The column-count
property has
precedence. The browser adjusts the width of the column around that
suggested column-width
, providing for
up to as many columns as listed as the integer value of column-count
, as long as each column is at least
as wide as the length value provided as the value for column-width
. Columns allow for scalable designs
that fit different screen widths.
We define the gap between columns with the column-gap
property, and whether to include a
dividing line between columns by declaring a line with the column-rule
rule
property. The column-gap
property
takes as its value a length, with the default value being
the key term normal
, which is 1em
in most browsers.
Using columns will make your very wide areas of content more legible. And, as viewport sizes narrow, the number of columns will shrink. While your 24-inch monitor may see six columns, your HTC One in portrait mode will only have one column if the following style is set:
columns: 240px 6;
The preceding line reads “divide the element’s content into a maximum of six columns, ensuring that no column is narrower than 240 px wide.” The iPhone is 320 px wide, so you can’t fit two columns in portrait mode, but you could in landscape if there was no padding or gap. A 1920 × 1080 display could fit eight columns, with no column gap. However, the browser will not render more than six columns, even if more columns of the declared width could fit in the space provided.
Similar to the border
shorthand,
the column-rule
is shorthand for
the column-rule-width
,
column-rule-style
, and column-rule-color
properties, and takes the same
values as the border shorthand, too. As long as the column-rule-width
is narrower than the column-gap
, the rule will show. These column
properties are animatable, with the exception of column-rule-style
:
p { margin: 0 0 1em; } div { padding: 1em; margin: 1em; border: 2px solid #ccc; columns: 240px 6; column-gap: 2em; column-rule: 2px dashed #ccc; }
This code snippet[76] will create a multicolumn layout in a large device, and display in a single column on a device (or parent) narrower than 480 px + 4 ems, as shown in Figure 11-1, with examples in the online chapter resources.
An interesting note in the preceding code is the margin set on the paragraphs: one of the reasons developers have been reluctant to use columns in the past is because of the way the columns sometimes leave gaps at the top or bottom of a column. Uneven bottoms can be OK: you want to make sure your gap is not at the top of a column. If a node with a margin or padding top starts a new column, there will be a gap at the top of that column. To ensure no gaps at the top of columns, set padding and margins on the bottom of paragraps and other children of your columns.
The column-span
property enables
elements to span across all columns when its value is set to all
. If an element has column-span: all;
set on it, the content above
it will be divided among all the columns equally, that element will then
cut across the entire parent, and the subsequent content will again be
rendered in columns.
By default, all columns will be set to approximately the same
height, divided equally across all columns. By setting the height of the
parent, and the column-fill
property
to fill
, (rather than the balance
default), the columns will be filled
sequentially. While the other column properties are well supported,
column-span
and column-fill
are not.
To set an exact column width, the calculation needs to include the
width
, column-width
, column-gap
, and column-rule-width
properties.
To effectively use columns in a responsive layout, make sure that the parent of the columns is a fluid width. Declare the maximum number of columns you would want displayed in a wide screen and the minimum width you would want to see displayed in any screen, and your content will be responsive.
While no media queries are necessary for this to work, shrink and grow your browser window to the smallest and widest widths possible to assess whether media query break points makes sense.
border-image
allows for a single image to be used to create decorative borders
on any element no matter the size or aspect ratio of that element. We can
create decorative borders for elements, beyond simple rounded corners,
with a single, very small image file size or even with gradients.
The border-image
property
virtually slices an image into nine sections, putting the corners of that
image in the corners of your element, with the width of your left and
right borders and the height of your top and bottom borders, and either
repeating or stretching the noncorner components to cover your element.
You can take a single, relatively small image and stretch it across a
small button or a whole page.
Figure 11-2 shows
three border images, and how one would slice them up in an image-editing
program before browsers supported the border-image
shorthand property, and how we tell
the browser to virtually slice up the image now.
Native iOS apps have buttons, like the tiny button shown in the
center of Figure 11-2. We
created those buttons earlier with gradients. We could have created that
button look with the sliding door method or the several other hacks we
used to make buttons last decade. Or, we could use a single small image
for every button, whether that button is 10 × 10 px or 200 × 300 px, by
using CSS border-image
.
Figure 11-3 shows three examples of elements with a border image set using the three small images from Figure 11-2. Let’s learn how to do it!
We’ve used the images from Figure 11-2 as the border images for the elements in Figure 11-3, maintaining the corners while repeating (in the case of the stamp) or stretching the middle section of the border image to cover the entire element.
In the stamp example, we’ve repeated the middle slices (T, R, B, and L) to create the outline of a stamp. To ensure that the image is not broken, the width and height should be multiples of the slice’s width (T and B) and height (R and L). While we’ve repeated the top, bottom, and sides, we’ve maintained the four corners (listed as 1, 2, 3, and 4), creating a stamp-like effect.
The border-image is a shorthand property used to declare border-image-source
, border-image-slice
, border-image-width
, border-image-outset
, and border-image-repeat
.
The syntax for the shorthand is:
-prefix-border-image: <source> <slice {1,4}> / <width {1,4}> <outset> <repeat{1,2}>;
Browsers that support border images only support the border-image
shorthand property, rather than the
separate properties that make up the shorthand. We’ll cover the various
properties that make up the shorthand border-image
property, but I recommend using the
shorthand instead of the longhand properties described next.
Note that the current syntax has changed several times since the first implementation. If you’re reading a blog post on the topic, make sure it’s using the current syntax.
Border images don’t work if there is no border. The first step is to declare a border for our
elements. As we know from Chapter 9, border-style
is the only required property:
.button { border: solid; } .stamp { border: solid; } .arrow { border: solid; }
If we do not include a border-style
with a value other than none
or hidden
the border image will fail to
display.
The border-image-source
is
the URL, gradient, or data URI of the image you want to use
as your border image. In the Figure 11-3 examples, while
the longhand property is not yet fully supported,
it is as if we had used border-image-source:
url(stamp.gif)
, but instead we start our three border-image
shorthand property declarations
with:
.button { border: solid; border-image: url(button_bi.png) ... } .stamp { border: solid; border-image: url(stamp.png) ... } .arrow { border: solid; border-image: url(arrow.png) ... }
Just as we can include gradients, base-64, GIF, JPEG, PNG, and even SVG images as background images, you can include all these image types as border images.
The border-image-slice
property defines from one to four lengths that set the distance
from each edge of the image marking the area that will be used to cut
or slice up our border image, as shown in Figure 11-2. The border-image-slice
also defines whether the
middle part of the border-image
,
labeled M in Figure 11-3, is discarded or
fills the background of the element.
The border-image-slice
property values represent inward offsets from the top, right, bottom,
and left (TRouBLe) edges of the image, respectively. You define four
imaginary lines that the browser then uses to divide the one border
image into nine regions: four corners, four edges, and a middle, as
demonstrated in Figure 11-3. The four
corners are placed in their respective corners, scaled to fit the
space allotted to them by the border width properties. The four sides
are stretched or repeated or a combo of the two (round), depending on
the values of the other border-image
properties.
In addition to the four values, the unprefixed version of the
border-image-slice
property takes
the optional value of fill
to
preserve the middle part of the border image. If the key term fill
is not present, the middle part of the
border image file is discarded. Whether that middle component is
stretched, repeated, or rounded depends on the value of the border-image-repeat
property described .
In our examples, we’ve sliced the image 5 px in from each side
for our button; 9 px for the stamp; and 0 px from the top and bottom,
5 px from the left, and 20 px from the right of our arrow. We want the
middle section of the image to show for the arrow and button, but not
for the stamp. If we were writing shorthand, we would have written
border-image-slice: 5px fill;
,
border-image-slice: 9px;
, and
border-image-slice: 0 5px 0 10px
fill;
respectively. Instead, we include them in the
shorthand property with no length units, and the fill
for the button and arrow:
.button { border: solid; border-image: url(button_bi.png) 5 fill... } .stamp { border: solid; border-image: url(stamp.png) 9 ... } .arrow { border: solid; border-image: url(arrow.png) 0 5 0 20 fill... }
Note we’ve used no length units. If you are setting the slice values in length, and the value will be interpreted as pixels, omit the units. If you are using percentage values, include the percent.
The border-image-width
property sets the width of the element’s border. If the border-image-width
property is declared as
part of the border-image
shorthand,
it takes precedence over the border-width
property. If omitted and the
border-width
is omitted, the width
of the borders will be 3 px in most browsers, the value of which is
medium
, the default value of the
border-width
property.
Since there are quirks with the value of auto
, it is often recommended to include
border-width
as a separate property
or part of the border
shorthand, rather than part
of the border-image
shorthand:
.button { border: solid 5px; border-image: url(button_bi.png) 5 fill... } .stamp { border: solid 9px; border-image: url(stamp.png) 9 / 9px ... } .arrow { border: solid; border-width: 0 5px 0 20px; border-image: url(arrow.png) 0 5 0 20 fill / 0 5px 0 20px... }
The four corners, labeled 1, 2, 3, and 4 in Figure 11-3, will be the
width of the left and right borders and the height of the top and
bottom borders. Having the border-image-width
the same width as the
border-image-slice
will create the
best-looking border image with no distortion. But they don’t need to
have the same values. The slice will be stretched (or shrunk) to the
width of the border-image-width
if
the values are not the same.
We add a slash between the border-image-slice
values and the border-image-width
values. In the unprefixed
version, we rely on border-width
—a
positive, nonpercentage length unit—to define the width of our
borders.
Remember the box model! border-width
is part of the box model and
will affect these elements. As you increase the border-image-width
, your element will grow
larger, unless prevented from doing so with box-sizing: border-box;
.
The border-image-outset
property specifies the amount by which the border image area
extends beyond the border box on all four sides. The default value is
0.
Because the stamp is a transparent PNG, and we have not filled
it, if we added a background-color
,
the color would show through the middle and through the transparent
parts of the border. There are two ways to resolve this border issue:
background-clip: padding-box;
or by
putting the border image outside the box with the border-image-outset
property. The former
does not alter the size of the box. The latter does, similar to the
box-shadow
; it makes the element
appear larger, but does not impact the box model:
.stamp { border: solid 9px; background-color: #dedeef; border-image: url(stamp.png) 9 / 9px / 12px ... }
Now if you’ve been playing along, testing each line of code, at this point the button and
arrow are looking good, but the stamp not so much. The top, right,
bottom, and left slices stretch by default, with a single slice
spreading across the entire width or height of the element. That looks
fine for our arrow and button, and is in fact what we need the arrow
and button to do, but it looks crappy for the stamp. We want the stamp
side slices to be repeated not stretched. For this we have the
border-image-repeat
property.
The border-image-repeat
property allows you to delineate how noncorner images (the sides and
middle) are repeated and/or scaled in TRouBLe order. The
specifications define four possible values, but only two are well
supported. stretch
means that the
image should not be tiled, but rather stretched to fill the area.
repeat
means the image is tiled (or
repeated) to fill the area.
If the area allocated for the repeating image is not exactly
divisible by the width of the image, the last tiled image may be cut
off. With round
the image should be
tiled (repeated) to fill the area, with the image being scaled down,
possibly losing its aspect ratio, but ensuring that the image is never
cropped. The unsupported space
value was supposed to repeat the slice as many times as can fully fit
in the area provided, with the tiles evenly spaced, showing whitespace
between the tiles if the width provided is not an exact multiple of
the image size. This value, however, has been (temporarily?) removed
from the specifications.
In our examples, we used stretch
for the button and round
for the stamp. You will always want to
stretch
gradients, as repeating
them creates harsh lines where one tile ends and the next begins. And
while it may seem to make sense to use repeat
for the stamp, we have no way of
knowing if the image is evenly divisible by the width of our design.
The round
distorts the image ever
so slightly, but that is better than having the image cut off.
Since round
isn’t fully
supported, repeat
is the
fallback.[77]
The arrow is an interesting case. We definitely don’t want to repeat it. We can stretch it, but only slightly before the image becomes distorted. Because of the shape, we set the top border and bottom slices to zero height so that if we do end up stretching the arrow part, it doesn’t lose its shape:
.button { border: solid 5px; border-image: url(button_bi.png) stretch 5 fill; } .stamp { border: solid 9px; background-color: #dedeef; border-image: url(stamp.png) round 9 / 9px / 12px; } .arrow { border: solid; border-width: 0 5px 0 20px; border-image: url(arrow.png) stretch 0 5 0 20 fill / 0 5px 0 20px; }
We don’t have to declare stretch
on the arrow or button, since it is
the default value.
Notice the last code example has semicolons instead of ellipses.
That completes the various properties that make up the border-image
shorthand property. However,
what we have won’t work in alld browsers. We include prefixing for
mobile WebKit through Android 4.2 and iOS 5.1 and Opera through 12.1.
border-image
started being
supported in IE (with IE11) with no prefix:
.stamp { background-color: #ccc; border: solid 9px transparent; -webkit-border-image: url(stamp.png) 9 / 9px / 12px round; -o-border-image: url(stamp.png) 9 round; border-image: url(stamp.png) round 9 / 9px / 12px; } .button { border: solid 5px transparent; -webkit-border-image: url(button.png) 5; -o-border-image: url(button.png) 5; border-image: url(button.png) 5 fill; } .arrow { border: solid transparent; border-width: 1px 5px 1px 20px; -webkit-border-image: url(arrow.png) 1 5 1 20 / 0 5px 0 20px stretch; -o-border-image: url(arrow.png) 1 5 1 20 / 0 5px 0 20px stretch; border-image: url(arrow.png) stretch 0 5 0 20 fill / 0 5px 0 20px; }
At this point, you hopefully have a good understanding of how to create a border image. There are a few tools to help you along. There are links to these tools and the demo of our button, arrow, and stamp in the online chapter resources.
Modern browsers are now supporting what is expected to be the final syntax of the flexbox layout mode, but all mobile browsers are supporting some version of flexbox, so it is worth mentioning—especially since flexbox enables developers to easily create flexible multicolumn layouts (as shown in Figure 11-4).
The flexbox layout mode provides flexibility in laying out web pages. The children of a flexed container can be laid out horizontally, vertically, in source order or not. The children can “flex” their width and height and avoid expanding beyond the size of their parent or empty space.
To work, CSS gives us a few new properties that may still be in
flux. The current new flexbox properties include the ordering and
orientation of flex-direction
, flex-wrap
, flex-flow
, and order
properties, the flexibility properties of
flex-grow
, flex-shrink
, flex-basis
, and the flex
shorthand, the alignment properties of
justify-content
, align-items
, align-self
, and align-content
properties—as well as new values
for the display
property.
The flexbox specifications add two values to the display
property:
flex
and inline-flex
.
Apply the flex
or inline-flex
(-ms-flexbox
in IE10) values to the display
property of the parent of the children
you want to position.[78] Flex’s default creates even columns out of the flexed item’s
children. The additional properties allow us to reverse the order, wrap,
change the order, create centered rows instead of columns, etc., all
without touching the underlying HTML content. By allowing CSS to provide
flexibility in the layout, in conjunction with media queries, we can send
different layouts of the same content to different viewport
configurations.
The various layouts shown in Figure 11-4 were all based on the same HTML:
<article> <div>A</div> <div>B</div> <div>C</div> </article>
So, how did we change the layout without touching the markup? Well, it wasn’t easy. We’re still dealing with various syntaxes in different browsers:
article { display: -webkit-box; display: -moz-box; display: -webkit-flex; display: -moz-flex; display: -ms-flex; display: flex; }
The preceding code is basic, creating columns out of the flex’s
children. If you look at the online chapter
resources, you’ll notice that the divs are now flowing
horizontally, as if we had floated them left, except that no matter how
much content you add to each nested <div>
, they will all be the same
height.
Unfortunately, we are still supporting diverse specifications with
and without prefixes. Because display
is an old property, when adding vendor prefixes, the prefix is on the
value, not the property: we included -webkit-box
and -moz-box
for older WebKit and Firefox through
v17. We then include the prefixed candidate recommendation for Chrome and
BB10, IE10, and Firefox 17-19. At the time of this writing, FF 20+, Opera,
IE11 beta, and Opera Mobile are prefix-free. IE10 supports the February
2012 “tweener” syntax, which is a bit different from the candidate
specification supported by the other browsers and IE11 beta.
The preceding code only created columns. We could have created rows. We could have declared even columns. We could have reverse ordered the presentation of those columns. This can all be done with the other flexbox properties.
Note: Absolutely positioned children of a flexbox cannot be a flexbox item, as absolutely positioned elements are taken out of the document flow.
Browsers have implemented the flexible box layout module as the specification evolved. Because of this, different browsers have implemented different syntaxes. The rest of this section uses only the current spec syntax, which may or may not be current when you read this. I am including flexbox even though it is in a state of flux because, mixed with media queries, flexbox is super powerful for mobile development. And even though the syntax I am including only works in beta versions of browsers (IE11, Chrome 29+) and Opera Mobile with Presto (Opera 12.1), the general idea of how it works will not change. Check out the online chapter resources for the more up-to-date property and value syntaxes.
To align the flexed children vertically instead of horizontally, we
could employ the flex-direction
property that specifies the direction of the flexbox layout. Since we
omitted the property, it defaulted to the value row
, creating a row out of the children. Other
options include row-reverse
, column
, and column-reverse
.
By default, the flex container is a single line. You can explicitly
set the flex-wrap
property to
nowrap
to keep that default single-line
layout, or set it to wrap
or wrap-reverse
to allow for a multiline
layout.
The flex-flow
property is shorthand for flex-direction
and flex-wrap
properties, which combined define the
axis of the flexbox’s layout.
If you want to change the display order of the flexed items, the
order
property can be used. The
order
value is set on the child flexbox
elements, not the flexbox parent. To reverse the order, we can use
flex-direction: row-reverse;
:
article { display: flex; flex-direction: row-reverse; }
To relocate a single child, apply order:
−1;
or otherwise the lowest value, to make the child element on
which it is applied come first, or order:
1;
, or whatever is the greatest value among the siblings, to
make it last:
article { display: flex; } div:nth-of-type(2) { order: −1; }
If you have an <article>
with three <div>
s (A, B, and C),
all three columns will be in one row, with B appearing first to sighted
users as if the order were B-A-C. Had we set:
div:nth-of-type(2) { order: −1; }
the order would have appeared to be A-C-B.
The flex
property defines
the flex-grow
, flex-shrink
, and flex-basis
features. Use the shorthand
flex
instead of the three longhand
properties.
Flexing is the ability of the container to alter its width or
height to fill the available space, allowing us to set sizes for our
elements. The flex
property is
applied on flexbox children, not on the flexbox parent. When set on the
flexbox children, the browser sets the size of the elements on which
flex
is declared on a per line basis,
evenly distributing the remaining free space on the elements that don’t
have flex
set.
The flex
property can take up
to three values. The flex-grow
components determines how much the flex item will grow relative to the
siblings within the flexbox parent when free space is distributed.
Similarly (or oppositely, but that’s not a word), the flex-shrink
factor determines how much the
element will shrink relative to its siblings when negative free space is
distributed. The flex-basis
takes the
same values as the width
property,
specifies the initial main size of the item, before free space is
distributed according to the flex-shrink
and flex-grow
. The default is flex: 1 1 0;
.
If we want A to be twice as wide as B, and B to be twice as wide as C, we could use this (see the online chapter resources):
div:nth-of-type(1) { flex: 4; } div:nth-of-type(2) { flex: 2; } div:nth-of-type(3) { flex: 1; }
When designing for different viewport sizes, using the flexbox layout properties in conjunction with media queries can ease the development process. For a wide screen, you may want to have three columns across the page, putting the aside on the left, the main content in the middle, and the footer on the right, in one row across the page. For a small phone, without touching the page content, you could put the main content on top, followed by the contents of the aside and the footer on the bottom. Semantically, I would put the content first. Basically, develop mobile first:
<article> <div>Main Content in center in wide screen, first in narrow screen</div> <aside>Left side in wide screen, after content in narrow screen</aside> <footer>Right side in wide screen, button in narrow screen</footer> </article> @media screen and (min-width: 600px) { article { display: flex; flex-direction: row; } article > * { flex: 1; } aside { order: −1; } article > div { flex: 3; } }
Note that the order is set on the <aside>
, making it appear first, as seen in the example in the online
chapter resources. All three siblings get flex: 1;
, which we overwrite with flex: 3
on the main content. This means the
article will be split 20%/60%/20% for aside/div/footer. Note that
display: flex;
is on the parent, and
the other properties (other than flex-direction
) are on the children.
In this case, we don’t have to declare a separate layout for
smaller sized screens since the default browser layout looks pretty much
the same as flex-direction: column
.
However, we could have declared:
@media screen and (max-width: 600px) { article { display: flex; flex-direction: column; } }
Remember to always view your layout in small and large formats, creating media query breakpoint layout changes where appropriate or necessary.
While not yet supported in the mobile space, and only at the candidate
recommendation level at the W3C, @supports
is already supported in Firefox and
Opera. The @supports
at-rule will be
helpful in enabling us to create separate layouts for browsers that
support flexbox, and a different layout for those that don’t, all
without resorting to hacks.
When flexbox is supported, it will be a long while before all of
your users’ devices support flexbox. We will surely be seeing other new
CSS features that, like flexbox, use new values for supported CSS
properties—like display: flex;
.
@supports
is similar to @media
, but instead of matching browsers based
on browser and device metrics, it will match based on browser CSS
support:
@supports (display: flex) and (background-color: red) { h1 {color: green;} }
This will make all <h1>
s
green in browsers that support both display:
flex;
and background-color:
red;
. It will not make any background colors red. It just
tests for support of properties and property values.
In the interim, some browsers have added some feature detection
through the @media
rules:
@media screen and (-webkit-transform-3d) { h1 { -webkit-transform: translateZ(0) rotate(5deg); -webkit-animation: makemedizzy 1s infinite; } }
The preceding code matches all WebKit devices that support 3D
transforms, rotating and animating the <h1>
s within the document. The feature
detection component of this media query is prefixed. This media query
will match WebKit browsers that still support the vendor prefixing for
the CSS transform property.[79] To match browsers that no longer need prefixing, use the
following:
@media screen and (transform-3d) { h1 { transform: translateZ(0) rotate(5deg); animation: makemedizzy 1s infinite; } }
Only browsers that support transform-3d
will understand this media
query.
You can also use the @media
query to feature detect support for animation and transitions, with and
without prefixes. This feature detection will eventually be replaced by
@supports
, described earlier. When
implemented, @supports
will support
all properties and values. @media
is
limited to only three properties, and does not discern between property
values.
So, why is @supports
exciting,
instead of just allowing browsers to ignore features they don’t support?
@supports
will allow you, for
example, to lay out a site using flexbox, if supported, and columns if
flexbox is not supported, without inadvertently sending columns to your
flexed layout.
The flexible layout lets you easily create fluid layouts. Unfortunately, flexbox properties are not fully supported on all browsers. CSS 2.1 did provide all the tools for creating fluid layouts: flexbox just makes it easier.
Until flexbox is fully supported on the overwhelming majority of devices, it will still be easier to work with percentages instead of pixels to create layouts that adapt to your screen size. Creating fluid layouts seems more difficult once you introduce fixed-width elements, but there are a few tricks that simplify what may seem like a challenge.
A common example of needing a flexible image is the header image: you want it to take up the whole width of the screen, no matter the device size, without zooming the page in, making the text illegible on smaller devices. You need your image to be 100% of the width whether you have a 440 px screen or a 640 px screen. The solution is so simple it is actually delineated in the preceding sentence:
header img { max-width: 100%; height: auto; }
You can declare fixed-width media, like images and video, to be of
any width relative to the width of their parent container. In the
preceding case, instead of displaying in the image’s default width
(width: auto;
) it will not grow bigger
than its parent. Because we defined max-width
instead of width
, it will stop growing once it reaches the
media’s actual width.
If you don’t mind showing a low-resolution image, you can use
min-width: 100%
or simply width: 100%;
. Unless you are stretching a
gradient or other stretchable image, do not declare an actual value for
the height, as it will likely distort the aspect ratio of the image. That
is why we include height: auto;
.
Growing and shrinking images is not the panacea that solves all our mobile image issues. Mobile devices tend to be very limited when it comes to memory. Yet, we have high DPI devices that look really crisp when we serve them larger images—which use up more bandwidth and more memory. Because of limited memory, latency, and different device resolutions, serving images in our current mobile landscape is no longer cut and dried like it was when we only worried about desktop.
The iPhone 4, released in 2009, was the first device with a “Retina Display” of 326 dots per inch (DPI). The third-generation iPad, released in 2012, has double the previous version’s resolution with 264 DPI. The first laptop with high DPI was the Retina Display MacBook.
The original iPhone is 320 × 480 px and the original iPad was 768 × 1024 px. The first Retina versions of these devices were 640 × 960 px and 1536 × 2048 px, respectively. The size of the screens remained the same, but four pixels were displayed in the area that used to require a single pixel, creating a better resolution, denser screen.
A Retina Display is a high-definition display. I have capitalized Retina Display as it is a trademark from Apple meaning twice the resolution. It doesn’t actually mean a specific DPI value. Nor is high resolution limited to Apple devices. In fact, there are devices currently on the market with higher resolution than the iPhone, but their manufacturers have to come up with nontrademarked descriptors. The correct term is the nontrademarked “high resolution.”
With the release of the iPhone 4, web developers had to handle Retina Displays, a.k.a. high-resolution displays.
A device pixel is the smallest point of color displayed by a device, which is not exactly the same as a CSS pixel. Understanding the difference may help make things less confusing.
The pixel density is the number, or ratio, of device pixels per CSS pixel. A device may be able to display more than one (or less than one) device pixel in a CSS pixel. The resolution, on the other hand, is the product of the width and height of the device, in pixels.
The density per inch, or DPI, is the density display. The DPI is the quotient of the pixels displayed by the size in inches by the device. For example, a 4-inch wide device displaying 800 px is:
800 pixels ÷ 4 inches = 200px per inch
A device that has a DPI of 200 pixels per inch or greater is considered high DPI. If we take the iPad as an example, where the standard device was originally 768 × 1024 px, and the high DPI standard size version (not the mini) was 1536 × 2048 px, even though they were both the same size at 7.75 inches tall,[80] they are 132 DPI and 264 DPI, respectively:
1024 pixels ÷ 7.75 inches = 132 pixels per inch 2048 pixels ÷ 7.75 inches = 264 pixels per inch
The higher the DPI, the smaller the device pixel, which allows for higher quality images. “Higher quality” images are generally just larger images. Continuing with our example, the newer iPad has double the pixels and therefore benefits from images with four times the pixels to display the same image as the original iPad.
Images made with image-editing software, the JPEGs and PNGs and GIFs, sent over the wires, all have the same resolution of 72 px per inch. A low-resolution image displayed on a high-resolution screen can seem blurry.
To fill the background of a full browser, we set the height and width of the image in the foreground or background to be the original, low DPI dimensions, but we can serve images four times the size (twice as tall and twice as wide leads to images that are four times the original size) to make them appear beautifully crisp in high resolution devices.
This causes a few issues: you don’t need to send high DPI images to low DPI devices, as larger images use more bandwidth and the larger the file size the more memory the file consumes—and mobile devices are notoriously limited in terms of memory.
While we have no current API to determining how much memory is left, we can determine the bandwidth. You may choose to only send high DPI images only to users on a fast network with a high resolution display that can make use of larger images using a combination of JavaScript and CSS. Media queries based on connection speed have been proposed, but the traction isn’t there (yet?).
You can query the current connection type with JavaScript
with navigator.connection.type
, which returns
the values of UNKNOWN
, ETHERNET
, WIFI
, CELL_2G
, CELL_3G
, or CELL_4G
:
var connection, speed; var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection || {'type':'0'}; // set download speed switch(connection.type) { case connection.CELL_3G: // 3G speed = 'medium'; break; case connection.CELL_2G: // 2G speed = 'slow'; break; default: speed = 'fast'; } document.body.classList.add(speed);
You can change the class of the body and target whether you import high DPI images based on that class:
@media screen and (-webkit-min-device-pixel-ratio: 2), screen and (min--moz-device-pixel-ratio: 2), screen and (-min-moz-device-pixel-ratio: 2), screen and (-o-min-device-pixel-ratio: 2/1), screen and (min-device-pixel-ratio: 2) { body.fast { background-image: url(../hidpi/bgimg.jpg); } }
Note that on some devices, images larger than 1024 px will tile in memory.
When including higher resolution images, you still want them to occupy the same physical
space as they would in non-Retina devices. The background-size
property, discussed in Chapter 9, enables us to
ensure that the site appears the same no matter the resolution.
Indeed, different displays show 72, 96, or 144 DPI. Whether you’re sending the regular image or the Retina display image that is four times the size, you still want the background images to display as if they were the same size, just crisper if the device can handle it.
Use the background-size
property to ensure that both your 100 × 100 px and your 200 × 200 px
Retina image display as 100 × 100 px:
.icon { background-size: 100px 100px; background-image(../lodpi/icon.jpg); } @media screen and (-webkit-min-device-pixel-ratio: 2), screen and (min--moz-device-pixel-ratio: 2), screen and (-min-moz-device-pixel-ratio: 2), screen and (-o-min-device-pixel-ratio: 2/1), screen and (min-device-pixel-ratio: 2) { .fast .icon { background-image(../hidpi/icon.jpg); } }
In the preceding code, even though the high DPI image may be four times the size as the low DPI image, they will occupy the same space.
In some cases, using data URIs for images may be more performant than making additional HTTP requests to serve regular images. Data URIs are string representations of binary image files.
Because data URIs use strings to represent binary data, their size can become fairly large, if not huge. For small images, like avatars and favicons, a data URI will likely improve download performance by reducing the number of HTTP requests (and possibly DNS lookups). For large images, like high DPI fullscreen backgrounds, it might be worth the DNS look up and HTTP request. There is no “right” solution.
A site like Twitter, where they are displaying the avatars of all the people who follow you, all the people you follow, and all the people they retweet, would not be able to successfully create a cacheable sprite (described in the next section, ) of avatars. People are able to change their Twitter avatars whenever they like. So, even if Twitter did create sprites, they would have to be updated with each request. For Twitter’s goals, data URIs for avatars might make sense. Their site requires small, noncacheable, nonspriteable images. A separate HTTP request for each of those images would render much slower than including the images as data URIs in a single text file with a single HTTP request.
A sprite is a larger image file containing several smaller images.
Sprites are used to reduce the number of HTTP requests and DNS
lookups, and increase the download speeds of web pages for background-image
images. Sprites can also be
used in animation.
For example, if your site uses many colorful icons or displays the favicons of a
plethora of rating sites, and you know what the limited number or
recurring icons will be, you can put all of those icons in one image.
Then, using background-position
,
show just sections of that image to the user. If you have single color
icons, you may be able to use font icons, described . An example of
such a sprite is displayed in Figure 11-5.
Sprites can also be used in animation in conjunction with the
steps() animation-timing-function
values, as we did with the Lemming .
To create a dancing icon, we can use the sprite in Figure 11-6 with the following CSS:
.psy { width: 22px; height: 40px; background-image: url(sprite.png); animation: dance 4s steps(23, start) infinite, movearound 9s steps (23, start) infinite 45ms; } @keyframes dance { 0% { background-position: 0 0; } 100% { background-position: −506px 0; } } @keyframes movearound { 0% { transform: translatex(−300px); } 100% { transform: translatex(300px); } }
In the dance animation, we change the background position. The character we are animating is only 22 px wide by 40 px tall. Approximately every 45 ms the background image jumps 22 px to the left, showing the icon to the right of the previous icon. In this way we can make the div appear to dance. An animation that jumped three times faster would be less janky, but would have required over 40 frames of artwork.
Small sprites reduce the number of HTTP requests, reduce the occurrence of flickering caused by delay in image loads, plus they can be cached and can be used in animation. Large sprites risk causing issues though, due to memory constraints on mobile devices. Use sprites wisely, and preferably keep them under 1024 px.
Safari 6 and Chrome 21 support image-set()
,
which enables you to serve different background images for different
pixel density displays:
body > header { background-image: url(images/header.png); background-image: -webkit-image-set(url(images/header.png) 1x, url(images/header_2x.png) 2x); height:60px; }
According to the CSS Working group, this is not ready for
implementation. I am including it here just to make you aware.
Hopefully this will soon be implementable, as images are easier to
manage in a srcset
syntax than media query
blocks.
Many websites and applications use a plethora of small images that are so common they are represented in font files. For example, the flower on the back side of the CubeeDoo cards, and the lighter shapes in the “shapes” theme are actually characters in CubeeDoo’s default font, as displayed in Figure 11-7.
There are over 10,000 different characters in the default character sets on the majority of devices. Chances are you’ll find the shape you need, be it an envelope, arrow, or other. Since font icons are just characters, you can easily change the size and color of the icon with CSS. Font icons scale without distortion, change color without image-editing software, and, if part of fonts found on most devices, load without requiring additional HTTP requests.
When you find the character you need, you can include it
directly in the HTML, as generated content using ::before
and/or ::after
, or include the character in SVG and
include it as a background image or data URI.
Whenever possible, choose font icons over images. Many companies create their own character set for their sites with their own unique iconography.
There is one other trick I would like to cover: masking. Sometimes you need to use a PNG because you need transparency. However, a detailed PNG produces a much larger file size than a JPEG. But JPEGs don’t have transparency, so you may feel like you need to use a PNG to provide that transparency. Masking allows you to create transparent JPEGs, so you can serve a JPEG instead of a PNG, saving a lot of bandwidth and memory.
CSS masking enables us to overlay a smaller filesize JPEG with a monotone 8-bit transparent PNG to create transparent sections in the original JPEG at display. By masking a JPEG with a transparent PNG, we can create transparencies based off the alpha of an image, greatly reducing bytes needed:
div { background-image:url(images/smallerFileThanPNG.jpg); -webkit-mask: url(images/partToShow.png); }
While downloading two images—the background image and the mask—may add an additional HTTP request, the savings of bytes can be worth it. In the online chapter resources example, the original high DPI PNG was 551 KB. Converting the PNG to a JPEG with no transparency brought the image to 88 KB, and the monotone luminance mask is only 4 KB, for a total of 92 KB: a huge savings over the transparent PNG.
Masking was originally a WebKit-only property, but is being standardized by the W3C. The original syntax is still prefixed, and only supported in WebKit, with basic support in all WebKit mobile browsers.
Client-Hints
are not implemented yet, or even in a draft specification form.
But since they may be coming, and if they do, it will be awesome, I am
mentioning it here.
Client-Hints
are hints that the
browser will send to the server along with the request header. When
supported, it is expected to pass three values, dpr
, dw
,
and dh
, for device pixel ratio,
device width, and device height:
Client-Hints: dh=1280, dw=768, dpr=2.0
The browser will be able to inform the server via the request header. The server can then serve the most appropriate image sizes based on the browser specifications.
This may sound like it is similar to user-agent sniffing. Browser
vendors copied each other’s UA strings to bypass the incomplete UA
sniffing-routines deployed on tons of websites. Because developers did
so much UA sniffing, browser vendors include each other’s strings to get
some semblance of support. Client-Hints
will hopefully not have this
problem, as there is likely little reason, other than to reduce
bandwidth consumption, to lie about the height or width of your
device.
[76] The various column properties are supported in all mobile browsers including Opera Mini, starting with IE10, and must be prefixed for WebKit and Firefox.
[77] WebKits don’t support the round
or space
value, replacing them with
repeat
instead (which is better
than failing, I guess).
[78] Include the vendor prefix on the value rather than the property,
as display
is not experimental, but
the new values are in some browsers. Use display: -webkit-flex;
in WebKit browsers
and display: -ms-flexbox;
in IE 10.
Opera and Firefox 20+ do not require prefixed values.
[79] It may also match some pre-Blink Opera browsers, as Opera Presto has added limited support for some WebKit vendor-prefixed properties and values.
[80] When you see the screen size listed as 9.5 or 9.7 inches, that is the diagonal length of the screen from top-left corner to bottom-right corner.