Slide 1

Slide 1 text

+Eric Bidelman @ebidel June 20 - 21, 2016 Progressively Enhanced Markup using web components to build PWAs

Slide 2

Slide 2 text

@ebidel Digital Jedi, Google Eric Bidelman

Slide 3

Slide 3 text

@ebidel Digital Jedi, Google Eric Bidelman

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

a set of emerging standards that allow developers to extend HTML and its functionality. WEB COMPONENTS

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

thistock.im BROWSER SUPPORT?

Slide 9

Slide 9 text

thistock.im BROWSER SUPPORT?

Slide 10

Slide 10 text

SHADOW DOM CUSTOM ELS el.createShadowRoot() document.registerElement()

Slide 11

Slide 11 text

SHADOW DOM CUSTOM ELS el.createShadowRoot() v0 document.registerElement()

Slide 12

Slide 12 text

SHADOW DOM CUSTOM ELS el.createShadowRoot() v0 document.registerElement() customElements.define() el.attachShadow({mode:'open'}) v1

Slide 13

Slide 13 text

SHADOW DOM CUSTOM ELS el.createShadowRoot() v0 document.registerElement() customElements.define() el.attachShadow({mode:'open'}) v1 HTML IMPORTS No changes:

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

LOVE IT. flickr.com/photos/krissen/6340984211

Slide 21

Slide 21 text

unsplash.com BROWSER SUPPORT MATTERS

Slide 22

Slide 22 text

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

...content...
… WEB COMPONENTS

Slide 26

Slide 26 text

INTEGRATED INTO THE PLATFORM

Slide 27

Slide 27 text

INTEGRATED INTO THE PLATFORM

Slide 28

Slide 28 text

INTEGRATED INTO THE PLATFORM

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

#UseThePlatform

Slide 32

Slide 32 text

WARNING

Slide 33

Slide 33 text

showingCode(); WARNING

Slide 34

Slide 34 text

CUSTOM ELEMENTS https://html.spec.whatwg.org/multipage/scripting.html#custom-elements

Slide 35

Slide 35 text

LET’S BUILD A BUTTON!

Slide 36

Slide 36 text

‣ Focus/keyboard behavior ‣ disabled attribute ‣ Built-in a11y features ‣ Participates in submission

Slide 37

Slide 37 text

‣ Focus/keyboard behavior ‣ disabled attribute ‣ Built-in a11y features ‣ Participates in submission

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Fancy button!
TODAY

Slide 41

Slide 41 text

