Dynamic CSS with Vue
Dynamic CSS with Vue
VueConf US — March 26, 2019
Jacob Schatz: Design Patterns
OddBird
Full-Stack Client Services (including Vue)
Susy
Susy, scribbled out
HTML/CSS History
1993: HTML
Markup Language
OddBird siblings (Jonny, Carl, and Miriam) in 1994
1997: <font>
&&
<table>
HTML 3.2
(╯'□')╯︵
┻━┻
“TABLES ARE FOR DATA”
1996 2000: Cascading Style Sheets
(The End)
2007-2010: Grid Frameworks
Also: OddBird
Span some columns on a single axis
class="
column col6of12 last
"
CSS Systems by Natalie Downe
target
/
context
*
100%
Responsive before Responsive™
.grid-span {
width: 23.7288136%; // 3 columns & 2 gutters
margin-right: 01.6949153%; // 1 gutter
padding-left: 08.4745763%; // 1 column & 1 gutter
}
2009: Sass* & Susy
* When I found out about it…
.grid-span {
width: span(3);
margin-right: gutter();
padding-left: span(1 wide);
}
2009: @media
queries
Safari 4 – if/then
2011: Responsive Web Design
A zillion different devices of all sizes (original)
There are too many variables to consider. The point of CSS is to make it so you don’t have to worry about them all. Define some constraints. Let the language work out the details.
—Keith J Grant, Resilient, Declarative, Contextual
Chris Fritz: Explicit, clear relationships
First, Do No Harm
Declarative Syntax
!=
Static Results
Go with the flow!
2011: calc()
Firefox 4 – function
calc(
16px
+
20%
)
Combine Fluid & Fixed Widths!
2012: Flexbox
Chrome 21
flex:
1 0 30
em;
Define Ideal with Flexibility!
2014: CSS Variables
Firefox 31 (unless you count currentColor
)
--aka
:
'custom properties'
;
-<empty>-
browser-prefix
var(
--property-name
, fallback)
Sass Variables Scope
.example { $columns: 2; }
.nested-class { /* $columns == undefined */ }
Rahul Kadyan: Browsers dont understand SFC Sass
CSS Variables Inherit
.example { --columns: 2; }
.nested-class { /* var(--columns) == 2 */ }
@media (min-width: 30em) {
.example {
$columns: 6; /* 👎 {} scope only */
--columns: 6; /* 👍 30em+ browsers! */
}
}
Inherit Everywhere:
:root {
--brand-color: hsl(330, 100%, 45%);
}
Don’t Inherit:
* {
--brand-color: initial;
}
Avoid Nesting…
button {
background: blue
}
.this button {
background: red;
}
…For Lower Specificity
button {
background: var(--btn-color, blue);
}
.this {
--btn-color: red;
}
Safe Inline Styles
<button style="--color: blue;">
Use the Variable…
button {
background: var(--color, red);
}
…or Ignore the Variable
button.green {
background: green;
}
Missing Longhand (& Defaults)
.example {
--shadow-y: -1px;
box-shadow: var(--shadow-x, 0)
var(--shadow-y, 1px)
var(--shadow-blur, 0)
var(--shadow-color, currentColor);
}
Variables +
Calc ==
Functions
[data-span] {
--width: calc(var(--span) / var(--columns) * 100%);
width: var(--width, initial);
}
[data-span='3'] {
--span: 3;
}
[data-span='half'] {
--span: calc(var(--columns) / 2);
}
Smart Media-Queries
* {
--columns: 6;
@media (min-width: 45em) { --columns: 12; }
}
AG Grid Nesting Test from OOCSS [permalink / source]
Manipulate hsl()
Values
* {
--h: 330;
--s: 100%;
--l: 34%;
background: hsl(var(--h), var(--s), var(--l));
}
Hue is Radial
* {
--complement: calc(var(--h) - 180);
background: hsl(var(--complement), var(--s), var(--l));
}
Lightness is “Clamped”
* {
--threshold: 55;
--contrast: calc((var(--l) - var(--threshold)) * -100%);
color: hsl(var(--h), var(--s), var(--contrast));
}
Inspired by Facundo Corradini [permalink / source]
Variable Issues:
- Unknown value
type
s (transition known properties, not variables) - Don’t work inside
url()
=>var(--size)em
calc(var(--size) * 1em)
Houdini Properties & Values API *
* Experimental (behind Chrome flag)
In JavaScript
CSS.registerProperty({
name: "--brand-color",
syntax: "<color>",
initialValue: "pink",
inherits: true,
});
In CSS
@property --brand-color {
syntax: "<color>";
initialValue: "pink";
inherits: true;
}
2017: CSS Grid
Firefox 52 & Chrome 57 & Safari 10
Mind-blown gif of Gregg Pollack as Carl Sagan
Truly Two-Dimensional Layouts
columns & rows! mixed units!
Maria Lamardo: ARIA layout regions
Lines & Spans
.container {
display: grid;
grid-template-columns: repeat(12, 1fr);
}
.example {
grid-column: 1 / 4;
grid-row: 2 / span 2;
}
Named Areas
body {
grid-template-areas: 'header header'
'nav main'
'nav footer';
}
h1 { grid-area: header; }
nav { grid-area: nav; }
main { grid-area: main; }
footer { grid-area: footer; }
Single Source of Truth
@media (max-width: 50em) {
body {
grid-template: 'header'
'nav'
'main'
'footer';
}
}
2018: Intrinsic Web Design
- Fluid & Fixed
- Stages of Squishiness
- Truly Two-Dimensional Layouts
- Nested Contexts
- Expand & Contract Content
- Media Queries, As Needed
px
/em
/%
=>
fixed
fr
=>
fluid
minmax()
=>
fluid until fixed
auto
=>
flow
%
=>
relative to parent width
vw
=>
relative to the viewport
fr
=>
relative to available space
1fr
==
minmax(auto, 1fr)
use minmax(0, 1fr)
to allow shrinking
Let’s Add Vue
CSS & JavaScript
Andrew Krawchyk
Client Day Planner
<div style="
--start: 30;
--duration: 60;
" >
Krystal Campioni: Inline Styles
<div :style="{
'--start': item.start,
'--duration': item.duration
}" >
Levitated Toy Factory at Beyond Tellerand
Jen Looper: Zen and the Art of Vue
One does not simply “learn” JavaScript
Guillaume Chau: Dot animation demo
Container: Grid Values
<section
:style="{'--size': blipSize, '--gap': gapSize}">
section {
/* Grids! */
display: grid;
grid-auto-flow: dense;
}
section {
/* Default grid values */
--size: 5px;
--gap: 1px;
/* Calculaed total size, for maintaining ratio */
--grid: calc(var(--size, 5px) * 6 + var(--gap, 1px) * 4);
grid-auto-rows: var(--grid);
grid-template-columns: repeat(auto-fill, var(--grid));
grid-gap: var(--gap);
}
Invader: Grid Span
<div
v-for="invader in invaders"
:key="invader.id"
:style="{'--span': invader.scale}">
.invader {
/* Outer grid span */
grid-column-end: span var(--span);
grid-row-end: span var(--span);
/* Inner "blip" layout */
display: grid;
grid-gap: inherit;
grid-template: repeat(5, 1fr) / repeat(5, 1fr);
}
Blip: Placement & Alpha
<span
v-for="blip in invader.blips"
:key="blip.id"
:style="{'--alpha': blip.alpha, '--column': blip.column}" />
.blip {
/* Use alpha to turn blips on or off */
background-color: hsla(0, 0%, 100%, var(--alpha, 0));
/* Ensure the proper column layout */
grid-column-start: var(--column);
}
Bar Charts
<table class="chart" style="--scale: 100">
<tr class="year">
<th class="date">2000</th>
<td class="bar" style="--value: 45">45%</td>
</tr>
<!-- etc… -->
</table>
2000 | |
---|---|
2001 | |
2002 | |
2003 | |
2004 | |
2005 | |
2006 | |
2007 | |
2008 | |
2009 |
.bar {
--start: calc(var(--scale) + 1 - var(--value));
grid-row-start: var(--start);
}
Daniela Vázquez (@d4tagirl) data visualization at CSS Conf Argentina
name | x value | y value | size == z value |
---|---|---|---|
item-0 | 51 | 56 | 20 |
item-1 | 69 | 65 | 13 |
item-2 | 83 | 63 | 1 |
item-3 | 36 | 78 | 22 |
item-4 | 19 | 75 | 13 |
item-5 | 46 | 61 | 6 |
item-6 | 9 | 73 | 7 |
item-7 | 12 | 79 | 10 |
item-8 | 98 | 78 | 13 |
item-9 | 22 | 33 | 3 |
item-10 | 14 | 2 | 0 |
item-11 | 22 | 33 | 19 |
item-12 | 19 | 98 | 17 |
item-13 | 94 | 32 | 4 |
item-14 | 64 | 69 | 11 |
item-15 | 98 | 36 | 6 |
item-16 | 25 | 73 | 5 |
item-17 | 17 | 20 | 22 |
item-18 | 65 | 45 | 11 |
item-19 | 18 | 5 | 17 |
index | value |
---|---|
0 | 95 |
1 | 31 |
2 | 78 |
3 | 5 |
4 | 40 |
5 | 38 |
6 | 36 |
7 | 67 |
8 | 49 |
9 | 22 |
10 | 1 |
11 | 39 |
12 | 41 |
13 | 25 |
14 | 39 |
Krystal Campioni: Sprite animation
Sprite from Monster Slayer animation
return {
sprite: {
src: spriteSrc, // e.g. url('…')
columns: 10,
rows: 5,
},
}
return {
actions: [
{
name: 'idle',
row: 0, // 0 index
},
{
name: 'attack',
row: 1,
},
{ ... },
],
}
<section
class="sprite-demo"
:style="{
'--src': sprite.src,
'--columns': sprite.columns,
'--rows': sprite.rows,
}">
<div
v-for="action in actions" :key="action.name"
:data-action="action.name"
:style="{
'--row': action.row,
}"/>
</section>
.sprite-demo {
--square: 10em; // anything but `%`
--steps: calc(var(--columns) - 1);
display: grid;
grid-template-columns: repeat(auto-fit, var(--square));
grid-auto-rows: var(--square);
}
@keyframes sprite {
to {
background-position: 100% var(--posY);
}
}
[data-action] {
--posY: calc(0px - var(--square) * var(--row));
animation: sprite 1s steps(var(--steps), end) infinite;
background-image: var(--src);
background-size: calc(var(--square) * var(--columns)) auto;
background-position: 0% var(--posY);
width: 100%;
Animated sprites from Monster Slayer by Krystal Campioni [permalink / source]
Mauricio Palma Movement Demos
Rachel Andrew: gridbyexample.com
examples, templates, and fallbacks
Jen Simmons: youtube.com/layoutland
also: labs.jensimmons.com
Sarah Drasner: Think Bigger
Inspired by Stacy Kvernmo [permalink / source]
Have Fun!
Built with VueFinder