CRAFTING UI

Frontend Design

Tegn med kode

Datavisualisering og UI-grafik

Datavisualisering

Datavisualisering

Det handler ikke kun om at vise data men om at designe den måde, data forstås på.

  • gør kompleks data læsbar, sammenlignelig og handlingsbar
  • fremhæver mønstre, relationer og afvigelser
  • fører (måske) til øget engagement

Vælg form efter budskab

  • Doughnut → del af helhed
  • Søjler → sammenlign størrelser
  • Linje → se retning og mønster

Datavisualisering

Data før grafik. Grafikken er et ekstra lag.

Datavisualisering

<figure>
  <figcaption>Erfaring pr. år</figcaption>
  <ul>
    <li>2023: 14 %</li>
    <li>2024: 26 %</li>
    <li>2025: 69 %</li>
  </ul>
</figure>

Hvis grafen forsvinder, skal data stadig kunne forstås

Datavisualisering og UI-grafik

CSS

SVG

Vores værktøjkasse

Typed attr()

Farvemanipulation

shape()

Gradienter

offset-path

<path>

<circle>

pathLength

stroke-dasharray

stroke-dashoffset

border-area

Pseudo-elementer

Farvemanipulation

color-mix() & Relative Color Syntax

color-mix()

color-mix()

Relative Color Syntax

Relative Color Syntax

Relative Color Syntax

hover- og active-state

Relative Color Syntax

Relative Color Syntax

Relative Color Syntax

a::after {
  content: '(opens in new tab)';
}

Pseudo-elementer

::before, ::after

a[href^="http"]::after {
  content: '(opens in new tab)';
}

Pseudo-elementer

::before, ::after

Pseudo-elementer

::before, ::after

a[href^="http"]::after {
  content: '(opens in new tab)';
}

Øvelse

nav.innerHTML += `<a href=${c.link}>${c.name}</a>` + " / ";

Pseudo-elementer

Øvelse

Pseudo-elementer

Øvelse

li + li::before {
  content: '/';
}

Pseudo-elementer

Øvelse

li + li::before {
  content: '/';
}

Pseudo-elementer

li:has(a)::after {
  content: '/';
}
li:has(+ li)::after {
  content: '/';
}
li:not(:last-child)::after {
  content: '/';
}
:not(:first-child)::before {
  content: '/';
}

Pseudo-elementer

Pseudo-elementer

Fra temaopgaven

Fra streng til datatype

[data-color]::after {
  content: attr(data-color);
}

Typed attr()

<h2 data-color="red"></h2>

Indsætter strengen "red"

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>));
}

Typed attr()

Sætter tekstfarven

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>), #fff);
}

Typed attr()

fallback

@scope

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>), #fff);
}

Typed attr()

<h2 data-color="red">
  Make me red
</h2>

@scope

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>), #fff);
}

Typed attr()

<h2 data-color="oklch(0.6 0.3 29)">
  Make me red
</h2>

@scope

Fra streng til datatype

Typed attr()

klik her

<div data-length="5rem"></div>
div {
  width: attr(data-length type(<length>));
  &::after {
    content: attr(data-length);
  }
}

Units vs. Types

Typed attr()

@scope

Units vs. Types

Typed attr()

<angle>

<color>

<custom-ident>

<image>

<integer>

<length>

<length-percentage>

<number>

<percentage>

<time>

Types of type()

Typed attr()

article {
  display: grid;
  grid-template-columns:
    repeat(8, 1fr);
  height: 300px;
}

div:nth-child(1) {
  height: 100%;
}
div:nth-child(2) {
  height: 70%;
}
...
<article>
  <div></div>
  <div></div>
  ...
</article>

Ikke så fleksibelt

10%
20%
30%
40%
50%
60%
70%
100%
article {
  display: grid;
  grid-template-columns:
    repeat(8, 1fr);
}

div:nth-child(1) {
  height: 100%;
}
div:nth-child(2) {
  height: 70%;
}
...
<article>
  <div style="height: 100%"></div>
  <div style="height: 70%"></div>
  ...
</article>

Data kommer typisk fra JS

10%
20%
30%
40%
50%
60%
70%
100%
bar.style.height = `${percent}%`
div {
  height: var(--height, 0%);
}
<article>
  <div style="--height: 100%"></div>
  <div style="--height: 70%"></div>
  ...
</article>

Det kunne ligeså godt se sådan ud

10%
20%
30%
40%
50%
60%
70%
100%
bar.style.setProperty("--height", `${percent}%`)
<article>
  <div data-percent="100"></div>
  <div data-percent="70"></div>
  ...
</article>

Eller sådan her

(en moderne løsning)

div {
  height: attr(data-percent %, 0%);
}
10%
20%
30%
40%
50%
60%
70%
100%
bar.dataset.percent = percent

attr()

div {
  width: attr(data-length type(<length>));
  &::after {
    content: attr(data-length);
  }
}

attr(data-number type(<number>), 0)

attr()

div {
  height: attr(data-percent %), 0%);
}

Lad os bygge en
responsiv Bar Chart

.bars {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  height: 300px;
}

Step 1: Orientering