Fancy button!
TODAY .better-button { min-width: 5.14em; margin: 0 0.29em; text-transform: uppercase; border-radius: 3px; cursor: pointer; padding: 0.7em 0.57em; … } .better-button.disabled { background: #eaeaea; color: #a8a8a8; cursor: auto; pointer-events: none; box-shadow: none; } .better-button.raised:not(.disabled), .better-button:not(.disabled):hover { box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),...; }

Slide 42

Slide 42 text

Fancy button!
.better-button { min-width: 5.14em; margin: 0 0.29em; text-transform: uppercase; border-radius: 3px; cursor: pointer; padding: 0.7em 0.57em; … } .better-button.disabled { background: #eaeaea; color: #a8a8a8; cursor: auto; pointer-events: none; box-shadow: none; } .better-button.raised:not(.disabled), .better-button:not(.disabled):hover { box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),...; } TODAY

Slide 43

Slide 43 text

const button = document.querySelector('.better-button'); button.addEventListener('click', e => { if (this.hasAttribute('disabled')) { e.preventDefault(); e.stopPropagation(); } // Draw ripple animation. });
Fancy button!
TODAY

Slide 44

Slide 44 text

const button = document.querySelector('.better-button'); button.addEventListener('click', e => { if (this.hasAttribute('disabled')) { e.preventDefault(); e.stopPropagation(); } // Draw ripple animation. });
Fancy button!
TODAY

Slide 45

Slide 45 text

WE CAN DO BETTER

Slide 46

Slide 46 text

better-button { min-width: 5.14em; margin: 0 0.29em; text-transform: uppercase; border-radius: 3px; cursor: pointer; padding: 0.7em 0.57em; … } better-button[disabled] { background: #eaeaea; color: #a8a8a8; cursor: auto; pointer-events: none; box-shadow: none; } better-button[raised]:not([disabled]), better-button:not([disabled]):hover { box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),...; } Fancy button! Fancy button!

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

PROGRESSIVELY ENHANCE

Slide 49

Slide 49 text

class BetterButton extends HTMLElement { } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } } // Do the same for the raised property.

Slide 50

Slide 50 text

class BetterButton extends HTMLElement { } static get observedAttributes() { return [‘disabled’]; } attributeChangedCallback(name, oldValue, newValue) { // only called for the disabled attr due to observedAttributes if (this.disabled) { this.setAttribute('tabindex', '-1'); this.setAttribute('aria-disabled', 'true'); } else { this.setAttribute('tabindex', '0'); this.setAttribute('aria-disabled', 'false'); } } Fancy button!

Slide 51

Slide 51 text

class BetterButton extends HTMLElement { } constructor() { super(); this.addEventListener('keydown', e => { if (e.keyCode === 32 || e.keyCode === 13) { this.dispatchEvent( new MouseEvent('click', {bubbles: true, cancelable: true})); } }); this.addEventListener('click', e => { if (this.disabled) { e.preventDefault(); e.stopPropagation(); } this.drawRipple(e.offsetX, e.offsetY); }); }

Slide 52

Slide 52 text

class BetterButton extends HTMLElement { } connectedCallback() { this.setAttribute('role', 'button'); this.setAttribute('tabindex', '0'); } disconnectedCallback() { ... }

Slide 53

Slide 53 text

class BetterButton extends HTMLElement { } drawRipple(x, y) { let div = document.createElement('div'); div.classList.add('ripple'); this.appendChild(div); div.style.top = `${y - div.clientHeight/2}px`; div.style.left = `${x - div.clientWidth/2}px`; // Make ripple color same as text color. div.style.backgroundColor = getComputedStyle(this).color; div.classList.add(‘run'); div.addEventListener( 'transitionend', e => div.remove()); } better-button .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.8; transition: all 800ms cubic-bezier(0.4,0,0.2,1); border-radius: 50%; width: 150px; height: 150px; will-change: opacity, transform; contain: content; } better-button .ripple.run { opacity: 0; transform: none; }

Slide 54

Slide 54 text

class BetterButton extends HTMLElement { } drawRipple(x, y) { let div = document.createElement('div'); div.classList.add('ripple'); this.appendChild(div); div.style.top = `${y - div.clientHeight/2}px`; div.style.left = `${x - div.clientWidth/2}px`; // Make ripple color same as text color. div.style.backgroundColor = getComputedStyle(this).color; div.classList.add(‘run'); div.addEventListener( 'transitionend', e => div.remove()); } better-button .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.8; transition: all 800ms cubic-bezier(0.4,0,0.2,1); border-radius: 50%; width: 150px; height: 150px; will-change: opacity, transform; contain: content; } better-button .ripple.run { opacity: 0; transform: none; }

Slide 55

Slide 55 text

class BetterButton extends HTMLElement { } drawRipple(x, y) { let div = document.createElement('div'); div.classList.add('ripple'); this.appendChild(div); div.style.top = `${y - div.clientHeight/2}px`; div.style.left = `${x - div.clientWidth/2}px`; // Make ripple color same as text color. div.style.backgroundColor = getComputedStyle(this).color; div.classList.add(‘run'); div.addEventListener( 'transitionend', e => div.remove()); } better-button .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.8; transition: all 800ms cubic-bezier(0.4,0,0.2,1); border-radius: 50%; width: 150px; height: 150px; will-change: opacity, transform; contain: content; } better-button .ripple.run { opacity: 0; transform: none; } BOOM

Slide 56

Slide 56 text

class BetterButton extends HTMLElement { } drawRipple(x, y) { let div = document.createElement('div'); div.classList.add('ripple'); this.appendChild(div); div.style.top = `${y - div.clientHeight/2}px`; div.style.left = `${x - div.clientWidth/2}px`; // Make ripple color same as text color. div.style.backgroundColor = getComputedStyle(this).color; div.classList.add(‘run'); div.addEventListener( 'transitionend', e => div.remove()); } better-button .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.8; transition: all 800ms cubic-bezier(0.4,0,0.2,1); border-radius: 50%; width: 150px; height: 150px; will-change: opacity, transform; contain: content; } better-button .ripple.run { opacity: 0; transform: none; } BOOM

Slide 57

Slide 57 text

class BetterButton extends HTMLElement { ... } window.customElements.define('better-button', BetterButton); Register the element Fancy button! let button = new BetterButton(); let button = document.createElement(‘better-button’); button.disabled = true; button.raised = true; Create instances

Slide 58

Slide 58 text

better-button { box-sizing: border-box; min-width: 5.14em; margin: 0 0.29em; font: inherit; text-transform: uppercase; outline-width: 0; border-radius: 3px; -moz-user-select: none; -ms-user-select: none; -webkit-user-select: none; user-select: none; cursor: pointer; padding: 0.7em 0.57em; transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); display: inline-block; overflow: hidden; position: relative; /* Fixes rendering issue where chrome doesn't properly clip parent

Slide 59

Slide 59 text

this.setAttribute('tabindex', '-1'); this.setAttribute('aria-disabled', 'true'); } else { this.setAttribute('tabindex', '0'); this.setAttribute('aria-disabled', 'false'); } } drawRipple(x, y) { let div = document.createElement('div'); div.classList.add('ripple'); this.appendChild(div); div.style.top = `${y - div.clientHeight/2}px`; div.style.left = `${x - div.clientWidth/2}px`; div.style.backgroundColor = window.getComputedStyle(this).color; div.classList.add('run'); div.addEventListener('transitionend', e => div.remove()); } } window.customElements.define('better-button', BetterButton);

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

PROGRESSIVELY ENHANCE

Slide 62

Slide 62 text

‣ Focus/keyboard behavior ‣ disabled attribute ‣ Built-in a11y features ‣ Participates in submission

Slide 63

Slide 63 text

‣ Focus/keyboard behavior ‣ disabled attribute ‣ Built-in a11y features ‣ Participates in submission

Slide 64

Slide 64 text

class BetterButton extends HTMLButtonElement { constructor() { super(); this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY)); } drawRipple(x, y) { ... } } customElements.define('better-button', BetterButton, {extends: 'button'}); Fancy button! document.createElement('button', {is: 'better-button'})

Slide 65

Slide 65 text

class BetterButton extends HTMLButtonElement { constructor() { super(); this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY)); } drawRipple(x, y) { ... } } customElements.define('better-button', BetterButton, {extends: 'button'}); Fancy button! document.createElement('button', {is: 'better-button'})

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

Focus/keyboard behavior disabled attribute Built-in a11y features Participates in

Slide 68

Slide 68 text

ASIDE customElements.define(‘better-button', …);

Slide 69

Slide 69 text

ELEMENT UPGRADES ASIDE

Slide 70

Slide 70 text

ELEMENT UPGRADES ASIDE HTMLElement FancyButtonElement

Slide 71

Slide 71 text

CUSTOM ELEMENTS PROGRESSIVELY ENHANCED MARKUP ARE ASIDE

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

( WPT: Nexus 5 - Chrome - 3G ) 2.2s first paint CODELABS SITE

Slide 77

Slide 77 text

( WPT: Nexus 5 - Chrome - 3G ) 2.2s first paint CODELABS SITE

Slide 78

Slide 78 text

paper-tabs:not(:defined) { /* Pre-style, give layout,replicate internal styles of paper-tabs */ display: block; height: 48px; opacity: 0; transition: opacity 0.3s ease-in-out; } :defined - pre-style components

Slide 79

Slide 79 text

paper-tabs:not(:defined) { /* Pre-style, give layout,replicate internal styles of paper-tabs */ display: block; height: 48px; opacity: 0; transition: opacity 0.3s ease-in-out; } :defined - pre-style components

Slide 80

Slide 80 text

Welcome to Codelabs!

Google Codelabs provide a guided, tutorial, hands-on coding...

Slide 81

Slide 81 text

Welcome to Codelabs!

Google Codelabs provide a guided, tutorial, hands-on coding...

PopularRecent...

Slide 82

Slide 82 text

Welcome to Codelabs!

Google Codelabs provide a guided, tutorial, hands-on coding...

PopularRecent...
55 min
Build a Progressive Web App with Firebase…
Start Updated 2016-05-25
...

Slide 83

Slide 83 text

Welcome to Codelabs!

Google Codelabs provide a guided, tutorial, hands-on coding...

PopularRecent...
55 min
Build a Progressive Web App with Firebase…
Start Updated 2016-05-25
...

Slide 84

Slide 84 text

What we’ve done… customElements.define('better-button', class extends HTMLButtonElement { ... }, {extends: 'button'});

Slide 85

Slide 85 text

What we’ve done… customElements.define('better-button', class extends HTMLButtonElement { ... }, {extends: 'button'});

Slide 86

Slide 86 text

SHADOW DOM https://w3c.github.io/webcomponents/spec/shadow/

Slide 87

Slide 87 text

Do Re Mi Fa So

Slide 88

Slide 88 text

Do Re Mi Fa So

Slide 89

Slide 89 text

Slide 90

Slide 90 text

Slide 91

Slide 91 text

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

SHADOW DOM

Slide 94

Slide 94 text

class BetterButton extends HTMLElement { constructor() { ... var shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.innerHTML = ` :host { ... } .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.6; transition: all 800ms cubic-bezier(0.4, 0, 0.2, 1); ... } ...
`; } }

Slide 95

Slide 95 text

class BetterButton extends HTMLElement { constructor() { ... var shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.innerHTML = ` :host { ... } .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.6; transition: all 800ms cubic-bezier(0.4, 0, 0.2, 1); ... } ...
`; } }

Slide 96

Slide 96 text

class BetterButton extends HTMLElement { constructor() { ... var shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.innerHTML = ` :host { ... } .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.6; transition: all 800ms cubic-bezier(0.4, 0, 0.2, 1); ... } ...
`; } } Styles are scoped to a shadow root. Selectors don’t leak out or collide with outside rules.

Slide 97

Slide 97 text

class BetterButton extends HTMLElement { constructor() { ... var shadowRoot = this.attachShadow({mode: 'open'}); shadowRoot.innerHTML = ` :host { ... } .ripple { position: absolute; transform: scale3d(0,0,0); opacity: 0.6; transition: all 800ms cubic-bezier(0.4, 0, 0.2, 1); ... } ...
`; } } Styles are scoped to a shadow root. Selectors don’t leak out or collide with outside rules.

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

Composition with

Slide 101

Slide 101 text

Composition with :host { ... }

Slide 102

Slide 102 text

Composition with :host { ... } Fancy button!

Slide 103

Slide 103 text

Composition with :host { ... } Fancy button!

Slide 104

Slide 104 text

Composition with :host { ... } :host { ... } Button Fancy button!

Slide 105

Slide 105 text

Composition with :host { ... } :host { ... } Button Fancy button!

Slide 106

Slide 106 text

Composition with :host { ... } :host { ... } Button Fancy button!

Slide 107

Slide 107 text

Composition with :host { ... } :host { ... } ::slotted(img) {height: 24px;} :host { ... } Button Fancy button!

Slide 108

Slide 108 text

Composition with :host { ... } :host { ... } ::slotted(img) {height: 24px;} :host { ... } Button Fancy button! Settings

Slide 109

Slide 109 text

Composition with :host { ... } :host { ... } ::slotted(img) {height: 24px;} :host { ... } Button Fancy button! Settings

Slide 110

Slide 110 text

No content

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

Style hooks using CSS Custom properties Placeholders in Shadow DOM: :host { padding: var(--button-padding, 0.7em); } .ripple { --default-size: 150px; width: var(--ripple-size, var(--default-size)); height: var(--ripple-size, var(--default-size)); }

Slide 113

Slide 113 text

Style hooks using CSS Custom properties Placeholders in Shadow DOM: better-button { background-color: #E91E63; font-size: 40px; --button-padding: 2em; --ripple-size: 300px; } Ginormous button! User provides styles: :host { padding: var(--button-padding, 0.7em); } .ripple { --default-size: 150px; width: var(--ripple-size, var(--default-size)); height: var(--ripple-size, var(--default-size)); }

Slide 114

Slide 114 text

RECAP

Slide 115

Slide 115 text

We taught the browser new HTML!

Slide 116

Slide 116 text

‣ Created reusable component w/o libs ‣ Progressively enhanced from ‣ Built-in A11y & keyboard behavior (CE) ‣ Self-contained DOM / CSS (SD) ‣ Has an imperative JS API (CE) ‣ Has a declarative API (CE/SD) ‣ Configurable styling (CSS custom props) We taught the browser new HTML!

Slide 117

Slide 117 text

unsplash.com IT’S A BEAUTIFUL FUTURE

Slide 118

Slide 118 text

elements.polymer-project.org customelements.io

Slide 119

Slide 119 text

+Eric Bidelman @ebidel github.com/ebidel thistock.im