Slide 1

Slide 1 text

Caffeinated Style Sheets Tommy Hodgins @innovati

Slide 2

Slide 2 text

Caffeinated Style Sheets

Slide 3

Slide 3 text

Caffeinated Style Sheets

Slide 4

Slide 4 text

{ CSS extended by JS }

Slide 5

Slide 5 text

CSS R&D 2014 – 2018

Slide 6

Slide 6 text

Big Takeaways

Slide 7

Slide 7 text

Big Takeaways • CSS can be extended in the browser by JavaScript

Slide 8

Slide 8 text

Big Takeaways • CSS can be extended in the browser by JavaScript • Many desired CSS features are simple to implement with JavaScript

Slide 9

Slide 9 text

Big Takeaways • CSS can be extended in the browser by JavaScript • Many desired CSS features are simple to implement with JavaScript • There are 25–50 useful design techniques these features allow

Slide 10

Slide 10 text

Big Takeaways • CSS can be extended in the browser by JavaScript • Many desired CSS features are simple to implement with JavaScript • There are 25–50 useful design techniques these features allow • Styling is event-driven in nature, and bigger than CSS

Slide 11

Slide 11 text

Big Takeaways • CSS can be extended in the browser by JavaScript • Many desired CSS features are simple to implement with JavaScript • There are 25–50 useful design techniques these features allow • Styling is event-driven in nature, and bigger than CSS • You don’t need a custom CSS syntax to extend CSS

Slide 12

Slide 12 text

Think of your stylesheet as a function of what’s going on in the browser

Slide 13

Slide 13 text

Think of your stylesheet as a function of what’s going on in the browser

Slide 14

Slide 14 text

Not a software framework, a mental framework

Slide 15

Slide 15 text

Not a software framework, a mental framework

Slide 16

Slide 16 text

The JS-in-CSS Recipe

Slide 17

Slide 17 text

The JS-in-CSS Recipe • The stylesheet is a JS function that returns a string of CSS

Slide 18

Slide 18 text

The JS-in-CSS Recipe • The stylesheet is a JS function that returns a string of CSS • The stylesheet function can subscribe to events

Slide 19

Slide 19 text

The JS-in-CSS Recipe • The stylesheet is a JS function that returns a string of CSS • The stylesheet function can subscribe to events • A tag is populated with the resulting CSS

Slide 20

Slide 20 text

The JS-in-CSS Recipe • The stylesheet is a JS function that returns a string of CSS • The stylesheet function can subscribe to events • A tag is populated with the resulting CSS • The stylesheet function can be extended with re-usable JS functions that return strings of CSS

Slide 21

Slide 21 text

onload = () => document.documentElement.style.background = 'lime'

Slide 22

Slide 22 text

onload = () => document.querySelector('style').textContent = ` html { background: lime; } `

Slide 23

Slide 23 text

window.addEventListener('load', loadStyles) function populate() { return document.querySelector('style').textContent = ` html { background: lime; } ` }

Slide 24

Slide 24 text

window.addEventListener('load', loadStyles) function populate() { let style = document.querySelector('#styles') if (!style) { style = document.createElement('style') style.id = 'styles' document.head.appendChild(style) } return style.textContent = ` html { background: ${Math.random() > .5 ? 'lime' : 'hotpink'}; } ` }

Slide 25

Slide 25 text

window.addEventListener('load', loadStyles) function populate() { let style = document.querySelector('#styles') if (!style) { style = document.createElement('style') style.id = 'styles' document.head.appendChild(style) } return style.textContent = stylesheet() } function stylesheet() { return ` html { background: ${Math.random() > .5 ? 'lime' : 'hotpink'}; } ` }

Slide 26

Slide 26 text

function stylesheet() { return ` html { color: ${coinToss('black', 'white')}; background: ${coinToss('lime', 'hotpink')}; } ` } function coinToss(a, b) { return Math.random() > .5 ? a : b }

Slide 27

Slide 27 text