Basic grid

<article class="bars">
  <div class="bar" data-percent="100"></div>
  <div class="bar" data-percent="70"></div>
  <!-- ... -->
.bar {
  height: attr(data-percent %); /* 70% */
}

Step 2: attr()

Sæt højden i %

<article class="bars">
  <div class="bar" data-percent="100"></div>
  <div class="bar" data-percent="70"></div>
  <!-- ... -->
.bar {
  height: attr(data-percent %, 0%); /* 0% */
}

Step 2: attr()

Husk fallback!

<article class="bars">
  <div class="bar" data-percent="100"></div>
  <div class="bar"></div>
  <!-- ... -->
.bar {
  background:
    color-mix(#3980DD attr(data-percent %, 0%), #DAE9FC);
}

Step 3: color-mix()

Procent-værdien styrer baggrundsfarven

.bar {
  --percent: attr(data-percent %, 0%);
  background:
    color-mix(#3980DD var(--percent), #DAE9FC);
  height: var(--percent);
}

Step 4: Housekeeping

Organiser med variabler

.bar {
  &::after {
    content: attr(data-percent) "%";
    
    position: absolute;
    top: -1lh;
    inset-inline: 0;
    justify-self: center;
  }
}

Step 5: pseudo-element

Vis procent-værdien

.bars {
  @container (width < 400px) {
    writing-mode: sideways-rl;
    height: 100%;
  }
}

.bar {
  min-width: 40px;
}

Step 6: writing-mode

Gør den responsiv

writing-mode

div {
  width: attr(data-length type(<length>));
  &::after {
    content: attr(data-length);
  }
}

writing-mode: sideways-lr | sideways-rl etc.

writing-mode

med flow-relative properties

writing-mode

med flow-relative properties

width

max-width

height

max-height

margin-left

margin-right

padding-top

top

inline-size

max-inline-size

block-size

max-block-size

margin-inline-start

margin-inline-end

padding-block-start

inset-block-start

writing-mode

med flow-relative properties

.bars {
  @container (width < 400px) {
    writing-mode: sideways-rl;
    height: 100%;
  }
}

.bar {
  min-width: 40px;
}

Step 6: writing-mode

Gør den responsiv

.bars {
  block-size: 300px; /* erstatter height */
  
  @container (width < 400px) {
    writing-mode: sideways-rl;
    block-size: 100%; /* erstatter height */
  }
}

.bar {
  min-inline-size: 40px; /* erstatter min-width */
  &::after {
    inset-block-start: -1lh; /* erstatter top */
  }
}

Step 7: Logical props

Gør boksmodellen flow-relativ

.bars {
  @container (width < calc((40px + 1rlh) * 8)) {
    /* ... */
  }
}

.bar {
  margin-inline: .5rlh;
  min-inline-size: 40px;
}

Tip

Brug calc() i @container

bar width

.bars {
  background:
    repeating-linear-gradient(
      to bottom,
      gainsboro 0 1px,
      transparent 0 25%
    );
}

Step 8: Gitterlinjer

Linjer med repeating-gradient

.bars {
  background:
    repeating-linear-gradient(
      var(--angle, to bottom),
      gainsboro 0 1px,
      transparent 0 25%
    );
}

Step 8: Gitterlinjer

Fix linjer på små skærme

.bars {
  background:
    repeating-linear-gradient(
      var(--angle, to bottom),
      gainsboro 0 1px,
      transparent 0 25%
    );
  @container (inline-size < 400px) {
    --angle: to left;
  }
}

Step 8: Gitterlinjer

Fix linjer ved breakpointet

Hvordan virker det?

.bars {
  background:
    repeating-linear-gradient(
      to bottom,
      gainsboro 0 1px,
      transparent 0 25%
    );
}

to bottom

0

1px

25%

(180deg)

Tegn med gradienter

Koniske, lineære, radiale

Tegn med gradienter

Koniske, lineære, radiale

Tegn med gradienter

.gradient-progress {
  background: linear-gradient(
    90deg,
    [FARVE 1] [PROCENT],
    [FARVE 2] 0
  );
}

Tegn med gradienter

linear-gradient progress

Tegn med gradienter

mask

Gitter

Fra temaopgaven

Gitter

Conic-gradient()

Doughnut chart

Fra temaopgaven

SVG

Fra bokse og gradients til koordinater, former og strokes.

Doughnut chart

viewBox giver os et stabilt koordinatsystem

<svg viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="50" />
</svg>

Vi tegner en circle med koordinaterne: centerpunkterne for x og y: 50, 50
radius: 50

x og y

cx og cy

stroke

stroke-width

stroke-dasharray

stroke-dashoffset

stroke-linecap

fill

pathLength

SVG-attributter

SVG Playground

Offset-path

.parent {
  .child {
    offset-path: border-box;
  }
}

Offset-path

svg {
  circle {
    offset-path: circle([COORDINATES]);
  }
}

Offset-path

Slides

Kursusmateriale

Crafting UI: Tegn med kode

By Dannie Vinther

Crafting UI: Tegn med kode

Crafting UI – Datavisualisering og UI-grafik

  • 176