From DHTML
to CSS Variables
@davidkpiano · Flashback Conference 2020
setInterval(move, 25)
requestAnimationFrame(move)
no idea
60 FPS
2012 →
document.body.appendChild(o)
var
let
const
<DIV ID="oDiv"
STYLE="background-color: #CFCFCF; position: absolute;
left: expression(document.body.clientWidth / 2 - oDiv.offsetWidth / 2);
top: expression(document.body.clientHeight / 2 - oDiv.offsetHeight / 2)">
Example DIV
</DIV>
CSS Expressions (IE7)
<style type="text/javascript">
tags.H1.color = "red";
tags.p.fontSize = "20pt";
with (tags.H3) {
color = "green";
}
with (tags.H2) {
color = "red";
fontSize = "16pt";
marginTop = "4cm";
}
</style>
JavaScript Style Sheets (Netscape 4)
CSS Custom Properties
--color-primary: blue;
<h1 style="--color-primary: blue;">
Hello Orlando
</h1>
:root {
--color-primary: blue;
}
h1 {
color: var(--color-primary, red);
}
section {
--color-primary: green;
}
Custom Property
Value
Default
.box {
--delta-x: 150px;
}
CSS
.box {
--delta-x: 150px;
transform: translateX(
var(--delta-x));
}
CSS
.box {
--delta-x: 150;
transform: translateX(
calc(var(--delta-x) * 1px));
}
CSS
.box {
transform:
translateX(calc(var(--delta-x) * 1px));
}
const box = document.querySelector('#box');
const hBox = new Hammer(box);
hBox.on('panleft panright', (e) => {
box.style
.setProperty('--delta-x', e.deltaX);
});
hBox.on('panend', () => {
box.style
.setProperty('--delta-x', 0);
});
CSS
JAVASCRIPT
.box {
--not-panning: calc(-1 * var(--panning) + 1);
transition:
transform
calc(var(--not-panning) * .6s)
ease-out;
transform:
translateX(calc(var(--delta-x) * 1px))
rotate(calc(var(--delta-x) * .1deg));
}
const box = document.querySelector('#box');
const hBox = new Hammer(box);
hBox.on('panleft panright', (e) => {
box.style
.setProperty('--delta-x', e.deltaX);
box.style
.setProperty('--panning', 1); // true
});
hBox.on('panend', () => {
box.style
.setProperty('--delta-x', 0);
box.style
.setProperty('--panning', 0); // false
});
CSS
JAVASCRIPT
calc(-1 * var(--panning) + 1) -1 * 1 + 1 = 0 -1 * 0 + 1 = 1
I warned you there would be lots of demos
CSS
CSSS
State
Fetch data
Fetch data
Fetch data
Fetch data
Normal
Hover
Active
Disabled
Fetched data
Success
Failed to fetch
Error
Fetching data...
Loading
Fetching data...
Loading
<button class="button loading">
Fetching data...
</button>
I wasn't always good at JavaScript.
input[type="checkbox"] {
/* ... */
}
input[type="checkbox"] ~ #app {
/* ... */
}
input[type="checkbox"] ~ #app .box {
background-color: white;
}
input[type="checkbox"]:checked
~ #app .box {
background-color: blue;
}
~
checkbox
#app
.box
~
Adjacent sibling selector
label for="..."
label for="..."
checkboxes
radios
Zero or more
One at a time
Fetching data...
<button class="loading">
Fetching data...
</button>
<button class="success">
Fetched data!
</button>
<button class="loading success">
Fetched data...?
</button>
Fetched data!
Fetched data...?
Is there a better way to model
state for dynamic UIs?
User flows are transitions
between UI states
due to events
Finite state machines
and statecharts
A state is a dynamic property expressing characteristics of an object that may change in response to user action or automated processes.
- aria-busy
- aria-current
- aria-disabled
- aria-grabbed
- aria-hidden
- aria-invalid
States do not affect the essential nature of the object, but represent data associated with the object or user interaction possibilities.
:state(...) {
/* ... */
}
:hover
:invalid
:active
:focus
:empty
:checked
Data Attributes
<button data-state="loading">
Fetching data...
</button>
button[data-state="loading"] {
opacity: 0.5;
}
elButton.dataset.state; // 'loading'
elButton.dataset.state = 'success';
delete elButton.dataset.state;
HTML
CSS
JavaScript
<button data-state="success">
Fetched data!
</button>
<button>
Fetch data
</button>
Data Attributes
Fetching data...
<button data-state="idle">
Fetch data
</button>
<button data-state="loading">
Fetching data...
</button>
<button data-state="success">
Fetched data!
</button>
<button data-state="error">
Failed to fetch data
</button>
Fetched data!
Failed to fetch
Fetch data
Loading
Success
Resolve
Initial state
States
Events
Transitions
Final State
Failed
REJECT
RETRY
Finite state machine
Define transitions between
states & events
function searchReducer(state = 'idle', event) {
switch (state) {
case 'idle':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
case 'searching':
switch (event.type) {
case 'RESOLVE':
return 'success';
case 'REJECT':
return 'failure';
default:
return state;
}
case 'success':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
case 'failure':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
default:
return state;
}
}
FSMs with switch/case
const machine = {
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
};
Define transitions between
states & events
const transition = (state, event) => {
return machine
.states[state] // current state
.on[event] // next state
|| state; // or same state
}
FSMs with object mapping
npm install xstate
const machine = {
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
};
import { Machine } from 'xstate';
const machine = Machine({
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
});
idle
dragging
pan
panstart
panend
(assign dx, dy)
(assign x, y)
<main data-state="loading" data-prev-state="idle">
idle
loading
error
<main data-state="loading" data-prev-state="error">
const elApp = document.querySelector('#app');
function setAppState(state) {
// change data-state attribute
elApp.dataset.prevState = elApp.dataset.state;
elApp.dataset.state = state;
// ...
}
✨✨✨
💥💥💥
[data-state="loading"][data-prev-state="idle"] {
/* loading initially */
}
[data-state="loading"][data-prev-state="error"] {
/* loading after error */
}
<main
data-state="success"
data-transition="loading success"
>
</main>
[data-transition^="loading"] {
/* from "loading" */
}
[data-transition="loading success"] {
/* from "loading" to "success" */
}
HTML
CSS
previous state
current state
start
selecting
selected
dragging
disposed
grabbing
disposed
data-state="idle"
data-show="loading"
data-hide="loading"
data-active
data-active
active when app is in "loading" state
inactive when app is in "loading" state
data-state="loading"
const elApp = document.querySelector('#app');
function setAppState(state) {
// change data-state attribute
elApp.dataset.state = state;
// remove any active data-attributes
document.querySelectorAll(`[data-active]`)
.forEach(el => {
delete el.dataset.active;
});
// add active data-attributes to proper elements
document
.querySelectorAll([
`[data-show~="${state}"]`,
`[data-hide]:not([data-hide~="${state}"])`
].join(','))
.forEach(el => {
el.dataset.active = true;
});
}
// set button state to 'loading'
setAppState('loading');
[data-show~="${state}"]
[data-hide]:not([data-hide~="${state}"])
Think in transitions
not just events,
not just state.
The world of statecharts
Spectrum community
XState Documentation
Make your code do more.
@davidkpiano · Flashback Conference 2020
Thank you Orlando!
From DHTML to CSS Variables
By David Khourshid
From DHTML to CSS Variables
Flashback Conf
- 4,200