function jsincss( stylesheet = () => '', selector = window, events = ['load', 'resize', 'input', 'click', 'reprocess'] ) { function registerEvent(target, event, id, stylesheet) { return target.addEventListener( event, e => populateStylesheet(id, stylesheet) ) }

Slide 28

Slide 28 text

function registerEvent(target, event, id, stylesheet) { return target.addEventListener( event, e => populateStylesheet(id, stylesheet) ) } function populateStylesheet(id, stylesheet) { let tag = document.querySelector(`#jsincss-${id}`) if (!tag) { tag = document.createElement('style') tag.id = `jsincss-${id}` document.head.appendChild(tag) } const currentStyles = tag.textContent const generatedStyles = stylesheet() if (!currentStyles || (generatedStyles !== currentStyles)) { return tag.textContent = generatedStyles } }

Slide 29

Slide 29 text

const currentStyles = tag.textContent const generatedStyles = stylesheet() if (!currentStyles || (generatedStyles !== currentStyles)) { return tag.textContent = generatedStyles } } let id = Date.now() + Math.floor(Math.random() * 100) if (selector === window) { return events.forEach(event => registerEvent(window, event, id, stylesheet) ) } else { return document.querySelectorAll(selector).forEach(tag => events.forEach(event => registerEvent(tag, event, id, stylesheet) ) ) } }

Slide 30

Slide 30 text

function jsincss( stylesheet = () => '', selector = window, events = ['load', 'resize', 'input', 'click', 'reprocess'] ) { function registerEvent(target, event, id, stylesheet) { return target.addEventListener( event, e => populateStylesheet(id, stylesheet) ) } function populateStylesheet(id, stylesheet) { let tag = document.querySelector(`#jsincss-${id}`) if (!tag) { tag = document.createElement('style') tag.id = `jsincss-${id}` document.head.appendChild(tag) } const currentStyles = tag.textContent const generatedStyles = stylesheet() if (!currentStyles || (generatedStyles !== currentStyles)) { return tag.textContent = generatedStyles } } let id = Date.now() + Math.floor(Math.random() * 100) if (selector === window) { return events.forEach(event => registerEvent(window, event, id, stylesheet) ) } else { return document.querySelectorAll(selector).forEach(tag => events.forEach(event => registerEvent(tag, event, id, stylesheet) ) ) } }

Slide 31

Slide 31 text

Caffeinated Style Sheets

Slide 32

Slide 32 text

// Event-Driven window.addEventListener('load', populate) // Virtual Stylesheet function populate() { return document.querySelector('style').textContent = stylesheet() } // Function function stylesheet() { return ` html ::before { font-size: 10vw; content: '${innerWidth} x ${innerHeight}'; } ` }

Slide 33

Slide 33 text

Extending a Selector selector { property: value; }

Slide 34

Slide 34 text

Extending a Selector selector | { property: value; }

Slide 35

Slide 35 text

Extending a Selector selector [ --custom] { property: value; }

Slide 36

Slide 36 text

Extending a Selector selector [ --custom="args"] { property: value; }

Slide 37

Slide 37 text

Extending an At-Rule @supports { /* group body rule */ }

Slide 38

Slide 38 text

Extending an At-Rule @supports | { /* group body rule */ }

Slide 39

Slide 39 text

Extending an At-Rule @supports --custom() { /* group body rule */ }

Slide 40

Slide 40 text

Extending an At-Rule @supports --custom(args) { /* group body rule */ }

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Always use a double-dash “--”

Slide 43

Slide 43 text

Finding JS-Powered Rules

Slide 44

Slide 44 text

Finding JS-Powered Rules • Loop through the document.styleSheets object

Slide 45

Slide 45 text

Finding JS-Powered Rules • Loop through the document.styleSheets object • For each stylesheet, loop through it's cssRules

Slide 46

Slide 46 text

Finding JS-Powered Rules • Loop through the document.styleSheets object • For each stylesheet, loop through it's cssRules • Look for style rules (type 1) or @supports rules (type 12)

