Back to Simple CSS With the Shadow DOM 9781617297496

For web designers, large complex applications can mean large complex battles between CSS and the DOM (Document Object Mo

784 116 15MB

English Pages 109 Year 2019

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Back to Simple CSS With the Shadow DOM
 9781617297496

Table of contents :
contents......Page 6
introduction......Page 7
Cascade, specificity, and inheritance......Page 10
1.1 The cascade......Page 11
1.1.1 Understanding stylesheet origin......Page 15
1.1.2 Understanding specificity......Page 17
1.1.3 Understanding source order......Page 22
1.1.4 Two rules of thumb......Page 24
1.2 Inheritance......Page 25
1.3 Special values......Page 27
1.3.1 Using the inherit keyword......Page 28
1.3.2 Using the initial keyword......Page 29
1.4.1 Beware shorthands silently overriding other styles......Page 30
1.4.2 Understanding the order of shorthand values......Page 31
Layout with CSS3......Page 36
Placing elements on a line with inline-block......Page 37
Grouping element dimensions with display: table......Page 40
Mixing different length units with calc......Page 44
Controlling the box model......Page 49
Using media queries for flexible layout......Page 50
Resolution detection......Page 52
Changing layout based on orientation and aspect ratio......Page 56
Additional device-detection features......Page 57
The future of CSS layout......Page 58
Using flexible boxes for nested layout......Page 59
Using the CSS3 Grid Alignment module......Page 63
Controlling content flow with CSS3 Regions......Page 68
Making complex shapes with CSS3 Exclusions and Shapes......Page 70
Browser support......Page 73
inline-block in IE6 and IE7......Page 74
Flexboxes in Chrome, Firefox, IE, and Safari......Page 75
Summary......Page 76
9.1 Style creep......Page 78
9.1.2 Style creep into your component......Page 81
9.2 Style creep solved with the Shadow DOM......Page 83
9.2.1 When styles creep......Page 86
9.3 Shadow DOM workout plan......Page 88
9.3.1 Application shell......Page 89
9.3.2 Host and ID selectors......Page 90
9.3.3 Grid and list containers......Page 93
9.4.1 Creating the exercise component......Page 97
9.4.2 Exercise component style......Page 100
9.5 Updating the slider component......Page 102
Summary......Page 104
C......Page 105
M......Page 106
U......Page 107
Z......Page 108
Promo......Page 0
CSS in Depth......Page 2
Hello HTML 5 and CSS3......Page 3

Citation preview

Save 50% on these books and videos – eBook, pBook, and MEAP. Enter cssweb50 in the Promotional Code box when you checkout. Only at manning.com.

CSS in JavaScript with styled-components and React by Dustin Schau Duration: 2h 42m $49.99

CSS in Depth in Motion by Chris Ward Duration: 2h 50m $49.99

CSS in Depth by Keith J. Grant ISBN 9781617293450 472 pages $35.99

Web Components in Action by Ben Farrell ISBN 9781617295775 432 pages $39.99

Hello HTML 5 and CSS3 by Rob Crowther ISBN 9781935182894 560 pages $31.99

Back to Simple CSS with the Shadow DOM Ben Farrell

Manning Author Picks

Copyright 2019 Manning Publications To pre-order or learn more about these books go to www.manning.com

For online information and ordering of these and other Manning books, please visit www.manning.com. The publisher offers discounts on these books when ordered in quantity. For more information, please contact

Special Sales Department Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 Email: Erin Twohey, [email protected]

©2019 by Manning Publications Co. All rights reserved.

No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher.

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps.

Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine. Manning Publications Co. 20 Baldwin Road Technical PO Box 761 Shelter Island, NY 11964 Cover designer: Leslie Haimes

ISBN: 9781617297496 Printed in the United States of America 1 2 3 4 5 6 7 8 9 10 - EBM - 24 23 22 21 20 19

contents introduction

iv

Cascade, specificity, and inheritance Chapter 1 from CSS in Depth Layout with CSS3 28 Chapter 8 from Hello HTML5 & CSS3 Shadow CSS 70 Chapter 9 from Web Components in Action index

97

iii

2

introduction You may have heard about Web Components, especially recently, with all major modern browsers adopting the collection of standards that sit at their foundation. Web Components are, in fact, the subject of my new book, Web Components in Action. The biggest and most impactful feature of Web Components for me has definitely been the Shadow DOM (Document Object Model). It likely wouldn’t sound very thrilling if I just said, “The Shadow DOM provides encapsulation for style and JS.” However, encapsulation does have some profound and exciting consequences. When you work on the web, everything you do is in the DOM. Until now, with one exception that isn’t welcome in polite company anymore (ahem...iFrames), you’ve only had one DOM to work with. One implication of this is that your style is cascaded through this one and only DOM. As applications grew larger over the years, simple, everyday CSS used in this one DOM just didn’t cut it anymore: Selectors collided and caused unexpected visual problems. We’d spend hours trying to figure out which rule overrides another. Fixing it would often mean breaking something else, and we’d get caught in a frustrating game of Whack-a-Mole. There have long been tools, patterns, and libraries to help manage this mess in larger applications and systems. As clever as some of these solutions are, they are really only putting a band-aid over the problem. What’s more, suddenly your team has to learn more rules, more patterns, and complicated front-end tooling. With the Shadow DOM, you can now focus on a mini-DOM for each component. Things get much simpler when you’re not worrying about all the styles on the entire page affecting a specific piece of UI. To help you get back to CSS basics and writing easy-to-manage CSS, I’ve handpicked chapters from three great Manning books for this sampler. Chapter 1 from CSS in Depth by Keith J. Grant gives you a refresher on CSS basics and provides an in-depth explanation of cascade, specificity, and inheritance—a particularly useful discussion since the Shadow DOM changes the scope of how these three components operate to be more manageable on a complex page.

iv

INTRODUCTION

v

In the chapter from Hello HTML5 & CSS3, author Rob Crowther spotlights the essential aspects of CSS layout. While some of the information is outdated, the importance of user-friendly CSS layout is as relevant as ever, and Rob’s entertaining style makes learning these vital skills fun. The last chapter in this quickstart guide is from my own book, Web Components in Action. It focuses on CSS in the Shadow DOM with an emphasis on why we can use simple CSS by comparing everyday situations with the Shadow DOM vs without it, as we are faced with styles creeping in and out of your component and ruining your visual design. This chapter also explores keeping external style out of your Web Components, the Shadow DOM for CSS encapsulation, rediscovering the ID attribute for Web Components, and the new Shadow DOM CSS selectors and how to use them. The examples here, like all the examples in this sampler, use simple and easy to read CSS. The Shadow DOM reignited my love of CSS, and I hope it’ll do the same for you. If that’s the case, the three books highlighted in this sampler are great for getting started with CSS basics or giving yourself a refresher.

Cascade, Specificity, and Inheritance

T

his chapter explores the basics of CSS. The topic of cascade, specificity, and inheritance is especially relevant as the Shadow DOM changes the scope of how these three operate to be more manageable on a complex page.

Chapter 1

Chapter 1 from CSS in Depth by Keith J. Grant

Cascade, specificity, and inheritance

This chapter covers  The four parts that make up the cascade  The difference between the cascade and

inheritance  How to control which styles apply to which

elements  Common misunderstandings about shorthand

declarations

CSS is unlike a lot of things in the world of software development. It’s not a programming language, strictly speaking, but it does require abstract thought. It’s not purely a design tool, but it does require some creativity. It provides a deceptively simple declarative syntax, but if you’ve worked with it on any large projects, you know it can grow into unwieldy complexity. When you need to learn to do something in conventional programming, you can usually figure out what to search for (for example, “How do I find items of type x in an array?”). With CSS, it’s not always easy to distill the problem down to a single question. Even when you can, the answer is often “it depends.” The best way to 2

The cascade

3

accomplish something is often contingent on your particular constraints and how precisely you’ll want to handle various edge cases. While it’s helpful to know some “tricks” or useful recipes you can follow, mastering CSS requires an understanding of the principles that make those practices possible. This book is full of examples, but it is primarily a book of principles. Part 1 begins with the most fundamental principles of the language: the cascade, the box model, and the wide array of unit types available. Most web developers know about the cascade and the box model. They know about pixel units and may have heard that they “should use ems instead.” The truth is, there’s a lot to these topics, and a cursory understanding of them gets you only so far. If you’re ever to master CSS, you must first know the fundamentals, and know them deeply. I know you’re excited to start learning the latest and greatest CSS has to offer. That is the exciting stuff. But first, we’ll go back to the fundamentals. I’ll quickly review the basics, which you’re likely already familiar with, and then dive deep into each topic. My aim is to strengthen the foundation on which the rest of your CSS is built. In this chapter, we begin with the C in CSS —the cascade. I’ll articulate how it works, then show you how to work with it practically. We then look at a related topic, inheritance. I’ll follow that with a look at shorthand properties and some common misunderstandings surrounding them. Together, these topics are all about applying the styles you want to the elements you want. There’s a lot of “gotchas” here that often trip up developers. A good understanding of these topics will give you better control over making your CSS do what you want it to do. With any luck, you’ll also better appreciate and even enjoy working with CSS.

1.1

The cascade Fundamentally, CSS is about declaring rules: Under various conditions, we want certain things to happen. If this class is added to that element, apply these styles. If element X is a child of element Y, apply those styles. The browser then takes these rules, figures out which ones apply where, and uses them to render the page. When you look at small examples, this process is usually straightforward. But as your stylesheet grows, or the number of pages you apply it to increases, your code can become complex surprisingly quickly. There are often several ways to accomplish the same thing in CSS. Depending on which solution you use, you may get wildly different results when the structure of the HTML changes, or when the styles are applied to different pages. A key part of CSS development comes down to writing rules in such a way that they’re predictable. The first step toward this is understanding how exactly the browser makes sense of your rules. Each rule may be straightforward on its own, but what happens when two rules provide conflicting information about how to style an element? You may find one of your rules doesn’t do what you expect because another rule conflicts with it. Predicting how rules behave requires an understanding of the cascade.

4

CHAPTER 1

Cascade, specificity, and inheritance

To illustrate, you’ll build a basic page header like one you might see at the top of a web page (figure 1.1). It has the website title atop a series of teal navigational links. The last link is colored orange to make it stand out as a sort of featured link. Many graphics in this book are meant to be viewed in color. The eBook versions display the color graphics, so they should be referred to as you read. To get your free eBook in PDF, ePub, and Kindle formats, go to https://www.manning.com/books/css-in-depth to register your print book.

NOTE TO PRINT BOOK READERS

As you build this page header, you’ll probably be familiar with most of the CSS involved. This will allow us to focus on aspects of CSS you might take for granted or only partially understand.

Figure 1.1

Page heading and navigation links

To begin, create an HTML document and a stylesheet named styles.css. Add the code in listing 1.1 to the HTML. A repository containing all code listings in this book is available for download at https://github.com/CSSInDepth/css-in-depth. The repository has all CSS embedded with the corresponding HTML in a series of HTML files.

NOTE

Listing 1.1 Markup for the page header



Wombat Coffee Roasters



Featured

link

Page title

5

The cascade

When two or more rules target the same element on your page, the rules may provide conflicting declarations. The next listing shows how this is possible. It shows three rulesets, each specifying a different font style for the page title. The title can’t have three different fonts at one time. Which one will it be? Add this to your CSS file to see. Listing 1.2 Conflicting declarations h1 { font-family: serif; } #page-title { font-family: sans-serif; } .title { font-family: monospace; }

Tag (or type) selector ID selector

Class selector

Rulesets with conflicting declarations can appear one after the other, or they can be scattered throughout your stylesheet. Either way, given your HTML, they all target the same element. All three rulesets attempt to set a different font family to this heading. Which one will win? To determine the answer, the browser follows a set of rules, so the result is predictable. In this case, the rules dictate that the second declaration, which has an ID selector, wins; the title will have a sans-serif font (figure 1.2). The cascade is the name for this set of rules. It determines how conflicts are resolved, and it’s a fundamental part of how the language works. Although most experienced developers have a general sense of the cascade, parts of it are sometimes misunderstood.

Figure 1.2 The ID selector wins over the other rulesets, producing a sans-serif font for the title.

Let’s unpack the cascade. When declarations conflict, the cascade considers three things to resolve the difference: 1

2 3

Stylesheet origin —Where the styles come from. Your styles are applied in conjunction with the browser’s default styles. Selector specificity —Which selectors take precedence over which. Source order —Order in which styles are declared in the stylesheet.

6

CHAPTER 1

Cascade, specificity, and inheritance

The rules of the cascade are considered in this order. Figure 1.3 shows how they’re applied at a higher level.

Conflicting declarations

Different origin or importance?

No

Is one an inline style? (Scope)

Yes Use declaration with higherpriority origin

Figure 1.3

Yes

Use inline declaration

No

Do selectors have different specificity?

No

Use declaration that comes later in source order

Yes Use declaration with higher specificity

High-level flowchart of the cascade showing declaration precedence

These rules allow browsers to behave predictably when resolving any ambiguity in the CSS. Let’s step through them one at a time.

A quick review of terminology Depending on where you learned CSS, you may or may not be familiar with all the names of the various parts of CSS syntax. I won’t belabor the point, but because I’ll be using these terms throughout the book, it’s best to be clear about what they mean. Following is a line of CSS. This is called a declaration. This declaration is made up of a property (color) and a value (black): color: black;

Properties aren't to be confused with attributes, which are part of the HTML syntax. For example, in the element , href is an attribute of the a tag. A group of declarations inside curly braces is called a declaration block. A declaration block is preceded by a selector (in this case, body): body { color: black; font-family: Helvetica; }

Together, the selector and declaration block are called a ruleset. A ruleset is also called a rule—although, it’s my observation that rule is rarely used so precisely and is usually used in the plural to refer to a broader set of styles. Finally, at-rules are language constructs beginning with an “at” symbol, such as @import rules or @media queries.

7

The cascade

1.1.1

Understanding stylesheet origin The stylesheets you add to your web page aren’t the only ones the browser applies. There are different types, or origins, of stylesheets. Yours are called author styles; there are also user agent styles, which are the browser’s default styles. User agent styles have lower priority, so your styles override them. Some browsers let users define a user stylesheet. This is considered a third origin, with a priority between user agent and author styles. User styles are rarely used and beyond your control, so I’ve left them out for simplicity.

NOTE

User agent styles vary slightly from browser to browser, but generally they do the same things: headings ( through ) and paragraphs (

) are given a top and bottom margin, lists ( and

    ) are given a left padding, and link colors and default font sizes are set. USER

    AGENT STYLES

    Let’s look again at the example page (figure 1.4). The title is sans-serif because of the styles you added. A number of other things are determined by the user agent styles: the list has a left padding and a list-style-type of disc to produce the bullets. Links are blue and underlined. The heading and the list have top and bottom margins.

    Figure 1.4 User agent styles set defaults for your web page header.

    After user agent styles are considered, the browser applies your styles—the author styles. This allows declarations you specify to override those set by the user agent stylesheet. If you link to several stylesheets in your HTML, they all have the same origin: the author. The user agent styles set things you typically want, so they don’t do anything entirely unexpected. When you don’t like what they do to a certain property, set your own value in your stylesheet. Let’s do that now. You can override some of the user agent styles that aren’t what you want so your page will look like figure 1.5.

    Figure 1.5 Author styles override user agent styles because they have higher priority.

    8

    CHAPTER 1

    Cascade, specificity, and inheritance

    In the following listing, I’ve removed the conflicting font-family declarations from the earlier example and added new ones to set colors and override the user agent margins and the list padding and bullets. Edit your stylesheet to match these changes. Listing 1.3 Overriding user agent styles h1 { color: #2f4f4f; margin-bottom: 10px; } #main-nav { margin-top: 10px; list-style: none; padding-left: 0; }

    Reduces the margins

    Removes user agent list styles

    #main-nav li { display: inline-block; } #main-nav a { color: white; background-color: #13a4a4; padding: 5px; border-radius: 2px; text-decoration: none; }

    Makes list items appear side by side rather than stacked

    Provides a buttonlike appearance for the navigational links

    If you’ve worked with CSS for long, you’re probably used to overriding user agent styles. When you do, you’re using the origin part of the cascade. Your styles will always override the user agent styles because the origins are different. You may notice I used ID selectors in this code. There are reasons to avoid doing this, which I’ll cover in a bit.

    NOTE

    IMPORTANT DECLARATIONS

    There’s an exception to the style origin rules: declarations that are marked as important. A declaration can be marked important by adding !important to the end of the declaration, before the semicolon: color: red !important;

    Declarations marked !important are treated as a higher-priority origin, so the overall order of preference, in decreasing order, is this: 1 2 3

    Author important Author User agent

    9

    The cascade

    The cascade independently resolves conflicts for every property of every element on the page. For instance, if you set a bold font on a paragraph, the top and bottom margin from the user agent stylesheet still applies (unless you explicitly override them). The concept of style origin will come into play when we get to transitions and animations because they introduce more origins to this list. The !important annotation is an interesting quirk of CSS, which we’ll come back to again shortly.

    1.1.2

    Understanding specificity If conflicting declarations can’t be resolved based on their origin, the browser next tries to resolve them by looking at their specificity. Understanding specificity is essential. You can go a long way without an understanding of stylesheet origin because 99% of the styles on your website come from the same origin. But if you don’t understand specificity, it will bite you. Sadly, it’s often a missed concept. The browser evaluates specificity in two parts: styles applied inline in the HTML and styles applied using a selector. INLINE

    STYLES

    If you use an HTML style attribute to apply styles, the declarations are applied only to that element. These are, in effect, “scoped” declarations, which override any declarations applied from your stylesheet or a

    Inline in CSS

    @media print { }

    Browser support quick check: CSS3 media queries

    Using media queries for flexible layout

    Full

    Partial

    2.0

    -

    3.5

    -

    9.0

    -

    9.5

    -

    4.0

    3.1

    43

    These are known as media queries. All three of the previous examples are constraining the styles they reference or include to only apply to print media. In CSS2 you could also restrict to screen, aural, braille, handheld, or speech, among others. The default, if you don’t specify anything, is all—the styles will apply no matter what the output device is.

    Mobile browser support CSS3 media queries are especially important for mobile browsers. All the current major smartphone browsers have support: the iOS and Android standard browsers; mobile Opera and Firefox; and IE in Windows Mobile 7.5.

    Media queries avoid browser detection by letting the browser itself determine what support it has. If a new browser or device comes along that you hadn’t anticipated, as long as you’ve used media queries, it should still select the most appropriate set of CSS rules. CSS3 dramatically extends the number of properties that can be used in media queries. In the following sections, you’ll see some practical examples of media queries in use.

    44

    CHAPTER 8

    Layout with CSS3

    Resolution detection The most common distinguishing features of different devices are screen resolution and window size. Most desktop users have a browser window at least 800 pixels wide, whereas most mobile browsers are less than 800 pixels wide. Media queries let you choose between the two situations. The basic syntax for creating a set of rules for a window 800 pixels wide is this: @media screen and (max-width: 800px) { } @media screen and (max-device-width: 800px) { }

    Any CSS rules placed inside the squiggly brackets will only be applied if the conditions are met. The first rule selects based on the browser window size, and the second one selects based on display size—the browser window doesn’t have to fill the entire width of the display for this rule to match. In this section, you’ll create a layout that adapts to the size of the browser window. Here’s what the layout looks like in a window 1024 pixels wide.

    Here’s the key markup (see the full listing in ch08/media-queriesadaptive.html):

    Adaptive Layout with Media Queries



      Using media queries for flexible layout

      45

    • Home
    • About
    • contact


    I never am really satisfied...



    This page is a demo for media queries.



    Viewing 480px or less mode Viewing 800px or less mode Viewing 800px or more mode

    The default three-column layout is for windows greater than 800 pixels wide: body header, footer nav ul nav a img

    { width: 90%; margin: 0 5%; font-family: "Komika Hand", sans-serif; } { display: block; width: auto; } { list-style: none; margin: 0; padding: 0; } { display: block; margin: 1em; padding: 1em; outline: 4px dashed black; } { max-width: 100px; display: block; margin: 0.5em auto; } { display: table; outline: none; padding: 0; }

    div nav, article, aside { nav, aside { article { #msg480, #msg800 {

    display: table-cell; } width: 25%; } width: 50%; } display: none; }

    If viewed in a window 800 pixels wide or narrower, the page switches to a two-column layout.

    46

    CHAPTER 8

    Layout with CSS3

    This layout is implemented with two sets of rules within media queries. The first provides a set of rules to be applied for any width less than 800 pixels, and the second is a set of rules that’s applied only when the window is between 481 and 800 pixels wide. This approach saves repeating rules: @media screen and (max-width: 800px) { div { display: block; overflow: hidden; margin: 0; } nav { display: block; width: auto; } nav ul { display: table; border-collapse: collapse; margin: 0 auto; } nav li { display: table-cell; } } @media screen and (max-width: 800px) and (min-width: 481px) { h1 { font-size: 110%; } article, aside { display: block; } article { width: 60%; float: left; margin-right: 0; } aside { width: 20%; font-size: 80%; float: right; margin: 1.2em; margin-left: 0; } img { max-width: 60px; }

    Using media queries for flexible layout

    47

    #msg480, #msg801 { display: none; } #msg800 { display: inherit; } }

    Finally, at a width of 480 pixels or less, the layout becomes single column. The majority of the required rules for this layout were specified in the lessthan-800 pixels example. These rules mostly adjust the size of elements to allow more content to appear on a mobile screen: @media screen and (max-width: 480px) { h1 { font-size: 105%; } nav { padding: 0; } nav a { margin: 0; padding: 0.25em 0.5em; } article, aside { display: block; width: auto; } #msg800, #msg801 { display: none; } #msg480 { display: inherit; } }

    Following are screenshots of the same page on an Android phone in portrait mode (left) and landscape mode (right).

    Portrait (480px width)

    Landscape (800px width)

    48

    CHAPTER 8

    Layout with CSS3

    In this example, the default layout is a full—sized screen desktop experience. Media queries were used to adapt to lower resolutions. For practical uses, it’s often better to do things the other way around— when IE9 is released all the major desktop browsers will support media queries. But IE on Windows Mobile 7 won’t, and neither will browsers on older feature phones. If you expect lots of these visitors, design for the small screen and use media queries to adapt for larger devices.

    Changing layout based on orientation and aspect ratio Maybe you want to do different things on a display that’s 640 pixels wide and 480 pixels tall compared to one that is 640 pixels wide but 800 pixels tall—you want to know whether the aspect ratio is landscape or portrait for a given width. You can specify rules like this: @media screen and (min-width: 640px and max-height: 480px) { } @media screen and (min-width: 640px and min-height: 800px) { }

    But this is a very fragile solution. For a start, you’re missing windows that are 640 pixels wide but, perhaps thanks to a permanent toolbar, only 780 pixels tall. You could adjust to that particular case, but what if some innovative manufacturer came up with a 700 x 500 pixel device? In general, the idea behind media queries is for you to end up doing less work—not rewriting chunks of your stylesheet for every possible combination of width and height. Fortunately, CSS3 provides an situation:

    orientation

    media query for just this

    @media screen and (min-width: 640px and orientation: portrait) { } @media screen and (min-width: 640px and orientation: landscape) { }

    Orientation is a special case of are equivalent to these:

    aspect-ratio.

    The previous two rules

    @media screen and (min-width: 640px and max-aspect-ratio: 1/1 ) { } @media screen and (min-width: 640px and min-aspect-ratio: 1/1 ) { }

    Using aspect-ratio, it’s possible to distinguish between widescreen displays and traditional monitor sizes: @media screen and (min-width: 640px and aspect-ratio: 16/9 ) { } @media screen and (min-width: 640px and aspect-ratio: 4/3 ) { }

    Using media queries for flexible layout

    49

    In this case you may want to select based on the monitor size rather than the window size: @media screen and (min-width: 640px and device-aspect-ratio: 16/9 ) { } @media screen and (min-width: 640px and device-aspect-ratio: 4/3 ) { }

    device-aspect-ratio:

    always matches the monitor, regardless of the

    window size.

    Additional device-detection features Media queries can be used for more than just screen sizes. There are several other features for detection in the spec, and various browser vendors are introducing more as they add functionality to their browsers. Here are some of the more interesting ones: ❂







    color—Select rules based on the number of bits available per color channel, where 8 bits is 255 levels per color. If you can remember the days of web-safe colors, this feature lets you work around the pixelation issues that web-safe colors avoided. Devices that have limited color support can be given a more constrained set of background colors. resolution—Select rules based on the dots per inch (dpi) of the display. A display with high dpi renders fonts more readably, so you can use a smaller font size. touch-enabled—This is currently a Mobile Firefox–only feature. Select rules based on whether the display is a touch input device, perhaps to give buttons and links more finger space. device-pixel-ratio—Currently a Mobile Safari–only feature. Select rules based on the zoom level, perhaps to provide a higher-resolution background image as the user zooms in so the image remains crisp and sharp.

    Can you really make a mobile website with just CSS? Is it possible to make your website deal with a full range of mobile devices and desktop PCs just by fiddling with CSS? As with most things, the answer is, “it depends.”

    50

    CHAPTER 8

    Layout with CSS3

    (continued) A brochureware website that is mostly static pages and doesn’t expect much interaction from the user is almost certainly a good candidate for adaptation with media queries. Similarly, blogs or other text-heavy websites ought to be straightforward enough to make work on a wide range of devices. Mobile users, who are often paying for their connectivity by the megabyte, might appreciate not being forced to download huge video files, large graphics, and lots of ads; but if the site in question is relatively lightweight in this department it shouldn’t be a problem. Also remember from chapter 4 that if you’re using HTML5 to serve your video files, you have built-in functionality to serve lower-resolution and lower-quality files to mobile devices. The more application-like a website is, the more likely it is that you won’t be able to deliver the same content to all devices and end up with a usable experience for all users. In this situation, you should consider dynamically loading portions of your app with JavaScript after you’ve determined the capabilities of the device. One last thing to bear in mind: studies have shown that many desktop users prefer to use the mobile versions of certain popular websites. The mobile versions are frequently simpler and more task focused—or, looked at another way, the desktop websites are too complex and confusing. Media queries and mobile websites don’t absolve web authors from thinking about the needs of their users.

    CSS3 promises to finally equip web authors with layout tools with power similar to that available in non—HTML frameworks like Adobe Flex, Microsoft Silverlight, and Java Swing. In the next section, you’ll learn how powerful CSS layout may become in the next few years.

    The future of CSS layout CSS3 has several proposed standards currently under heavy develop-

    ment that could completely alter how layout on the web is done. In this section, you’ll learn about these new approaches, all of which have at least experimental implementations available. They include flexible boxes, which are excellent for toolbars and menus; grid-align, which is great for traditional grid-based designs; and regions, exclusions, and positioned floats, which are good for multiple-column magazine-style layouts.

    The future of CSS layout

    51

    At the time of writing none of the approaches in this section are suitable for use on a public website because support is just too spotty. You may be able to make use of them in a tightly controlled environment such as an intranet, a web view in an iOS or Android app, or a Windows 8 Metro app.

    Using flexible boxes for nested layout Flexible boxes, commonly referred to as flexboxes, are a layout approached developed in Firefox to be used for laying out various elements of the user interface. They’re primarily aimed at creating menus and toolbars, particularly toolbars made up of nested elements. Currently Chrome, Firefox, IE10, and Safari have some support for flexboxes; you’ll need to add the relevant prefix to get the listings in this section working. This section first gives you a quick introduction to flexboxes using this simple markup fragment, and then looks at practical use cases and issues:

    1 2 3 4 5

    To produce five equal-size boxes, set the parent element to display: box and set equal box-flex values on the child elements: div { width: 90%; display: box; } div div { box-flex: 1; }

    CHAPTER 8

    Layout with CSS3

    Setting a larger box-flex value on certain elements causes them to take up an increasing proportion of the spare space: div div:nth-child(2) { box-flex: 2; } div div:nth-child(4) { box-flex: 3; }

    Flexboxes allow elements to be displayed in a different order than their position in the markup: div div { box-ordinal-group: 2; } div div:nth-child(5) { box-ordinal-group: 1; }

    Because the fifth child is set to be  ordinal-group: 1, it appears before all the elements that are  ordinal-group: 2.

    Browser support quick check: flexbox

    52

    Full

    Partial

    -

    2.0

    -

    2.0

    -

    10.0

    -

    -

    -

    3.1

    The future of CSS layout

    53

    Note that even though element 5 is now the first displayed, it’s still the fifth element as far as the CSS is concerned. Element 2 and element 4 have larger box-flex values, even though they’re now shown as the third and fifth elements. Although flexboxes are horizontal by default, they can also be set to be vertical: div { width: 5em; height: 600px; box-orient: vertical; }

    Note that in both horizontal and vertical cases, you need to specify a length in that direction in order to get the flex to appear. This is because the flex distributed among the elements comes from the leftover space after the intrinsic size of the elements is taken away. This can lead to some counter-intuitive results when the elements with flex don’t have a well-defined intrinsic width. This is easily demonstrated by adding some text—the cell will expand to contain it. The available space gets used up, so the flex can no longer be distributed.

    54

    CHAPTER 8

    Layout with CSS3

    For collections of elements that do have an intrinsic width, flexboxes offer an ability that can’t be replicated by tables, display: table, floats, or inline-block: they can create flexible grids that can have a variable number of elements per row, as with floats and inline-block, but the individual elements flex so they exactly fill up each row, as with table rows and display: table. This is thanks to the multiline property. The following example has a grid of 60 cells, each containing a number. 800px

    640px

    480px

    800px

    At different widths, a different number of elements fit on each row. If you zoom in on a few cells, you can see that they’re also slightly different widths depending on the size of the container.

    640px

    480px

    Following is a snippet of the markup (left) and the CSS (right) required for this layout. The key property is box-lines:
    • 1
    • 2
    • 3
    • 4
    • ... ...


    ul { display: box; box-lines: multiple; } li { display: block; box-flex: 1; min-width: 3em; }

    The future of CSS layout

    Flexbox good

    Can manipulate layout order with CSS. Multiline grids that exactly fill the available space.

    55

    Flexbox bad Intended for toolbars, menus, and so on rather than full pages. Weird things happen when content has no fixed intrinsic width.

    The CSS3 Grid Alignment module completely separates the layout from the elements in your markup. You use CSS to define a grid and then assign elements to the grid using a row and column reference. Currently only IE10 has any support for this module, although the WebKit support is under development. Following is some simple markup that will be turned into the three-column layout shown here:

    Browser support quick check: grid-align

    Using the CSS3 Grid Alignment module Full

    Partial

    -

    19.0*

    -

    -

    -

    10.0

    -

    -

    -

    3.1

    * Chrome needs a runtime flag to be set to enable the experimental support; see https://bugs.webkit.org/show_bug.cgi? id =60731 for details of progress.

    Header Side bar I never am really satisfied... etc., etc. Side bar Footer

    56

    CHAPTER 8

    Layout with CSS3

    A grid is created by defining a set of rows and (or) columns. In this example, you’ll go ahead and create three columns and three rows on the element: body { display: grid; grid-columns: auto 1fr auto; grid-rows: auto 1fr auto; }

    The first and last rows and columns will shrink to fit their content (auto), and the middle cell of each column will flex so the whole thing takes up all available space. These declarations create a conceptual grid into which to fit elements. All that remains is to assign the elements to the relevant spots of the grid: header { grid-column: 1; grid-row: 1; grid-column-span: 3; } aside.b { grid-column: 1; grid-row: 2; } article { grid-column: 2; grid-row: 2; } aside.d { grid-column: 3; grid-row: 2; } footer { grid-column: 1; grid-row: 3; grid-column-span: 3; }

    Note that unlike with display: table, it’s possible to have elements spanning multiple slots in the layout. This means far less messing around with wrapper elements to control the styling. As with template layouts, you can rearrange the content by modifying the CSS. Here the main content is moved into the top three slots: header { grid-column: 1; grid-row: 2; } aside.b { grid-column: 2; grid-row: 2; }

    The future of CSS layout

    57

    article { grid-column: 1; grid-row: 1; grid-column-span: 3; } aside.d { grid-column: 1; grid-row: 3; grid-column-span: 3; } footer { grid-column: 3; grid-row: 2; } Note that even though grid— align gives you the opportunity to completely separate your markup from the layout, this doesn’t mean you should throw your content into the HTML willy— nilly. Remember that many users of your content, such as screen—reader users and search engines, don’t care too much about the layout you’ve achieved with CSS—your content should make sense in the order it appears in your markup.

    More complex layouts are possible if you nest elements. Adjust the body of your example page to contain the following markup; you’ll then use a nested grid to lay out the content elements: Header Side bar 1

    Content 1 Content 2 Content 3 Content 4 Content 5 Content 6

    Side bar 2 Footer

    The relevant CSS (excluding some rules to add fonts, borders and padding) is shown next. The element this time contains a two-

    58

    CHAPTER 8

    Layout with CSS3

    column layout with four rows, but you also assign a two-column, threerow layout to the element: body { display: grid; grid-columns: auto 1fr; grid-rows: auto 1fr 1fr auto; } div { display: grid; grid-columns: 1fr 1fr; grid-rows: 1fr 1fr 1fr; }

    Now distribute the elements around the grid, making the two rows:

    span

    header { grid-column: 1; grid-row: 1; grid-column-span: 2; } aside:nth-of-type(1) { grid-column: 1; grid-row: 2; } aside:nth-of-type(2) { grid-column: 1; grid-row: 3; } footer { grid-column: 1; grid-row: 4; grid-column-span: 2; } div { grid-column: 2; grid-row: 2; grid-row-span: 2; }

    Because the elements are all children of the element, the row and column references are for the grid defined on the : article { min-height: 2em; } article:nth-child(1) { grid-column: article:nth-child(2) { grid-column: article:nth-child(3) { grid-column: article:nth-child(4) { grid-column: article:nth-child(5) { grid-column: article:nth-child(6) { grid-column:

    1; 2; 1; 2; 1; 2;

    grid-row: grid-row: grid-row: grid-row: grid-row: grid-row:

    1; 1; 2; 2; 3; 3;

    } } } } } }

    See the full listing in ch08/grid-align-3.html. The ability of the grid-based layouts to rearrange content with only CSS makes them an ideal complement to media queries. You’ll now adapt the previous example to make it respond to media queries. Here’s what the layout will look like at lower screen resolutions (see the full listing in ch08/grid-align-4.html).

    The future of CSS layout

    640px width

    59

    480px width

    To start with, define the single-column, small-screen layout: body { display: grid; grid-rows: auto; grid-columns: 1fr; } header { grid-row: 1; } #sidebar { grid-row: 3; } #content1 { grid-row: 2; } #content2 { grid-row: 4; } footer { grid-row: 5; }

    For windows 600 pixels wide and greater, you’ll switch to a twocolumn layout. Note that although the grid can be easily redefined on the body rule, the elements must be explicitly slotted into that grid: @media screen and (min-width: 600px) { body { grid-columns: auto 1fr; grid-rows: auto 1fr 1fr auto; }

    60

    CHAPTER 8

    Layout with CSS3

    header { grid-column: 1; grid-row: 1; grid-column-span: 2; } #sidebar { grid-column: 1; grid-row: 2; grid-rowspan: 2; } #content1 { grid-column: 2; grid-row: 2; } #content2 { grid-column: 2; grid-row: 3; } footer { grid-column: 1; grid-row: 4; grid-column-span: 2; } }

    This CSS defines a three-column grid for windows wider than 760 pixels. Again, the slot locations have to be explicitly set: @media screen and (min-width: 760px) { body { grid-columns: auto 1fr 1fr; grid-rows: auto 1fr auto; } header { grid-column: 1; grid-row: 1; grid-column-span: 3; } #sidebar { grid-column: 1; grid-row: 2; } #content1 { grid-column: 2; grid-row: 2; } #content2 { grid-column: 3; grid-row: 2; } footer { grid-column: 1; grid-row: 3; grid-column-span: 3; } } Grids offer great flexibility in laying out elements on the page and solve nearly all the issues designers had with CSS layouts compared to table—based layouts. But the elements being laid out are still essentially square boxes with a fixed amount of content. In the next section, you’ll learn about a proposal that lets you fit your content into any shape and spread it across multiple elements.

    In print-publishing tools such as Adobe InDesign, it’s common to create several text boxes and then link them together so the content added to them automatically overflows from one box to the next. In this paradigm, text flows automatically from one region of the page to another and from one page to another— you don’t need to calculate how much text will fit in each region. You specify some text and a collection of regions, and the application takes care of the rest.

    Browser support quick check: regions

    Controlling content flow with CSS3 Regions Full

    Partial

    -

    19.0

    -

    -

    -

    10.0

    -

    -

    -

    5.2

    The future of CSS layout

    61

    Adobe is a W3C member and has decided to give similar capabilities to web authors—this fulfills a dual goal of making web layout more powerful for web designers while making it easier for Adobe to generate content straight to the web from its print-publishing tools. To this end they have proposed the CSS3 Regions module. Adobe has helped implement support for their proposal in WebKit, and IE10 also has preliminary support. Here’s an example page layout created with the new Regions module.

    The previous screenshot shows three text boxes. The diagram at right outlines each box explicitly. The content in the boxes flows between them without having to be assigned to one box or another as would normally be required on a web page. The HTML contains four elements. The with id value source contains all the content: a set of four paragraphs.

    I never am really satisfied...

    In almost every computation...

    Many persons who...

    The Analytical Engine...

    62

    CHAPTER 8

    Layout with CSS3

    This is followed by three empty elements, all with a class of region. You’ll flow the content into these three empty elements.







    The magic happens in the CSS. First the source is assigned to flow1. Then the declaration for elements with a class of region says to take the content for these elements from the flow that has just been defined. The remainder of the CSS positions the region elements on the page as shown earlier. Check out ch08/regions1.html file for the full code.

    #source{ flow-into: flow1; text-align:justify; } .region { flow-from: flow1; }

    Making complex shapes with CSS3 Exclusions and Shapes The CSS3 Exclusions specification allows you to wrap content in and around complex shapes. This spec was also born out of Adobe’s proposals; initially it was for shaping the regions now in the CSS3 Regions specification. The following layout can be achieved with a tweak to the

    The future of CSS layout

    63

    CSS from the last example in the previous section. The key difference

    from the previous example is the addition of the properties:

    wrap-shape-mode

    and

    wrap-shape

    .region { flow-from: flow1; wrap-shape-mode: content; wrap-shape: polygon( 0px,160px 20px,232px 40px,262px 60px,282px 80px,296px 100px,305px 120px,313px 140px,316px 160px,320px 180px,316px 200px,313px 220px,305px 240px,296px 260px,282px 280px,262px 300px,232px 320px,160px 300px,90px 280px,52px 260px,34px 240px,20px 220px,10px 200px,4px 180px,1px 160px,0px 140px,1px 120px,4px 100px,10px 80px,20px 60px,34px 40px,52px 20px,90px 0px,160px ); } #region1 { wrap-shape: polygon( 0px,320px 0px,0px 320px,320px 0px,320px ); } #region3 { wrap-shape: polygon( 0px,320px 320px,0px 320px,320px 0px,320px ); }

    The shapes don’t have to contain content—they can also exclude it. This is what the CSS3 Exclusions module is concerned with. The syntax is exactly the same as for Regions, but instead of content flowing into the shapes, the content is flowed around them. In this example, the content is displaying as normal inside the #source element. Then the shapes are absolutely positioned over that content. This is changed by using the around keyword instead of content: wrap-shape-mode: around;

    64

    CHAPTER 8

    Layout with CSS3

    The Exclusions spec is still under heavy development, but it represents some useful additions to the web author’s toolkit. Positioned floats are a concept created by the IE team at Microsoft; they first appeared in IE10 Platform Preview 2. They achieve results similar to the exclusions so they have been folded into the Exclusions spec. To demonstrate, let’s use a simple page with five paragraphs:

    I never am really satisfied...

    In almost every computation...

    Many persons who are not...

    The Analytical Engine has no pretensions...

    The Analytical Engine weaves algebraic patterns...



    Making the last paragraph a positioned float is as simple as setting the both value for the wrap-flow property: p:last-child { width: 200px; position: absolute; wrap-flow: both; top: 75px; left: 250px; }

    All the other text flows around the positioned float. Other possible values are start and end, which allow the text to flow only past the start or end of the object, leaving the other side empty, and minimum and maximum which allow flow only into narrowest or widest sides, respectively.

    Browser support

    65

    In the last example, the floated element looked a bit cramped. You can apply spacing to positioned floats with the wrap-margin property: p:last-child { width: 200px; position: absolute; wrap-flow: both; wrap-margin: 1em; top: 75px; left: 250px; }

    To demonstrate an alternative effect, let’s arrange the other paragraphs into four  columns: p { display: table-cell; }

    You can see that the text still flows around the floated element, even though the four paragraphs are independently positioned.

    Browser support for these new features is still fairly patchy, but they’re worth exploring now so you can be prepared for the future.

    Browser support As discussed in the introduction, browser support for CSS layout has long been an issue. Everything in the CSS2 spec is now implemented in all major browsers: that includes everything discussed in the section “Underused CSS2 layout features.” Support for the other features we’ve discussed is patchier, reflecting the experimental nature of the specifications. The following table shows the details.

    66

    CHAPTER 8

    Layout with CSS3

    12

    14

    4

    6

    8

    9

    10

    11.5

    12

    5

    5.1

    inline-block























    display: table































    calc box-sizing





















    Media queries





















    Flexboxes















    Multiline flexboxes



    Templates/grids



    Regions Exclusions



    Key: ● Complete or nearly complete support ○ Incomplete or alternative support Little or no support

    inline-block in IE6 and IE7 Although IE didn’t add support for inline-block until version 8, it’s possible to achieve the same effect by taking advantage of some nonstandard behavior. IE has an internal concept called hasLayout that endows elements with special properties as far as the layout engine is concerned. For our current purposes, the only thing you need to know is that an element that is display: inline but also hasLayout will behave like an inline-block element in other browsers. One of the simplest ways to trigger hasLayout is to use the IE-specific CSS extension zoom with a value of 1 (which makes no visible difference), coupled with the star hack: display: inline-block; *display: inline; zoom: 1;

    Most browsers will ignore the second two properties as invalid, whereas IE7 and earlier will ignore the first property but process the second two.

    Browser support

    67

    calc in Chrome and Firefox Firefox requires the -moz- prefix for calc while Chrome requires -webkit-. For maximum support, you should specify four rules—one for browsers with no calc support, one for Firefox, and one for standards-compatible browsers (currently only IE): width: width: width: width:

    23%; -moz-calc(100%/4 - 10px); -webkit-calc(100%/4 - 10px); calc(100%/4 – 10px);

    This code sets the element width to 23% in browsers that have no support for calc and 100%/4–10px for any browser that does support it.

    box-sizing in Firefox and Safari 5 Firefox and older versions of Safari require a -moz- prefix for box-sizing: -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;

    If you need to support IE8, because of the significant impact the box model can have on your layout, it’s best to use either IE conditional comments or modernizr.js to provide alternative rules to that browser.

    Flexboxes in Chrome, Firefox, IE, and Safari Currently, prefixes are required in all browsers that support flexboxes. To get maximum support, you need to specify each property four times: div { display: -moz-box; display: -webkit-box; display: -ms-box; display: box; -moz-box-orient: vertical; -webkit-box-orient: vertical; -ms-box-orient: vertical; box-orient: vertical; } div div { -moz-box-flex: 1; -webkit-box-flex: 1; -ms-box-flex: 1; box-flex: 1; }

    68

    CHAPTER 8

    Layout with CSS3

    This code sets the parent element to be a flexbox container and gives any child elements the same amount of flex in all browsers that have support.

    Media queries and old browsers If a browser doesn’t support media queries, then it won’t apply any of the rules listed in a media query section. Any rules outside of a media query section will constitute the default state of your site, so you should always consider the sorts of devices the majority of your users will be browsing with. If your site is primarily for desktop users, then your default styles should be aimed at a desktop-style layout—around 1000 pixels wide and (most likely) using an older version of IE. If your site is more mobile focused, then it would be better to target a small screen by default and add media queries to improve the experience in modern desktop browsers.

    Regions and exclusions Although IE10 and Chrome both have some support for these modules, there are several limitations. In IE10 the source content for flow-into must be an iframe. In Chrome 17–20 you must explicitly enable support for regions in the about:flags page. Neither browser has much support for shaped exclusions, but IE10 does support rectangular exclusions.

    Summary In this chapter, you’ve learned about some of the murky past of CSS layout and how the situation has improved thanks to the gradual decline of old versions of IE, allowing the use of the full range of CSS2 layout tools. New features like box-model, calc, and media queries already have wide support and promise to improve the situation even further. Finally, you’ve glimpsed the bright future of CSS layout, thanks in no small part to the new versions of IE—flexboxes, templates, grids, and exclusions promise to make web page layout much easier and more flexible. To stand out from the crowd, what your web page needS isn’t an elegantly coded three— column layout—you want color, movement, and interactivity. In the next chapter, you’ll start to learn about the flashier aspects of CSS3 as we look at colors, transformations, transitions, and animations.

    Web Components in Action Chapter 9: Shadow CSS

    T

    his chapter is targeted square on CSS in the Shadow DOM--such a big concept that I devote three chapters of my book just to this. Here, I get into why we can use simple CSS by comparing everyday situations without the Shadow DOM vs. with it. A few new CSS selectors come with this new feature as well, and this chapter discusses what they are and why you’d use them.

    Chapter 9

    Chapter 9 from Web Components in Action by Ben Farrell

    Shadow CSS

    This chapter covers  Keeping external style out of your Web Components  The Shadow DOM for CSS encapsulation  Shadow DOM CSS selectors  Rediscovering the ID attribute for Web Components

    Let’s continue on with our Shadow DOM exploration! In the last chapter, we zeroed in on a really nice aspect of the Shadow DOM. As awesome as DOM encapsulation is, the CSS aspect of the Shadow DOM is even better! Despite coming up with clever ways to mitigate style creep in our web development work over the years, it has always been a problem.

    9.1

    Style creep Style creep can sometimes be a bit of a headache in web development work. To sum up, it’s when CSS rules come in and affect elements you didn’t intend to affect. You may be working to style an element in one place, but some style rules you’ve defined in your CSS for another element on your page are unintentionally picked up because the CSS selectors match. Although style creep isn’t limited to Web Components, let’s take a look at a Web Component example to see how it impacts us. 70

    71

    Style creep

    Figure 9.1 shows a simple little Web Component that is essentially a stylized numerical stepper. For this hypothetical use case, let’s say that no matter what the other buttons look like in our web application, it’s important that this stepper be red, and that the plus and minus buttons are flush around the number in the middle. We’re going for a very specific look here, and it needs to be perfect. The next listing shows us how this was achieved.

    Figure 9.1 A stylized stepper component comprising two buttons and a text span

    Listing 9.1 A stepper component without logic, just style





    Sample component on page

    Notice how each style rule in here is prefaced with sample-component. In such a simple example with only one component on the page, specifying .sample-component button isn’t strictly necessary. After all, our component has all of the buttons in the entire page here. Button is such a common element, however, that as soon as we start adding other content to our page, this button style will start affecting that other content. By making the rule specific to our .sample-component, we’re avoiding style from this component leaking out into other elements we didn’t intend. It’s good to have a refresher on how global styles like these work. In figure 9.2, we see that the CSS rules we define in our component become part of the page’s global style space. In turn, these styles will affect any and all elements on our page.

    Web page Global styles to be applied everywhere on page

    CSS rules are pushed globally

    Other DOM elements are affected by all global styles

    Web Component with CSS

    Figure 9.2 Without using the Shadow DOM, style defined in your Web Component will apply to the entire page.

    Style creep

    9.1.1

    73

    Style creep into component descendants Even with this specificity, our button rules could leak the other way as well. What if we had another component within this one with buttons of its own? Those buttons still have somewhere in their ancestry, so the CSS here would creep into any components downstream. It’s inevitable you’ll face some style creep, no matter how specific your selectors are, and you’ll need to debug it. But again, web developers have faced this issue forever. That said, when using Web Components, it’s easier to overlook these kinds of problems because we tend to treat the components we work with as standalone, encapsulated objects and skip over the inner content when scanning the DOM in our debug tools.

    9.1.2

    Style creep into your component So, let’s say you’ve covered all your bases. You’ve carefully planned your class names and CSS rules to be a good component developer and not let your styles leak out of your components. That’s only half the battle—style can still creep into your component from the page and miscellaneous parent components. Let’s pretend your web app is driven by some sort of design system. Design systems, like Bootstrap, define a consistent look and feel in your web pages or applications. For example, you’d likely want most buttons in your application to adopt a single look, like in figure 9.3.

    Figure 9.3 An example globally stylized button that could come from a design system

    With the next listing, we’ll add this button to our page with a simple button element and some page-level CSS to style it. Listing 9.1 A styled button coexisting on our page with a Web Component





    Button from Design System

    Non-component button element

    Looking at the results in figure 9.4, we can already see how the button style is creeping into our component and doing some bad things.

    Figure 9.4 How a global button style can negatively affect our stepper component

    We’re starting to adopt some of the look of the button in our stepper. We have the drop shadow, and the blue gradient backgrounds, which of course don’t match the numeric text in the middle anymore. Things are even more broken when you click the button—the background changes to red. In short, things are getting messy! This is all caused by the generic button styles having just a few different rules than our stepper component button. The stepper’s background color rule is overridden by the generic button’s background rule. And of course, the stepper button shouldn’t have a text shadow or box shadow rule like the generic button does. We’re not even getting into rule specificity here! Pretend that our generic button had a “big-button” variation as well, which just so happens to match the rule name inside our component. Let’s go back and make this variation by increasing the font size and padding of that button to make it a proper “big button.” Our goal is to get something that looks like our previous generic buttons in figures 9.3 and 9.4, just bigger in context.

    Style creep solved with the Shadow DOM

    75

    The reality, however, is that when we define this variation by changing all of our button rules in the CSS outside of the component from button {} to button.bigbutton {}, we get some unexpected results. With more rule specificity like this, and the coincidental naming of “big-button” for both buttons (inside our component and out), we’ve just created a situation in which rules we’ve defined outside of our component are more specific than those within. This really hurts the shape of our stepper buttons, shown in figure 9.5, that we’ve carefully defined with the border-radius rule.

    Figure 9.5 More specificity and samenamed classes wreck the stepper component even more.

    We can fix this, of course. We can add even more specificity in our CSS selectors inside the component, just like we did for the generic button. We can go from button {} to button.big-button {}. Also, though, we have to negate the properties that aren’t covered in our component that are defined in our generic button: sample-component button.big-button { box-shadow: none; text-shadow: none; padding: 0; }

    With these changes, we’re back to our component looking just fine. It’s obvious now that we have to be a little on guard for these types of problems. How much on guard really depends on how much you can control the surrounding application and anticipate how that style could creep in and affect you. The button versus stepper situation would have really been helped if rules for the element as a whole weren’t defined in the global CSS. Creating more unique names would be helpful as well. As much as this sounds like a mess, and it is, it’s something we as web developers have had to deal with forever. All that said, the Shadow DOM promises a fix!

    9.2

    Style creep solved with the Shadow DOM In the last chapter, we saw that creating a shadow root on our component created a separate and independent DOM, where access to this DOM was limited and JS calls couldn’t leak through to change elements or query-select components. When all was said and done, it was super easy! We can protect our Web Component’s DOM in the same way here. With the next listing, we can go back to our stepper component and use the Shadow DOM.

    76

    CHAPTER 9

    Shadow CSS

    Listing 9.2 Using the Shadow DOM to protect our stepper component’s style class SampleComponent extends HTMLElement { Creating a shadow root connectedCallback() { to use the Shadow DOM const root = this.attachShadow({mode: 'open'}); root.innerHTML = `- 5 + `; } }

    Not only did I introduce the Shadow DOM into our stepper component in listing 9.3, but I also got a little overly excited and removed all of my specific rules. My CSS selectors now specify only the rules for the generic and tags. After everything we’ve had to deal with in this example, as well as over the years of CSS pain in web development, this feels lazy and prone to breakage, doesn’t it?

    Style creep solved with the Shadow DOM

    77

    But the point is, now that we have a separate DOM, and we know that our component is this simple, as with our stepper component, we can absolutely style our elements generically here, and it’s perfectly fine! Style won’t creep in, as shown in figure 9.6, and style won’t creep out and affect child components that also use Shadow DOMs.

    Web page Global styles applied everywhere on page

    style rejected

    Other DOM elements

    Web Component using Shadow DOM

    Figure 9.6 Web Components using the Shadow DOM are unaffected by page-level CSS styling

    Listing 9.3 isn’t perfect yet, though. For the most part, figure 9.7 looks OK, but the stepper component has some bad spacing in it.

    Figure 9.7 The stepper component, almost fixed, and living side by side with a globally styled button

    What happened here? Well, our component used to have a display style of flex. The old rule is left in, but it’s not working: sample-component { display: flex; }

    That’s because the tag is now outside of our Shadow DOM. Technically speaking, the tag that represents our component is the shadow host, and this host contains the shadow root, which contains our Shadow DOM. Since CSS can’t

    78

    CHAPTER 9

    Shadow CSS

    leak into the Shadow DOM, this rule using sample-component is now meaningless for what we want to achieve here. Instead, styling the Shadow DOM comes with a few new ways to use CSS selectors. The first is the new selector, :host. The :host selector is shorthand for styling what’s inside the shadow host, as figure 9.8 shows. Changing our selector to :host { display: flex; }

    puts our display: flex rule back in action.

    Web Component my-component { ...style... }

    style rejected

    Shadow host

    Shadow root :host { ...style... }

    Figure 9.8 CSS on the shadow host (or using the component’s tag as the selector) won’t penetrate into the shadow root or into the Shadow DOM.

    9.2.1

    When styles creep There is a bit of nuance to Shadow DOM CSS encapsulation, however. The Shadow DOM works pretty well to guard against outside styles coming into your Shadow DOMguarded component. The nuance is that we’re guarding against style creep when defined by a selector and not overall style. To explain what I mean, let’s try another example in the next listing, where we define some style on the of the page, outside the Shadow DOM. Listing 9.3 Listing 9.4 Text rules affecting inside the Shadow DOM

    Some text styling on

    A span containing  text inside our component’s Shadow DOM

    Apply the text styling to

    the entire page body



    So, what do you expect here? I promised that the Shadow DOM guards against styles coming into your component, yet when the example runs, as seen in figure 9.9, the Figure 9.9 The large, green, bold text indicates that tag contains big, green, bold text! outside style is affecting the This is because the nuance I’m talking about is that contents of our Shadow DOM we’re really guarding against CSS selectors from the outside being able to latch onto classes on the inside. Yet when an ancestor of your component (Shadow DOM or no) has some style applied to it that doesn’t require selecting anything inside your component, that style will still affect the children. Now, if we removed that text class from the body as so,

    and put that same class on the inside our component like this, root.innerHTML = `Some Text`;

    you’ll see that the text style has no effect, as show in figure 9.10. The “text” selector from our example can’t penetrate Figure 9.10 When we place the class directly on the span the Shadow DOM, yet those same rules as a style from tag, the Shadow DOM the outside can. However, even something as simple as successfully blocks the style. an outside style won’t creep in in the same way because “button” is still a selector (albeit a generic one). This can be pretty useful and makes a lot of sense. If all the text on your overall page is styled a certain way, or your page has a specific background color, you don’t want your components to depart from these basic styles.

    80

    CHAPTER 9

    Shadow CSS

    What if you didn’t want even that style to creep in? We can do a bit of a trick with the :host selector in the next listing. Listing 9.4 Resetting the style in the Shadow DOM

    While we certainly could set each individual style rule to “initial” to reset them, it’s more encompassing to reset everything in our shadow root using the all CSS property and the brand new :host selector. To go beyond the :host selector and explore a little more, let’s start a new demo project to properly give the Shadow DOM a try!

    9.3

    Shadow DOM workout plan So, this demo has a bit of a dual meaning. Yes, we will be going through some Shadow DOM exercises to introduce some new concepts, but the demo we’ll be making is also an exercise browser and workout creator. The final product in this chapter won’t be as interactive as it could be, and that’s because we’ll keep exploring this demo in chapter 14 as we cover events to implement the rest of the functionality. For this chapter, we’ll end up with an exercise library on the left and your custom workout plan on the right, as in figure 9.11. Clicking each exercise in the library will add it to your plan. Exercise types are either “strength” or “cardio” and are represented by the blue or green stripe, respectively. To keep things simple on the page, and because I don’t personally own a bunch of exercise videos to share with you, my thumbnails and backgrounds are gray. However, in this book’s Github repo, I’ve included GIF links in my data model, defined in components/exerciselibrary/exerciselibrary.js, so that each exercise renders with a motion thumbnail that will let you properly preview the exercise.

    Shadow DOM workout plan

    Figure 9.11

    9.3.1

    81

    A demo app to browse exercises from a library and create a custom workout plan

    Application shell As a first step, let’s create the overall application structure along with some placeholders for child components. Specifically, we’ll create an HTML page, CSS file, and component, where the file structure looks like figure 9.12. If you are following along, please remember to use some sort of simple web server, given that we do have dependencies loaded from Figure 9.12 Basic file structure as we start our our index.html that may not work just demo application using the file system. As with our other demos, our index.html will be extremely simple, as in the following listing. Listing 9.5 The index.html for our demo application

    Component import Workout Creator



    82

    CHAPTER 9

    Shadow CSS



    Component declared in HTML

    Our CSS is even simpler in the next listing, and is just negating the margin and padding of the page body while sizing the to the entirety of the page with a bit of padding. Listing 9.6 The main.css for our demo application body { margin: 0; padding: 0; }

    Resetting margin and padding on page

    wkout-creator-app { height: calc(100vh - 20px); padding: 10px; }

    Sizing the application to take up the entire page

    For the itself, the component’s code, shown in the next listing, is also very simple. Listing 9.7 The main application component for our demo application import Template from './template.js'; export default class WorkoutCreatorApp extends HTMLElement { constructor() { super(); this.attachShadow({mode: 'open'}); Using the Shadow DOM this.shadowRoot.innerHTML = Template.render(); in our component } } if (!customElements.get('wkout-creator-app')) { customElements.define('wkout-creator-app', WorkoutCreatorApp ); }

    Note that, unlike in past demos, we are now using the Shadow DOM. Also, unlike what we did earlier in this book, we are doing all of our component setup in the constructor and directly using the shadowRoot property to access our local Shadow DOM. Lastly, I’m going to be using some Shadow DOM CSS features as well as doing some things you’d never do without the Shadow DOM. Neither of these are easy to back out of! So, here I’m going all in on the Shadow DOM with no turning back.

    9.3.2

    Host and ID selectors Continuing on from our WorkoutCreatorApp module that defines the component, let’s take a peek at the template.js module that holds our HTML and CSS in the next listing.

    83

    Shadow DOM workout plan Listing 9.8 Application template module that defines our HTML and CSS export default { render() { return `${this.css()} ${this.html()}`; },

    Left container for the html() { exercise library return `

    Divider line with `; an ID attribute }, css() { return ``; } }

    First off, we’re creating three child elements. Two of them are components that aren’t defined yet, so they’ll just be rendered as empty elements, which are styled with a background color, so we can visualize their placement thus far, as figure 9.13 shows. In the middle of these two sits a black divider line. Even with just this, we have two points to discuss with the Shadow DOM. First, we’re using the previously mentioned :host CSS selector to assign some style to our host component. In this case, we simply want to use a display type of “flex” to lay out our three elements. The second point is an important one. It sounds like a small point, but it’s actually kind of huge. Our divider line is assigned the ID of “divider-line”: . We then use this ID to assign style with CSS: #divider-line {}. Why is this so important? Well, ingrained in every web developer is that we should use the ID attribute sparingly. The reason is that there can be only one element with that ID in your entire DOM. If you make a mistake and assign a second element with

    84

    CHAPTER 9

    Figure 9.13

    Shadow CSS

    How our barebones application looks so far in a browser

    the same ID, you’re bound to get CSS or query-selection problems when you’re only able to select or style one of the multiple elements with the same ID. Typically, our selectors will be multiple classes together to get the specificity required to accurately select or style an element. For our divider line, we might use a CSS selector that looks like wkout-creator-app div.divider-line.center.thin {}

    Yes, I got a little ridiculous with the selector just now using .center and .thin, but I’m just trying to underscore the point of overdoing the specificity, which is usually needed. Now, however, we can use the Shadow DOM. Coming back to the point that each ID in your entire DOM must be unique, remember we’re now using multiple DOMs! Your ID needs to be unique only inside the scope of your Web Component. An element with an ID of #divider could easily exist elsewhere on the page or in other Web Components, and there would be no conflict. Even better, given that there are only three elements in this Web Component with just the divider line using a tag, we could easily not bother with an ID altogether, instead using a selector like this: div {}. Personally, I think this is really exciting. Coming back to when I introduced the Shadow DOM in the last chapter, I quoted that it removes the brittleness of web development. This is a prime example. We can focus on the structure and style of our component and not worry about conflicts anywhere else. Our selectors can be as dead simple and easy to read as our component’s internal structure allows.

    85

    Shadow DOM workout plan

    9.3.3

    Grid and list containers We’re going to continue on now with more of same concepts we just explored in order to get a grid of exercises and our workout plan list in place. That’s two more components, which makes our project structure look like figure 9.14.

    New exercise library component

    New workout plan component

    Previously added Workout Creator app component

    Figure 9.14 Project file structure as our two container components are added for the exercise library and workout plan

    Remember, we are actually rendering those and components already in the application component; it’s just that they aren’t defined yet, so they just render as elements. As such, our first step after creating the new files and folders for the components is to import those modules at the head of workoutcreatorapp/template.js: import ExerciseLibrary from '../exerciselibrary/exerciselibrary.js'; import Plan from '../plan/plan.js';

    With those defined, let’s get to work fleshing these components out! Both are pretty simple, in fact. This is largely due to us not paying any attention to interactivity yet. The next listing shows our plan/plan.js and plan/template.js files. Listing 9.9 Workout plan component files // Plan.js import Template from './template.js'; export default class Plan extends HTMLElement { constructor() { super(); this.attachShadow({mode: 'open'});

    86

    CHAPTER 9

    Shadow CSS

    this.shadowRoot.innerHTML = Template.render(); }

    Assigning HTML/CSS to our component

    } if (!customElements.get('wkout-plan')) { customElements.define('wkout-plan', Plan); } // Template.js export default { render() { return `${this.css()} ${this.html()}`; },

    HTML to render html() { return `My Plan

    Total Time:`; }, CSS to render css() { return ``; }, }

    Since our workout plan list is empty at the start of the application, we aren’t rendering anything except the container, header text, and a footer to show total plan duration. Again, we’re using a Shadow DOM, which enables us to use element IDs to target both the time and container tags for styling. On both of these, we’re just setting sizing and background fill color, as well as telling our exercise list container to scroll when it gets too tall. Also again, we’re using the :host selector to tell our component’s shadow root to display using a vertical flexbox. The component is similar, except we actually do want to feed it with data. The purpose of this component is to show a list of exercises to choose from, so they should all be present when the application loads. As such, we’ll

    87

    Shadow DOM workout plan

    be rendering a header and container, just like the last component, but we’ll also be populating the container with all of our exercises. The next listing shows exerciselibrary/exerciselibrary.js and exerciselibrary/template.js. Listing 9.10 Exercise library component files // exerciselibrary.js import Template from './template.js';

    Component definition module for the exercise library

    export default class ExerciseLibrary extends HTMLElement { constructor() { super(); this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = Template.render([ { label: 'Jump Rope', type: 'cardio', thumb: '', time: 300, sets: 1}, { label: 'Jog', type: 'cardio', thumb: '', time: 300, sets: 1}, { label: 'Pushups', type: 'strength', thumb: '', count: 5, sets: 2, estimatedTimePerCount: 5 }, { label: 'Pullups', type: 'strength', thumb: '', count: 5, sets: 2, estimatedTimePerCount: 5}, { label: 'Chin ups', type: 'strength', thumb: '', count: 5, sets: 2, estimatedTimePerCount: 5}, { label: 'Plank', type: 'strength', thumb: '', time: 60, sets: 1} ]); } } if (!customElements.get('wkout-exercise-lib')) { customElements.define('wkout-exercise-lib', ExerciseLibrary); } // template.js Template module for the export default { exercise library, which render(exercises) { holds our HTML and CSS return `${this.css()} ${this.html(exercises)}`; }, html(exercises) { let mkup = `Exercises `; for (let c = 0; c < exercises.length; c++) { mkup += ``; } return mkup + ``;

    Looping through exercises  and rendering them

    }, css() { return ``; } }

    You’ll notice right away the big list of exercises we’re feeding into the Template .render function. Each exercise has a label as well as a type of either “cardio” or “strength.” Depending on whether you count each rep or just do the exercise for a set amount of time, the exercise will have a number for “count” and “sets” or for “time.” If we’re tracking count and sets, the only way we can estimate the total time of our workout is to estimate how much time each single rep of our exercise takes, so we use another property called estimatedTimePerCount. Lastly, there is an empty thumb property on each exercise. Like I said at the beginning of this chapter, we’ll just leave this blank to not show a thumbnail in this book. You can search for your own images or GIFs online to populate these or just look at the Github repo for this book for ones I’ve found. Also, in my Github repo are more exercises for our data model. Our exerciselibrary/template.js file is mostly the same as the previous plan/template.js. Of course, the main difference is that we’re accepting the list of exercises and rendering each one. Again, we’re waiting to define the for now while we focus on everything else, which gives us something that looks like figure 9.15.

    Figure 9.15

    Filling in the components on the left and right sides of the application

    89

    Adaptable components

    You’ll notice that even though we’ve rendered our exercises, they aren’t showing up. That’s because even though they are there in the DOM, they don’t have a size or background—so, despite being present, they have a zero-pixel height and don’t show visually. We’ll address this with the component. It is the last one to cover, and it’s actually pretty interesting.

    9.4

    Adaptable components Why do I find this so interesting? Well, it’s because we’re going to start on a component that needs to look slightly different depending on how it’s used, and we’ll learn an alternate way of using the :host selector. In the next chapter, we’ll push even further on this adaptable component to make it look completely different in the workout plan container.

    9.4.1

    Creating the exercise component Since our workout plan needs some interactivity to function, lets focus instead on the exercise library first, as it’s easier to iterate on style for something that appears on page load instead of requiring the extra step of clicking to add. We’re, of course, going to need to create the component files and end up with the file structure shown in figure 9.16.

    Exercise component

    Exercise library component Workout plan component

    Workout Creator app component Main HTML/CSS pages Figure 9.16

    Final file structure for the application

    90

    CHAPTER 9

    Shadow CSS

    Since both the workout plan and exercise library render the exercise component, we should place that import into both plan/template.js and exerciselibrary/template.js modules. import Exercise from '../exercise/exercise.js';

    Let’s take a look at the Web Component definition for in the following listing. Listing 9.11 Component files for the exercise component import Template from './template.js'; export default class Exercise extends HTMLElement { constructor() { super(); this.attachShadow({mode: 'open'}); const params = { label: this.getAttribute('label'), type: this.getAttribute('type'), thumb: this.getAttribute('thumb'), time: this.getAttribute('time'), count: this.getAttribute('count'), estimatedTimePerCount: this.getAttribute('estimatedtimepercount'), sets: this.getAttribute('sets'), }; this.shadowRoot.innerHTML = Template.render(params); } get label() { return this.getAttribute('label'); } set label(val) {

    Getters/setters for each property

    this.setAttribute('label', val); }

    // more getters/setters for thumb, type, time, count, estimateTimePerCount, and sets serialize() { Function to serialize all return { properties into an object label: this.label, type: this.type, thumb: this.thumb, time: this.time, count: this.count, estimatedTimePerCount: this.estimatedTimePerCount, sets: this.sets, } Function to assemble an }

    attribute string for a cloned

    exercise component static toAttributeString(obj) { let attr = ''; for (let key in obj) { if (obj[key]) { attr += key + '="' + obj[key] + '" '; } } return attr;

    Adaptable components

    91

    } } if (!customElements.get('wkout-exercise')) { customElements.define('wkout-exercise', Exercise); }

    To save space here, I’ve eliminated all but one of my getters/setters. In this component definition, we’re employing something we picked up in chapter 3. We’re using reflection to use attributes and properties interchangeably. We can either element .setAttribute(property, value) on the element or element.property = value to set a property. Either way, we’re getting or setting some data that is internally based on the element’s attribute. If I didn’t cut it short for brevity, we’d have getters/setters for thumb, type, time, count, estimateTimePerCount, and sets as well. The other two methods are ways to gather our data. First, we have serialize, which just assembles our data into one object we can pass around easily. The other static method, toAttributeString, is similar. It assembles all of our data like serialize does but creates a string that we can use to populate attributes. We’ll end up with a string in the format of property=”value” property2=”value2” property3=”value3”

    This extra method might not seem necessary, but we want to weed out those undefined properties. Remember that because of the variation of the exercises, some will have a rep count property, like when you lift weights, while others will have a duration property, like when you’re jogging. So rather than having property=”undefined” be an attribute on our tag when the actual undefined value gets converted to a string, or having to check for undefined on each property in our templates, making them a bit long and hard to read, this is a good alternative. All this is to explain why in exerciselibrary/template.js, we’ll modify our loop in the html() function to be for (let c = 0; c < exercises.length; c++) { mkup += ``; }

    With this, we can create attributes on our new element for each and every valid property in our data. As this is a static method (accessed from the class rather than the instance), we can use it either on the raw data objects we have in exerciselibrary/exerciselibrary.js before the component is created or against an already created component to copy those values. Whether a simple object or component, the properties are all there and can be used the same way by this method. The tag we get in the end looks like either of the following, depending on the exercise:

    92

    9.4.2

    CHAPTER 9

    Shadow CSS

    Exercise component style With all of the attributes we need set on the component, and the component definition created, there’s just one last thing to do: create the HTML and CSS seen in the next listing. Listing 9.12 First pass of exercise component export default { render(exercise) { return `${this.css(exercise)} ${this.html(exercise)}`; }, html(exercise) { return ` ${exercise.label} x `; },

    Styling the overall css(exercise) { component return ``; } }

    With all of this now put together, our component renders all of the components we have. Seen in figure 9.17, the first minor thing to notice is our component backgrounds: background: radial-gradient(circle, rgba(235,235,235,1) 0%, rgba(208,208,208,1) 100%); /*background-image: url('${exercise.thumb}');*/

    Figure 9.17

    Newly styled exercise components

    I’ve commented out the background image, but if you’ve searched online and found some great thumbnails for each exercise and added them to the data in the component, feel free to uncomment this line. If you didn’t, we’re simply showing a gradient gray background. Notice as well how simple the HTML is. We’re showing a 200 × 200 box with a black label at the top. This is fine for the library view, but you might imagine that this could all be a little problematic to display as a list view in the exercise plan. Again, we’re using some concepts we’ve covered before in this chapter. We’re identifying and selecting elements using the ID attribute as well as using the :host selector for our component’s shadow root context.

    94

    CHAPTER 9

    Shadow CSS

    Note, however, that we have a small variation on the :host selector: :host(.cardio) { border-left-color: #28a7ff; } :host(.strength) { border-left-color: #75af01; }

    Back when rendering each of these components, we did put a class of strength or cardio on each component: mkup += ``;

    This variation on the :host selector allows us to consider any classes on the component’s tag itself and use that for more CSS specificity. To be clearer and more concise, :host(.cardio) enables us to style the element