SlideShare ist ein Scribd-Unternehmen logo
1 von 145
Downloaden Sie, um offline zu lesen
Caffeinated
Style
Sheets Tommy Hodgins
@innovati
Caffeinated
Style
Sheets
Caffeinated
Style
Sheets
{ CSS extended by JS }
CSS R&D
2014 – 2018
Big Takeaways
Big Takeaways
• CSS can be extended in the browser by JavaScript
Big Takeaways
• CSS can be extended in the browser by JavaScript
• Many desired CSS features are simple to implement with JavaScript
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
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
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
Think of your stylesheet
as a function of what’s
going on in the browser
Think of your stylesheet
as a function of what’s
going on in the browser
Not a software framework,
a mental framework
Not a software framework,
a mental framework
The JS-in-CSS Recipe
The JS-in-CSS Recipe
• The stylesheet is a JS function that returns a string of CSS
The JS-in-CSS Recipe
• The stylesheet is a JS function that returns a string of CSS
• The stylesheet function can subscribe to events
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 <style> tag is populated with the resulting CSS
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 <style> tag is populated with the resulting CSS
• The stylesheet function can be extended with re-usable JS
functions that return strings of CSS
onload = () => document.documentElement.style.background = 'lime'
<style> </style>
<script>
onload = () => document.querySelector('style').textContent = `
html {
background: lime;
}
`
</script>
<style> </style>
<script>
window.addEventListener('load', loadStyles)
function populate() {
return document.querySelector('style').textContent = `
html {
background: lime;
}
`
}
</script>
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'};
}
`
}
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'};
}
`
}
function stylesheet() {
return `
html {
color: ${coinToss('black', 'white')};
background: ${coinToss('lime', 'hotpink')};
}
`
}
function coinToss(a, b) {
return Math.random() > .5 ? a : b
}
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 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
}
}
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)
)
)
}
}
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)
)
)
}
}
Caffeinated
Style
Sheets
<style> </style>
<script>
// 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}';
}
`
}
</script>
Extending a Selector
selector { property: value; }
Extending a Selector
selector | { property: value; }
Extending a Selector
selector [ --custom] { property: value; }
Extending a Selector
selector [ --custom="args"] { property: value; }
Extending an At-Rule
@supports { /* group body rule */ }
Extending an At-Rule
@supports | { /* group body rule */ }
Extending an At-Rule
@supports --custom() { /* group body rule */ }
Extending an At-Rule
@supports --custom(args) { /* group body rule */ }
Always use a
double-dash “--”
Finding JS-Powered Rules
Finding JS-Powered Rules
• Loop through the document.styleSheets object
Finding JS-Powered Rules
• Loop through the document.styleSheets object
• For each stylesheet, loop through it's cssRules
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)
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 ‘--’
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
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')
}
})
)
body[ --custom="1, 2, 3"] {
background: lime;
}
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()
}
body
1, 2, 3
background: lime;
custom('body', 1, 2, 3, `background: lime;`)
@supports --custom(1, 2, 3) {
body {
background: lime;
}
}
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()
}
1, 2, 3
body { background: lime; }
custom(1, 2, 3, `body {background: lime; }`)
The “No More Tears” approach to CSS parsing,
Write only 100% valid CSS and let the browser parse it!
Can be processed
server-side or client-slide
Caffeinated
Style
Sheets
/* :parent */
.child[ --parent] {
border: 10px dashed red;
}
/* :has() */
ul[ --has="'strong'"] {
background: cyan;
}
/* @element {} */
@supports --element('input', {minCharacters: 5}) {
[ --self] {
background: hotpink;
}
}
The Tag Reduction Pattern
The Tag Reduction Pattern
• A selector to search for tags in the document
The Tag Reduction Pattern
• A selector to search for tags in the document
• A test to filter matching tags
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
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
The Tag Reduction Pattern
The Tag Reduction Pattern
1. Get an array of all tags matching the selector
The Tag Reduction Pattern
1. Get an array of all tags matching the selector
2. Reduce the array to a string (our CSS stylesheet)
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)
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
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
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
100% CSS + 100% JS
Zero Compromise
100% CSS + 100% JS
Zero Compromise
Min-Width as a Selector
.example:min-width(500px) { /* rule */ }
What we want in CSS:
Min-Width as a Selector
el.offsetWidth >= width
What we can test with JS:
Min-Width as a Selector
.example[ --min-width="500"] { /* rule */ }
What we can write in CSS:
Min-Width as a Selector
minWidth('.example', 500, ' /* rule */')
What we can run in JS:
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
}, '')
}
Min-Width as an At-Rule
What we want in CSS:
@element .example and (min-width: 500px) {
:self { background: lime; }
}
Min-Width as an At-Rule
el.offsetWidth >= width
What we can test with JS:
Min-Width as an At-Rule
@supports --minWidth('.example', 500) {
[ --self] { background: lime; }
}
What we can write in CSS:
Min-Width as an At-Rule
minWidth('.example', 500, ' /* stylesheet */')
What we can run in JS:
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
}, '')
}
Parent Selector
.example:parent { /* rule */ }
What we want in CSS:
Parent Selector
el.parentElement
What we can test with JS:
Parent Selector
.example[ --parent] { /* rule */ }
What we can write in CSS:
Parent Selector
parent('.example', ' /* rule */')
What we can run in JS:
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
}, '')
}
Do you see the power here?
:has()
.example:has(.demo) { /* rule */ }
What we want in CSS:
:has()
el.querySelector(child)
What we can test with JS:
:has()
.example[ --has="'.demo'"] { /* rule */ }
What we can write in CSS:
:has()
has('.example', '.demo', ' /* rule */')
What we can run in JS:
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
}, '')
}
Previous Selector
.example:previous { /* rule */ }
What we want in CSS:
Previous Selector
el.previousElementSibling
What we can test with JS:
Previous Selector
.example[ --previous] { /* rule */ }
What we can write in CSS:
Previous Selector
previous('.example', ' /* rule */')
What we can run in JS:
Contains String
.example:contains-text('demo') { /* rule */ }
What we want in CSS:
Contains String
el.textContent.includes('string')
What we can test with JS:
Contains String
.example[ --contains-text="'demo'"] { /* rule */ }
What we can write in CSS:
Contains String
containsText('.example', 'demo', ' /* rule */')
What we can run in JS:
Attribute Comparison
.example[price > 50] { /* rule */ }
What we want in CSS:
Attribute Comparison
el.getAttribute(attr) >= number
What we can test with JS:
Attribute Comparison
.example[ --attr-greater="'price', 50"] { /* rule */ }
What we can write in CSS:
Attribute Comparison
attrGreater('.example', 'price', 50, ' /* rule */')
What we can run in JS:
Style based on any property or test
you can write, on any element, when
any event happens in the browser
Style based on any property or test
you can write, on any element, when
any event happens in the browser
Style based on any property or test
you can write, on any element, when
any event happens in the browser
— 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.”
Caffeinated
Style
Sheets
// 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
}, '')
}
What about XPath?
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
}, '')
}
/* :has(li) */
//*[li] [ --xpath] {
background: lime;
}
/* li:parent */
//li/parent::* [ --xpath] {
background: hotpink;
}
/* li:contains-text('hello') */
//*[contains(text(),'hello')] [ --xpath] {
background: purple;
}
xpath(
rule.selectorText
.split('[ --xpath]')[0]
.replace(/ /g, '')
.trim(),
rule.cssText.split(rule.selectorText)[1]
.trim()
.slice(1, -1)
)
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)
))
}
})
)
XPath in CSS
Media queries took

11 years to specify
Media Queries
@media (min-width: 500px) { /* stylesheet */ }
What we wanted in CSS:
Media Queries
window.innerWidth >= number
What we could test with JS:
Media Queries
media({minWidth: 500}, ' /* stylesheet */')
What we can run in JS:
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
: ''
}
What CSS features do you
want 10 years from now?
Element Queries?
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 === (
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
}, '')
}
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
}, '')
}
Scoped Styles?
New Selectors?
New Pseudo-Classes?
New Properties?
New At-Rules?
New Units?
Look up ‘jsincss’ on
npm for some ideas
Please feel free to publish
your own ‘jsincss’ plugins!
Thank you!
ʕ•ᴥ•ʔ
@audience { }

Weitere ähnliche Inhalte

Was ist angesagt?

10gen Presents Schema Design and Data Modeling
10gen Presents Schema Design and Data Modeling10gen Presents Schema Design and Data Modeling
10gen Presents Schema Design and Data Modeling
DATAVERSITY
 
Alexander Taranov. Right slicing for drupal = painless teming
Alexander Taranov. Right slicing for drupal = painless temingAlexander Taranov. Right slicing for drupal = painless teming
Alexander Taranov. Right slicing for drupal = painless teming
DrupalSib
 
Java scriptap iforofficeposter
Java scriptap iforofficeposterJava scriptap iforofficeposter
Java scriptap iforofficeposter
Tayla Gayle X
 

Was ist angesagt? (10)

Implementing CSS support for React Native
Implementing CSS support for React NativeImplementing CSS support for React Native
Implementing CSS support for React Native
 
Jquery
JqueryJquery
Jquery
 
Assetic (Zendcon)
Assetic (Zendcon)Assetic (Zendcon)
Assetic (Zendcon)
 
10gen Presents Schema Design and Data Modeling
10gen Presents Schema Design and Data Modeling10gen Presents Schema Design and Data Modeling
10gen Presents Schema Design and Data Modeling
 
Introduction to Web Components
Introduction to Web ComponentsIntroduction to Web Components
Introduction to Web Components
 
Alexander Taranov. Right slicing for drupal = painless teming
Alexander Taranov. Right slicing for drupal = painless temingAlexander Taranov. Right slicing for drupal = painless teming
Alexander Taranov. Right slicing for drupal = painless teming
 
stickyHeader.js
stickyHeader.jsstickyHeader.js
stickyHeader.js
 
Java scriptap iforofficeposter
Java scriptap iforofficeposterJava scriptap iforofficeposter
Java scriptap iforofficeposter
 
HTML and CSS crash course!
HTML and CSS crash course!HTML and CSS crash course!
HTML and CSS crash course!
 
CSS & eCSStender [CSS Summit 2011]
CSS & eCSStender [CSS Summit 2011]CSS & eCSStender [CSS Summit 2011]
CSS & eCSStender [CSS Summit 2011]
 

Ähnlich wie Caffeinated Style Sheets

DSpace 4.2 XMLUI Theming
DSpace 4.2 XMLUI ThemingDSpace 4.2 XMLUI Theming
DSpace 4.2 XMLUI Theming
DuraSpace
 
Girl Develop It Cincinnati: Intro to HTML/CSS Class 4
Girl Develop It Cincinnati: Intro to HTML/CSS Class 4Girl Develop It Cincinnati: Intro to HTML/CSS Class 4
Girl Develop It Cincinnati: Intro to HTML/CSS Class 4
Erin M. Kidwell
 
Css for Development
Css for DevelopmentCss for Development
Css for Development
tsengsite
 

Ähnlich wie Caffeinated Style Sheets (20)

Web 102 INtro to CSS
Web 102  INtro to CSSWeb 102  INtro to CSS
Web 102 INtro to CSS
 
Plone Interactivity
Plone InteractivityPlone Interactivity
Plone Interactivity
 
Css week10 2019 2020 for g10 by eng.osama ghandour
Css week10 2019 2020 for g10 by eng.osama ghandourCss week10 2019 2020 for g10 by eng.osama ghandour
Css week10 2019 2020 for g10 by eng.osama ghandour
 
Creative Web 2 - CSS
Creative Web 2 - CSS Creative Web 2 - CSS
Creative Web 2 - CSS
 
Slow kinda sucks
Slow kinda sucksSlow kinda sucks
Slow kinda sucks
 
Css Founder.com | Cssfounder org
Css Founder.com | Cssfounder orgCss Founder.com | Cssfounder org
Css Founder.com | Cssfounder org
 
Jquery 5
Jquery 5Jquery 5
Jquery 5
 
CSS-part-1.ppt
CSS-part-1.pptCSS-part-1.ppt
CSS-part-1.ppt
 
Styling Components with JavaScript: MelbCSS Edition
Styling Components with JavaScript: MelbCSS EditionStyling Components with JavaScript: MelbCSS Edition
Styling Components with JavaScript: MelbCSS Edition
 
DSpace 4.2 XMLUI Theming
DSpace 4.2 XMLUI ThemingDSpace 4.2 XMLUI Theming
DSpace 4.2 XMLUI Theming
 
Css
CssCss
Css
 
uptu web technology unit 2 Css
uptu web technology unit 2 Cssuptu web technology unit 2 Css
uptu web technology unit 2 Css
 
Girl Develop It Cincinnati: Intro to HTML/CSS Class 4
Girl Develop It Cincinnati: Intro to HTML/CSS Class 4Girl Develop It Cincinnati: Intro to HTML/CSS Class 4
Girl Develop It Cincinnati: Intro to HTML/CSS Class 4
 
Web Programming& Scripting Lab
Web Programming& Scripting LabWeb Programming& Scripting Lab
Web Programming& Scripting Lab
 
CSS
CSSCSS
CSS
 
Css
CssCss
Css
 
Web Design Course: CSS lecture 1
Web Design Course: CSS lecture 1Web Design Course: CSS lecture 1
Web Design Course: CSS lecture 1
 
2_css.pptx
2_css.pptx2_css.pptx
2_css.pptx
 
2_css.pptx
2_css.pptx2_css.pptx
2_css.pptx
 
Css for Development
Css for DevelopmentCss for Development
Css for Development
 

Kürzlich hochgeladen

Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...
Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...
Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...
Sheetaleventcompany
 
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxAWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
ellan12
 
CALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service Online
CALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service OnlineCALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service Online
CALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service Online
anilsa9823
 
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Call Girls In Delhi Whatsup 9873940964 Enjoy Unlimited Pleasure
 
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
soniya singh
 
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine ServiceHot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
sexy call girls service in goa
 
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
soniya singh
 

Kürzlich hochgeladen (20)

All Time Service Available Call Girls Mg Road 👌 ⏭️ 6378878445
All Time Service Available Call Girls Mg Road 👌 ⏭️ 6378878445All Time Service Available Call Girls Mg Road 👌 ⏭️ 6378878445
All Time Service Available Call Girls Mg Road 👌 ⏭️ 6378878445
 
Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...
Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...
Call Girls Service Chandigarh Lucky ❤️ 7710465962 Independent Call Girls In C...
 
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxAWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
 
@9999965857 🫦 Sexy Desi Call Girls Laxmi Nagar 💓 High Profile Escorts Delhi 🫶
@9999965857 🫦 Sexy Desi Call Girls Laxmi Nagar 💓 High Profile Escorts Delhi 🫶@9999965857 🫦 Sexy Desi Call Girls Laxmi Nagar 💓 High Profile Escorts Delhi 🫶
@9999965857 🫦 Sexy Desi Call Girls Laxmi Nagar 💓 High Profile Escorts Delhi 🫶
 
CALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service Online
CALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service OnlineCALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service Online
CALL ON ➥8923113531 🔝Call Girls Lucknow Lucknow best sexual service Online
 
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
 
Top Rated Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
Top Rated  Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...Top Rated  Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
Top Rated Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
 
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort ServiceEnjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
 
'Future Evolution of the Internet' delivered by Geoff Huston at Everything Op...
'Future Evolution of the Internet' delivered by Geoff Huston at Everything Op...'Future Evolution of the Internet' delivered by Geoff Huston at Everything Op...
'Future Evolution of the Internet' delivered by Geoff Huston at Everything Op...
 
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark WebGDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
 
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call GirlVIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
 
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
 
Nanded City ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready ...
Nanded City ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready ...Nanded City ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready ...
Nanded City ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready ...
 
Moving Beyond Twitter/X and Facebook - Social Media for local news providers
Moving Beyond Twitter/X and Facebook - Social Media for local news providersMoving Beyond Twitter/X and Facebook - Social Media for local news providers
Moving Beyond Twitter/X and Facebook - Social Media for local news providers
 
VVVIP Call Girls In Connaught Place ➡️ Delhi ➡️ 9999965857 🚀 No Advance 24HRS...
VVVIP Call Girls In Connaught Place ➡️ Delhi ➡️ 9999965857 🚀 No Advance 24HRS...VVVIP Call Girls In Connaught Place ➡️ Delhi ➡️ 9999965857 🚀 No Advance 24HRS...
VVVIP Call Girls In Connaught Place ➡️ Delhi ➡️ 9999965857 🚀 No Advance 24HRS...
 
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine ServiceHot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
 
(+971568250507 ))# Young Call Girls in Ajman By Pakistani Call Girls in ...
(+971568250507  ))#  Young Call Girls  in Ajman  By Pakistani Call Girls  in ...(+971568250507  ))#  Young Call Girls  in Ajman  By Pakistani Call Girls  in ...
(+971568250507 ))# Young Call Girls in Ajman By Pakistani Call Girls in ...
 
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night StandHot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
 
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
 
(INDIRA) Call Girl Pune Call Now 8250077686 Pune Escorts 24x7
(INDIRA) Call Girl Pune Call Now 8250077686 Pune Escorts 24x7(INDIRA) Call Girl Pune Call Now 8250077686 Pune Escorts 24x7
(INDIRA) Call Girl Pune Call Now 8250077686 Pune Escorts 24x7
 

Caffeinated Style Sheets

  • 4. { CSS extended by JS }
  • 7. Big Takeaways • CSS can be extended in the browser by JavaScript
  • 8. Big Takeaways • CSS can be extended in the browser by JavaScript • Many desired CSS features are simple to implement with JavaScript
  • 9. 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
  • 10. 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
  • 11. 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
  • 12. Think of your stylesheet as a function of what’s going on in the browser
  • 13. Think of your stylesheet as a function of what’s going on in the browser
  • 14. Not a software framework, a mental framework
  • 15. Not a software framework, a mental framework
  • 17. The JS-in-CSS Recipe • The stylesheet is a JS function that returns a string of CSS
  • 18. The JS-in-CSS Recipe • The stylesheet is a JS function that returns a string of CSS • The stylesheet function can subscribe to events
  • 19. 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 <style> tag is populated with the resulting CSS
  • 20. 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 <style> tag is populated with the resulting CSS • The stylesheet function can be extended with re-usable JS functions that return strings of CSS
  • 21. onload = () => document.documentElement.style.background = 'lime'
  • 22. <style> </style> <script> onload = () => document.querySelector('style').textContent = ` html { background: lime; } ` </script>
  • 23. <style> </style> <script> window.addEventListener('load', loadStyles) function populate() { return document.querySelector('style').textContent = ` html { background: lime; } ` } </script>
  • 24. 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'}; } ` }
  • 25. 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'}; } ` }
  • 26. function stylesheet() { return ` html { color: ${coinToss('black', 'white')}; background: ${coinToss('lime', 'hotpink')}; } ` } function coinToss(a, b) { return Math.random() > .5 ? a : b }
  • 27. 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) ) }
  • 28. 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 } }
  • 29. 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) ) ) } }
  • 30. 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) ) ) } }
  • 32. <style> </style> <script> // 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}'; } ` } </script>
  • 33. Extending a Selector selector { property: value; }
  • 34. Extending a Selector selector | { property: value; }
  • 35. Extending a Selector selector [ --custom] { property: value; }
  • 36. Extending a Selector selector [ --custom="args"] { property: value; }
  • 37. Extending an At-Rule @supports { /* group body rule */ }
  • 38. Extending an At-Rule @supports | { /* group body rule */ }
  • 39. Extending an At-Rule @supports --custom() { /* group body rule */ }
  • 40. Extending an At-Rule @supports --custom(args) { /* group body rule */ }
  • 41.
  • 44. Finding JS-Powered Rules • Loop through the document.styleSheets object
  • 45. Finding JS-Powered Rules • Loop through the document.styleSheets object • For each stylesheet, loop through it's cssRules
  • 46. 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)
  • 47. 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 ‘--’
  • 48. 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
  • 49. 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') } }) )
  • 50. body[ --custom="1, 2, 3"] { background: lime; }
  • 51. 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() }
  • 53. custom('body', 1, 2, 3, `background: lime;`)
  • 54. @supports --custom(1, 2, 3) { body { background: lime; } }
  • 55. 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() }
  • 56. 1, 2, 3 body { background: lime; }
  • 57. custom(1, 2, 3, `body {background: lime; }`)
  • 58. The “No More Tears” approach to CSS parsing, Write only 100% valid CSS and let the browser parse it!
  • 59. Can be processed server-side or client-slide
  • 61. /* :parent */ .child[ --parent] { border: 10px dashed red; } /* :has() */ ul[ --has="'strong'"] { background: cyan; } /* @element {} */ @supports --element('input', {minCharacters: 5}) { [ --self] { background: hotpink; } }
  • 62. The Tag Reduction Pattern
  • 63. The Tag Reduction Pattern • A selector to search for tags in the document
  • 64. The Tag Reduction Pattern • A selector to search for tags in the document • A test to filter matching tags
  • 65. 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
  • 66. 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
  • 67. The Tag Reduction Pattern
  • 68. The Tag Reduction Pattern 1. Get an array of all tags matching the selector
  • 69. The Tag Reduction Pattern 1. Get an array of all tags matching the selector 2. Reduce the array to a string (our CSS stylesheet)
  • 70. 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)
  • 71. 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
  • 72. 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
  • 73. 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
  • 74. 100% CSS + 100% JS Zero Compromise
  • 75. 100% CSS + 100% JS Zero Compromise
  • 76. Min-Width as a Selector .example:min-width(500px) { /* rule */ } What we want in CSS:
  • 77. Min-Width as a Selector el.offsetWidth >= width What we can test with JS:
  • 78. Min-Width as a Selector .example[ --min-width="500"] { /* rule */ } What we can write in CSS:
  • 79. Min-Width as a Selector minWidth('.example', 500, ' /* rule */') What we can run in JS:
  • 80. 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 }, '') }
  • 81. Min-Width as an At-Rule What we want in CSS: @element .example and (min-width: 500px) { :self { background: lime; } }
  • 82. Min-Width as an At-Rule el.offsetWidth >= width What we can test with JS:
  • 83. Min-Width as an At-Rule @supports --minWidth('.example', 500) { [ --self] { background: lime; } } What we can write in CSS:
  • 84. Min-Width as an At-Rule minWidth('.example', 500, ' /* stylesheet */') What we can run in JS:
  • 85. 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 }, '') }
  • 86.
  • 87.
  • 88. Parent Selector .example:parent { /* rule */ } What we want in CSS:
  • 90. Parent Selector .example[ --parent] { /* rule */ } What we can write in CSS:
  • 91. Parent Selector parent('.example', ' /* rule */') What we can run in JS:
  • 92. 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 }, '') }
  • 93. Do you see the power here?
  • 94. :has() .example:has(.demo) { /* rule */ } What we want in CSS:
  • 95.
  • 97. :has() .example[ --has="'.demo'"] { /* rule */ } What we can write in CSS:
  • 98. :has() has('.example', '.demo', ' /* rule */') What we can run in JS:
  • 99. 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 }, '') }
  • 100. Previous Selector .example:previous { /* rule */ } What we want in CSS:
  • 102. Previous Selector .example[ --previous] { /* rule */ } What we can write in CSS:
  • 103. Previous Selector previous('.example', ' /* rule */') What we can run in JS:
  • 104. Contains String .example:contains-text('demo') { /* rule */ } What we want in CSS:
  • 106. Contains String .example[ --contains-text="'demo'"] { /* rule */ } What we can write in CSS:
  • 107. Contains String containsText('.example', 'demo', ' /* rule */') What we can run in JS:
  • 108. Attribute Comparison .example[price > 50] { /* rule */ } What we want in CSS:
  • 109. Attribute Comparison el.getAttribute(attr) >= number What we can test with JS:
  • 110. Attribute Comparison .example[ --attr-greater="'price', 50"] { /* rule */ } What we can write in CSS:
  • 111. Attribute Comparison attrGreater('.example', 'price', 50, ' /* rule */') What we can run in JS:
  • 112. Style based on any property or test you can write, on any element, when any event happens in the browser
  • 113. Style based on any property or test you can write, on any element, when any event happens in the browser
  • 114. Style based on any property or test you can write, on any element, when any event happens in the browser
  • 115. — 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.”
  • 117. // 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 }, '') }
  • 119.
  • 120. 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 }, '') }
  • 121. /* :has(li) */ //*[li] [ --xpath] { background: lime; } /* li:parent */ //li/parent::* [ --xpath] { background: hotpink; } /* li:contains-text('hello') */ //*[contains(text(),'hello')] [ --xpath] { background: purple; }
  • 122. xpath( rule.selectorText .split('[ --xpath]')[0] .replace(/ /g, '') .trim(), rule.cssText.split(rule.selectorText)[1] .trim() .slice(1, -1) )
  • 123. 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) )) } }) )
  • 125. Media queries took
 11 years to specify
  • 126. Media Queries @media (min-width: 500px) { /* stylesheet */ } What we wanted in CSS:
  • 127. Media Queries window.innerWidth >= number What we could test with JS:
  • 128. Media Queries media({minWidth: 500}, ' /* stylesheet */') What we can run in JS:
  • 129. 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 : '' }
  • 130. What CSS features do you want 10 years from now?
  • 132. 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 === (
  • 133. 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 }
  • 134. } 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 }, '') }
  • 135. 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 }, '') }
  • 142. Look up ‘jsincss’ on npm for some ideas
  • 143. Please feel free to publish your own ‘jsincss’ plugins!