Slide 47

Slide 47 text

Finding JS-Powered Rules • Loop through the document.styleSheets object • For each stylesheet, loop through it's cssRules • Look for style rules (type 1) or @supports rules (type 12) • Check selectorText or conditionText to see if it includes ‘--’

Slide 48

Slide 48 text

Finding JS-Powered Rules • Loop through the document.styleSheets object • For each stylesheet, loop through it's cssRules • Look for style rules (type 1) or @supports rules (type 12) • Check selectorText or conditionText to see if it includes ‘--’ • Parse selector (if a rule), arguments, and styles

Slide 49

Slide 49 text

Array.from(document.styleSheets).forEach(stylesheet => Array.from(stylesheet.cssRules).forEach(rule => { if (rule.type === 1 && rule.selectorText.includes(' --')) { console.log('found a js-powered style rule') } else if (rule.type === 12 && rule.conditionText.includes(' --')) { console.log('found a js-powered @supports rule') } }) )

Slide 50

Slide 50 text

body[ --custom="1, 2, 3"] { background: lime; }

Slide 51

Slide 51 text

if (rule.type === 1 && rule.selectorText.includes(' --custom')) { const selector = rule.selectorText .split('[ --custom')[0] .trim() const arguments = rule.selectorText.includes(' --custom=') ? rule.selectorText.replace(/.*\[ --custom=(.*)\]/, '$1').slice(1, -1) : '' const declarations = rule.cssText .split(rule.selectorText)[1] .trim() .slice(1, -1) .trim() }

Slide 52

Slide 52 text

body 1, 2, 3 background: lime;

Slide 53

Slide 53 text

custom('body', 1, 2, 3, `background: lime;`)

Slide 54

Slide 54 text

@supports --custom(1, 2, 3) { body { background: lime; } }

Slide 55

Slide 55 text

if (rule.type === 12 && rule.conditionText.includes(' --custom')) { const arguments = rule.conditionText .split(' --custom')[1] .trim() .slice(1, -1) const stylesheet = rule.cssText .split(rule.conditionText)[1] .trim() .slice(1, -1) .trim() }

Slide 56

Slide 56 text

1, 2, 3 body { background: lime; }

Slide 57

Slide 57 text

custom(1, 2, 3, `body {background: lime; }`)

Slide 58

Slide 58 text

The “No More Tears” approach to CSS parsing, Write only 100% valid CSS and let the browser parse it!

Slide 59

Slide 59 text

Can be processed server-side or client-slide

Slide 60

Slide 60 text

Caffeinated Style Sheets

Slide 61

Slide 61 text

/* :parent */ .child[ --parent] { border: 10px dashed red; } /* :has() */ ul[ --has="'strong'"] { background: cyan; } /* @element {} */ @supports --element('input', {minCharacters: 5}) { [ --self] { background: hotpink; } }

Slide 62

Slide 62 text

The Tag Reduction Pattern

Slide 63

Slide 63 text

The Tag Reduction Pattern • A selector to search for tags in the document

Slide 64

Slide 64 text

The Tag Reduction Pattern • A selector to search for tags in the document • A test to filter matching tags

Slide 65

Slide 65 text

The Tag Reduction Pattern • A selector to search for tags in the document • A test to filter matching tags • To know which tag we want to apply styles to

Slide 66

Slide 66 text

The Tag Reduction Pattern • A selector to search for tags in the document • A test to filter matching tags • To know which tag we want to apply styles to • A rule or stylesheet we want to apply

Slide 67

Slide 67 text

The Tag Reduction Pattern

Slide 68

Slide 68 text

The Tag Reduction Pattern 1. Get an array of all tags matching the selector

Slide 69

Slide 69 text

The Tag Reduction Pattern 1. Get an array of all tags matching the selector 2. Reduce the array to a string (our CSS stylesheet)

Slide 70

Slide 70 text

