🏠

Dynamic CSS with Vue

Miriam Suzanne | @mirisuzanne | @oddbird

OddBird

Dynamic CSS with Vue

VueConf US — March 26, 2019

OddBird Faces

OddBird

Full-Stack Client Services (including Vue)

HTML/CSS History

1993: HTML

Markup Language

1997: <font> && <table>

HTML 3.2

(╯'□')╯︵ ┻━┻

“TABLES ARE FOR DATA

1996 2000: Cascading Style Sheets

CSS is Awesome (The End)

2007-2010: Grid Frameworks

Also: OddBird

class="column col6of12 last"

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

Ethan Marcotte

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

First, Do No Harm

CSS is Awesome

Declarative Syntax

!=

Static Results

Go with the flow!

Change Me!
Next In Flow
Last In Flow

2011: calc()

Firefox 4 – function

calc( 16px + 20% )

Combine Fluid & Fixed Widths!

2012: Flexbox

Chrome 21

flex: 1 0 30em;

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 */ }

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; }
}

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));
}

Complement

Variable Issues:

  1. Unknown value types (transition known properties, not variables)
  2. Don’t work inside url()
  3. 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

Truly Two-Dimensional Layouts

columns & rows! mixed units!

Grid lines 1-indexed and -1 in reverse (see dev tools) [permalink / source]

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';
  }
}

Resize window to see media-query in action… [permalink / source]

2018: Intrinsic Web Design

Jen Simmons

  1. Fluid & Fixed
  2. Stages of Squishiness
  3. Truly Two-Dimensional Layouts
  4. Nested Contexts
  5. Expand & Contract Content
  6. 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

Client Day Planner

Screenshot of schedule grid

<div style="
  --start: 30;
  --duration: 60;
" >
<div :style="{
  '--start': item.start,
  '--duration': item.duration
}" >
1
2
3
4
5
6
7
8
9
10

This one is for Maria [permalink / source]

Levitated Toy Factory at Beyond Tellerand

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
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]

Lea Verou on Variables

video | slides

Rachel Andrew: gridbyexample.com

examples, templates, and fallbacks

CSS is Awesome

Inspired by Stacy Kvernmo [permalink / source]

Stay in touch…