The Tag Reduction Pattern 1. Get an array of all tags matching the selector 2. Reduce the array to a string (our CSS stylesheet) 3. Create a unique identifier (from plugin name, selector, options)

Slide 71

Slide 71 text

The Tag Reduction Pattern 1. Get an array of all tags matching the selector 2. Reduce the array to a string (our CSS stylesheet) 3. Create a unique identifier (from plugin name, selector, options) 4. Test each matching tag

Slide 72

Slide 72 text

The Tag Reduction Pattern 1. Get an array of all tags matching the selector 2. Reduce the array to a string (our CSS stylesheet) 3. Create a unique identifier (from plugin name, selector, options) 4. Test each matching tag If tag passes: add unique identifier to tag, and add copy of CSS rule or stylesheet to output

Slide 73

Slide 73 text

The Tag Reduction Pattern 1. Get an array of all tags matching the selector 2. Reduce the array to a string (our CSS stylesheet) 3. Create a unique identifier (from plugin name, selector, options) 4. Test each matching tag If tag passes: add unique identifier to tag, and add copy of CSS rule or stylesheet to output If tag fails: remove any unique identifier that might exist

Slide 74

Slide 74 text

100% CSS + 100% JS Zero Compromise

Slide 75

Slide 75 text

100% CSS + 100% JS Zero Compromise

Slide 76

Slide 76 text

Min-Width as a Selector .example:min-width(500px) { /* rule */ } What we want in CSS:

Slide 77

Slide 77 text

Min-Width as a Selector el.offsetWidth >= width What we can test with JS:

Slide 78

Slide 78 text

Min-Width as a Selector .example[ --min-width="500"] { /* rule */ } What we can write in CSS:

Slide 79

Slide 79 text

Min-Width as a Selector minWidth('.example', 500, ' /* rule */') What we can run in JS:

Slide 80

Slide 80 text

function minWidth(selector, width, rule) { return Array.from(document.querySelectorAll(selector)) .reduce((styles, tag, count) => { const attr = (selector + width).replace(/\W/g, '') if (tag.offsetWidth >= width) { tag.setAttribute(`data-minwidth-${attr}`, count) styles += `[data-minwidth-${attr}="${count}"] { ${rule} }\n` } else { tag.setAttribute(`data-minwidth-${attr}`, '') } return styles }, '') }

Slide 81

Slide 81 text

Min-Width as an At-Rule What we want in CSS: @element .example and (min-width: 500px) { :self { background: lime; } }

Slide 82

Slide 82 text

Min-Width as an At-Rule el.offsetWidth >= width What we can test with JS:

Slide 83

Slide 83 text

Min-Width as an At-Rule @supports --minWidth('.example', 500) { [ --self] { background: lime; } } What we can write in CSS:

Slide 84

Slide 84 text

Min-Width as an At-Rule minWidth('.example', 500, ' /* stylesheet */') What we can run in JS:

Slide 85

Slide 85 text

function minWidth(selector, width, stylesheet) { return Array.from(document.querySelectorAll(selector)) .reduce((styles, tag, count) => { const attr = (selector + width).replace(/\W/g, '') if (tag.offsetWidth >= width) { tag.setAttribute(`data-minwidth-${attr}`, count) styles += stylesheet.replace( /\[ --self\]/g, `[data-minwidth-${attr}="${count}"]` ) } else { tag.setAttribute(`data-minwidth-${attr}`, '') } return styles }, '') }

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

Parent Selector .example:parent { /* rule */ } What we want in CSS:

Slide 89

Slide 89 text

Parent Selector el.parentElement What we can test with JS:

Slide 90

Slide 90 text

Parent Selector .example[ --parent] { /* rule */ } What we can write in CSS:

Slide 91

Slide 91 text

Parent Selector parent('.example', ' /* rule */') What we can run in JS:

Slide 92

Slide 92 text

function parent(selector, rule) { return Array.from(document.querySelectorAll(selector)) .filter(tag => tag.parentElement) .reduce((styles, tag, count) => { const attr = selector.replace(/\W/g, '') tag.parentElement.setAttribute(`data-parent-${attr}`, count) styles += `[data-parent-${attr}="${count}"] { ${rule} }\n` return styles }, '') }

Slide 93

Slide 93 text

Do you see the power here?

Slide 94

Slide 94 text

:has() .example:has(.demo) { /* rule */ } What we want in CSS:

Slide 95

Slide 95 text

No content

Slide 96

Slide 96 text

:has() el.querySelector(child) What we can test with JS:

Slide 97

Slide 97 text

:has() .example[ --has="'.demo'"] { /* rule */ } What we can write in CSS:

Slide 98

Slide 98 text

:has() has('.example', '.demo', ' /* rule */') What we can run in JS:

Slide 99

Slide 99 text

function has(selector, child, rule) { return Array.from(document.querySelectorAll(selector)) .filter(tag => tag.querySelector(child)) .reduce((styles, tag, count) => { const attr = (selector + child).replace(/\W/g, '') tag.setAttribute(`data-has-${attr}`, count) styles += `[data-has-${attr}="${count}"] { ${rule} }\n` return styles }, '') }

Slide 100

Slide 100 text

Previous Selector .example:previous { /* rule */ } What we want in CSS:

Slide 101

Slide 101 text

Previous Selector el.previousElementSibling What we can test with JS:

Slide 102

Slide 102 text

Previous Selector .example[ --previous] { /* rule */ } What we can write in CSS:

Slide 103

Slide 103 text

Previous Selector previous('.example', ' /* rule */') What we can run in JS:

Slide 104

Slide 104 text

Contains String .example:contains-text('demo') { /* rule */ } What we want in CSS:

Slide 105

Slide 105 text

Contains String el.textContent.includes('string') What we can test with JS:

Slide 106

Slide 106 text

Contains String .example[ --contains-text="'demo'"] { /* rule */ } What we can write in CSS:

Slide 107

Slide 107 text

Contains String containsText('.example', 'demo', ' /* rule */') What we can run in JS:

Slide 108

Slide 108 text

Attribute Comparison .example[price > 50] { /* rule */ } What we want in CSS:

Slide 109

Slide 109 text

Attribute Comparison el.getAttribute(attr) >= number What we can test with JS:

Slide 110

Slide 110 text

Attribute Comparison .example[ --attr-greater="'price', 50"] { /* rule */ } What we can write in CSS:

Slide 111

Slide 111 text

Attribute Comparison attrGreater('.example', 'price', 50, ' /* rule */') What we can run in JS:

Slide 112

Slide 112 text

Style based on any property or test you can write, on any element, when any event happens in the browser

Slide 113

Slide 113 text

Style based on any property or test you can write, on any element, when any event happens in the browser

Slide 114

Slide 114 text

Style based on any property or test you can write, on any element, when any event happens in the browser

Slide 115

Slide 115 text

— Tab Atkins, on ‘dynamic values’ in CSS “The JS you write is less of a performance hit than what would happen if we recast the entire style system to handle this sort of thing.”

Slide 116

Slide 116 text

Caffeinated Style Sheets

Slide 117

Slide 117 text

// Tag Reduction function custom(selector, option, rule) { return Array.from(document.querySelectorAll(selector)) .reduce((styles, tag, count) => { const attr = (selector + option).replace(/\W/g, '') if (option(tag)) { tag.setAttribute(`data-custom-${attr}`, count) styles += `[data-custom-${attr}="${count}"] { ${rule} }\n` } else { tag.setAttribute(`data-custom-${attr}`, '') } return styles }, '') }

Slide 118

Slide 118 text

What about XPath?

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

function xpath(selector, rule) { const tags = [] const result = document.evaluate( selector, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null ) for (let i=0; i < result.snapshotLength; i ++) { tags.push(result.snapshotItem(i)) } return tags.reduce((styles, tag, count) => { const attr = selector.replace(/\W/g, '') tag.setAttribute(`data-xpath-${attr}`, count) styles += `[data-xpath-${attr}="${count}"] { ${rule} }\n` return styles }, '') }

Slide 121

Slide 121 text

/* :has(li) */ \/\/\*[li] [ --xpath] { background: lime; } /* li:parent */ \/\/li\/parent\:\:\* [ --xpath] { background: hotpink; } /* li:contains-text('hello') */ \/\/\*\[contains\(text\(\)\,\'hello\'\)\] [ --xpath] { background: purple; }

Slide 122

Slide 122 text

xpath( rule.selectorText .split('[ --xpath]')[0] .replace(/\ /g, '') .trim(), rule.cssText.split(rule.selectorText)[1] .trim() .slice(1, -1) )

Slide 123

Slide 123 text

import jsincss from 'https: //unpkg.com/jsincss/index.vanilla.js' import xpath from 'https: //unpkg.com/jsincss-xpath-selector/index.vanilla.js' Array.from(document.styleSheets).forEach(stylesheet => Array.from(stylesheet.cssRules).forEach(rule => { if (rule.type === 1 && rule.selectorText.includes('[ --xpath]')) { jsincss(() => xpath( rule.selectorText .split('[ --xpath]')[0] .replace(/\ /g, '') .trim(), rule.cssText.split(rule.selectorText)[1] .trim() .slice(1, -1) )) } }) )

Slide 124

Slide 124 text

XPath in CSS

Slide 125

Slide 125 text

Media queries took
 11 years to specify

Slide 126

Slide 126 text

Media Queries @media (min-width: 500px) { /* stylesheet */ } What we wanted in CSS:

Slide 127

Slide 127 text

Media Queries window.innerWidth >= number What we could test with JS:

Slide 128

Slide 128 text

Media Queries media({minWidth: 500}, ' /* stylesheet */') What we can run in JS:

Slide 129

Slide 129 text

function media(conditions, stylesheet) { return Object.keys(conditions).every( test => ({ minWidth: number => number <= innerWidth, maxWidth: number => number >= innerWidth, minHeight: number => number <= innerHeight, maxHeight: number => number >= innerHeight, minAspectRatio: number => number <= innerWidth / innerHeight, maxAspectRatio: number => number >= innerWidth / innerHeight, orientation: string => { switch (string) { case 'portrait': return innerWidth < innerHeight case 'landscape': return innerWidth > innerHeight } } })[test](conditions[test]) ) ? stylesheet : '' }

Slide 130

Slide 130 text

What CSS features do you want 10 years from now?

Slide 131

Slide 131 text

Element Queries?

Slide 132

Slide 132 text

function element(selector, conditions, stylesheet) { const features = { minWidth: (el, number) => number <= el.offsetWidth, maxWidth: (el, number) => number >= el.offsetWidth, minHeight: (el, number) => number <= el.offsetHeight, maxHeight: (el, number) => number >= el.offsetHeight, minAspectRatio: (el, number) => number <= el.offsetWidth / el.offsetHeight, maxAspectRatio: (el, number) => number >= el.offsetWidth / el.offsetHeight, orientation: (el, string) => { switch (string) { case 'portrait': return el.offsetWidth < el.offsetHeight case 'square': return el.offsetWidth === el.offsetHeight case 'landscape': return el.offsetWidth > el.offsetHeight } }, minChildren: (el, number) => number <= el.children.length, children: (el, number) => number === el.children.length, maxChildren: (el, number) => number >= el.children.length, minCharacters: (el, number) => number <= ( (el.value && el.value.length) || el.textContent.length ), characters: (el, number) => number === (

Slide 133

Slide 133 text

minAspectRatio: (el, number) => number <= el.offsetWidth / el.offsetHeight, maxAspectRatio: (el, number) => number >= el.offsetWidth / el.offsetHeight, orientation: (el, string) => { switch (string) { case 'portrait': return el.offsetWidth < el.offsetHeight case 'square': return el.offsetWidth === el.offsetHeight case 'landscape': return el.offsetWidth > el.offsetHeight } }, minChildren: (el, number) => number <= el.children.length, children: (el, number) => number === el.children.length, maxChildren: (el, number) => number >= el.children.length, minCharacters: (el, number) => number <= ( (el.value && el.value.length) || el.textContent.length ), characters: (el, number) => number === ( (el.value && el.value.length) || el.textContent.length ), maxCharacters: (el, number) => number >= ( (el.value && el.value.length) || el.textContent.length ), minScrollX: (el, number) => number <= el.scrollLeft, maxScrollX: (el, number) => number >= el.scrollLeft, minScrollY: (el, number) => number <= el.scrollTop, maxScrollY: (el, number) => number >= el.scrollTop }

Slide 134

Slide 134 text

} return Array.from(document.querySelectorAll(selector)) .reduce((styles, tag, count) => { const attr = ( selector + Object.keys(conditions) + Object.values(conditions) ).replace(/\W/g, '') if (Object.entries(conditions).every(test => features[test[0]](tag, test[1]) )) { tag.setAttribute(`data-element-${attr}`, count) styles += stylesheet.replace( /\[ --self\]/g, `[data-element-${attr}="${count}"]` ) } else { tag.setAttribute(`data-element-${attr}`, '') } return styles }, '') }

Slide 135

Slide 135 text

function element(selector, conditions, stylesheet) { const features = { minWidth: (el, number) => number <= el.offsetWidth, maxWidth: (el, number) => number >= el.offsetWidth, minHeight: (el, number) => number <= el.offsetHeight, maxHeight: (el, number) => number >= el.offsetHeight, minAspectRatio: (el, number) => number <= el.offsetWidth / el.offsetHeight, maxAspectRatio: (el, number) => number >= el.offsetWidth / el.offsetHeight, orientation: (el, string) => { switch (string) { case 'portrait': return el.offsetWidth < el.offsetHeight case 'square': return el.offsetWidth === el.offsetHeight case 'landscape': return el.offsetWidth > el.offsetHeight } }, minChildren: (el, number) => number <= el.children.length, children: (el, number) => number === el.children.length, maxChildren: (el, number) => number >= el.children.length, minCharacters: (el, number) => number <= ( (el.value && el.value.length) || el.textContent.length ), characters: (el, number) => number === ( (el.value && el.value.length) || el.textContent.length ), maxCharacters: (el, number) => number >= ( (el.value && el.value.length) || el.textContent.length ), minScrollX: (el, number) => number <= el.scrollLeft, maxScrollX: (el, number) => number >= el.scrollLeft, minScrollY: (el, number) => number <= el.scrollTop, maxScrollY: (el, number) => number >= el.scrollTop } return Array.from(document.querySelectorAll(selector)) .reduce((styles, tag, count) => { const attr = ( selector + Object.keys(conditions) + Object.values(conditions) ).replace(/\W/g, '') if (Object.entries(conditions).every(test => features[test[0]](tag, test[1]) )) { tag.setAttribute(`data-element-${attr}`, count) styles += stylesheet.replace( /\[ --self\]/g, `[data-element-${attr}="${count}"]` ) } else { tag.setAttribute(`data-element-${attr}`, '') } return styles }, '') }

Slide 136

Slide 136 text

Scoped Styles?

Slide 137

Slide 137 text

New Selectors?

Slide 138

Slide 138 text

New Pseudo-Classes?

Slide 139

Slide 139 text

New Properties?

Slide 140

Slide 140 text

New At-Rules?

Slide 141

Slide 141 text

New Units?

Slide 142

Slide 142 text

Look up ‘jsincss’ on npm for some ideas

Slide 143

Slide 143 text

Please feel free to publish your own ‘jsincss’ plugins!

Slide 144

Slide 144 text

Thank you! ʕ•ᴥ•ʔ

Slide 145

Slide 145 text

@audience { }