SCSS Workshop

Raphael Koch, Inware AG

LESS vs SCSS

Die Unterschiede zwischen LESS und SCSS

LESS
@import "/templates/definitions";

.responsive-sm(@rules) {
    @media (min-width: @breakpoint-sm) { @rules(); }
}

.block {
    &__elem {
        font-size: @elem-size;
        color: fade(@color-primary, 10%);
        width: ~'calc(100% - $grid)';

        .responsive-sm({
            font-size: @elem-size * 2;
        });

        &--red {
            --background: @color-red;
        }
    }
}
SCSS
@import "../../definitions";

@mixin responsive-sm {
    @media (min-width: $breakpoint-sm) { @content; }
}

.block {
    &__elem {
        font-size: $elem-size;
        color: rgba($color-primary, 10%);
        width: calc(100% - #{$grid});

        @include responsive-sm {
            font-size: $elem-size * 2;
        }

        &--red {
            --background: #{$color-red};
        }
    }
}

Source-Maps

SCSS hat einen kleinen Helfer welcher beim Debuggen helfen kann.

Source-Maps mappen die Source einer CSS-Declaration. Anstelle von /temp/scss_13435253353.css steht,
dann die spezifische SCSS-Datei wo es deklariert wurde.

Das setzen der Source-Maps greift erst, wenn die CSS-Dateien neu compiled werden.
Mit dem URL-Parameter cssRefresh=1 kannst du das CSS-Caching umgehen und ein Recompiling aller SCSS der aktuellen Seite starten.

$options = [
    'sourceMap' => true,
    'sourceMapInline' => true
];

// Im parseContent der Class Seiten
$buffer = OutputBuffer::filter(OutputBuffer::SCSS_SRCES, 'Scss::getCss', $buffer, 0, $options);

Mehr SCSS

Variablen

Neben der normalen Variablendeklaration gibt es auch noch das Flag !default.

Man kann es verstehen wie:
«Es sei denn, diese ist bereits zugewiesen»

@import '../../definitions';

$gap: 1rem !default;

.grid {
  display: grid;
  gap: $gap;
}

Mixins

SCSS erlaubt es bei Mixins auch Default-Werte mitzugeben. So kann ein Mixin mit oder ohne Parameterangabe aufgerufen werden.

Parameter mit Default-Wert müssen immer nach Parameter ohne Default-Wert kommen.

SCSS erlaubt es auch vorhergehende Parameter als Default-Wert zu benutzen und auch deren Default-Wert.

Mixins können auch in anderen Mixins verwendet werden und mit @content; kann man einen Content-Block im Mixin platzieren.

@mixin rectangle($width, $height: 100px) {
  width: $width;
  height: $height;
}

@mixin square_default($width: 100px, $height: $width) {
  width: $width;
  height: $height;
}

.box {
  @include rectangle(50px);

  &--square {
    @include square_default();
  }
}

@mixin content_wrap($value) {
  .wrap {
    @include square_default($value);
    @content;
  }
}

Logik und if-Konstrukt

SCSS hat Standard Logik, wie man sie so kennt.

Dazu auch das if-else um Fallunterscheidungen in Funktionen oder Mixins zu nutzen. Es kann aber auch ausserhalb von Funktionen und Mixins benutzt werden.

Achtung: Das ist nur on-compile-time.

$true: true;
$false: false;

// Boolean Logic
$and: $true and $false;
$or: $true or $false;
$not: not $true;
$combined: not ($true and $false) or $false;

// equality
$equal: 2 == $var;
$unequal: 2 != $var;
$larger_2: 2 < $var;
$smaller_2: 2 > $var;

// if-else
@if $gap == 2rem {
  .gap::before {
    content: 'gap is 2rem';
  }
} @else if $gap == 3rem {
  .gap::before {
    content: 'gap is 3rem';
  }
} @else {
  .gap::before {
    content: 'gap is something else';
  }
}

Funktionen

SCSS erlaubt es eigene Funktionen zu definieren. So kann man einfach neue Funktionalitäten hinzufügen.

Bei unserem SCSS-Compiler habe ich bereits einige Funktionen PHP-seitig eingebaut, welche ihr nutzen könnt:

  • stripUnit($value) - entfernt die Einheit von der Zahl
  • toEm($value) - Pixel zu em Transformation auf Basis 16
  • toRem($value) - Pixel zu rem Transformation auf Basis 16
  • toPx($value) - rem zu Pixel Transformation auf Basis 16
  • toRgb($value) - HEX zu RGB Transformation
  • toData($path) - Datei zu Data-URI
  • toDataRecolor($path, $color) - SVG zu Data-URI und ersetzen von currentColor auf $color Wert

@function percent($target, $container) {
  @return ($target / $container) * 100%;
}

@function isOdd($number) {
  @return $number % 2 == 0;
}

.block {
  $container-width: 1000px;
  width: percent(200px, $container-width);

  @if isOdd(stripUnit($container-width)) {
    &--odd {
      background-color: blue;
    }
  } @else {
    &--even {
      background-color: red;
    }
  }
}

Listen und Maps

Ähnlich wie andere Programmiersprachen unterstützt SCSS Arrays und Hash-Maps. Hier genannt Lists und Maps. Diesen kommen zusammen mit den entsprechenden Funktionen um sie nutzen zu können.
Auch mehr-dimensionale Listen oder Maps sind möglich, oder Maps mit Listen etc.
Achtung: Listen in SCSS sind 1-indexiert!

Die Listen-Funktion length kann auch auf Maps genutzt werden.

// Define a list
$countries: 'usa', 'japan', 'china', 'france', 'sweden';

// Inspect a list
$length: length($countries);
$nth: nth($countries, 1); // Starts at 1
$index: index($countries, 'france');

// Add element to a list
$countries: append($countries, 'mexico');

// Join lists
$more-countries: 'egypt', 'thailand';
$countries: join($countries, $more-countries);

// Define a map
$category-colors: ('news': #558899, 'sport': #182939, 'art': #c7d829,);

// Get a value
$news-color: map-get($category-colors, news);
$news-color: map-get($category-colors, 'news');

// Check if a map has a value
$has_economy: map-has-key($category-colors, economy); // false
$has_news: map-has-key($category-colors, news); // true

// Remove key-value pair
$category-colors: map-remove($category-colors, news);

// Merge maps (also used to add a new pair)
$economy: ('economy': #3549dd,);
$category-colors: map-merge($category-colors, $economy);

Interpolation

Interpolation nennt man das «Escapen» was ich bei calc erwähnt hatte. Sie können jedoch weitaus mehr. So können Properties dynamisch gesetzt, oder Klassennamen automatisch generiert werden. Besonders nützlich sind diese in Kombination mit Iteratoren oder Mixins.

Notwendig sind Interpolationen in Klassennamen, Properties, in Strings und überall wo CSS arithmetische Operationen erlaubt, wie CSS-Variablen oder calc.

$color: red;
$padding: 1em;
$icon: 'sparta';

@mixin position($top-or-bottom, $left-or-right) {
  position: absolute;
  #{$top-or-bottom}: 0;
  #{$left-or-right}: 0;
}

.#{$color}-box {
  --color: #{$color};

  @include position(top, left);
  width: calc(100px - #{$padding});
  content: 'Color is #{$color}';
  background-image: url("/icons/#{$icon}.svg");
}

Iteratoren

SCSS hat drei Arten von Iteratoren for-Loop, each-Loop und while-Loop. Diese sind nützlich wenn mal viele ähnliche Klassen definieren muss.
Zum Beispiel in der Grid-Datei welche wir benutzen.

Achtung: Iteratoren können sehr schnell riesige CSS-Dateien erstellen, seid deshalb vorsichtig mit diesen.

// @for
@for $i from 1 to 3 {
  .box-#{$i} {
    content: 10px * $i;
  }
}
@for $i from 1 through 3 {
  .alt-box-#{$i} {
    content: 10px * $i;
  }
}

// @each
$category-colors: ('news': #558899, 'sport': #182939, 'art': #c7d829,);
@each $country in $countries {
  .#{$country} {
    content: 10px * index($countries, $country);
  }
}
@each $category, $color in $category-colors {
  .#{$category} {
    color: $color;
  }
}

// @while
$column: 4;
@while $column > 0 {
  .cols-#{$column} {
    width: 10px * $column;
  }
  $column: $column - 2;
}

Extend

Extend ist inspiriert von OOP. So kann man ganz einfach Code-Wiederholungen verhindern und das compilierte CSS klein halten.

Mit Extend kommen auch noch die Placeholder Selectors. Das sind Selectors welche nur extended werden können. Sie werden vom Compiler sonst ignoriert. Die Definition von Placeholder Selectors ist gleich wie normale Class-Selectors, aber statt ein Punkt verwendet man ein Prozent-Zeichen.

Das Limit von Extend ist bei Nesting, Suffixe können nicht vererbt werden.

Achtung: Auch die Parents von der Klasse werden geerbt!

%centered {
  text-align: center;
  margin: auto;
  &:focus {
    color: blue;
  }
}

.box-1 { @extend %centered; }

.box-wrapper {
  .box-2:hover {
    @extend %centered;
    color: red;
  }
}
.box-3 { @extend .box-1; }

.box-4 { @extend .box-2; }

// CSS Output
.box-1, .box-3, .box-wrapper .box-2:hover, .box-wrapper .box-4:hover {
  text-align: center;
  margin: auto;
}
.box-1:focus, .box-3:focus,
.box-wrapper .box-2:focus:hover,
.box-wrapper .box-4:focus:hover {
  color: blue;
}
.box-wrapper .box-2:hover,
.box-wrapper .box-4:hover {
  color: red;
}

Was mach das wohl?

SCSS
@mixin inline-animation($durations, $name: 'animation', $default: 2s, $iterations: infinite) {
    @if length($durations) > 0 and (false != index($durations, $default)){
        $animation: #{$name}-#{unique-id()}; // built-in function
        @keyframes #{$animation} { @content; }
        @each $duration in $durations {
            $suffix: '';
            @if $duration != $default { $suffix: '-#{$duration}'; }
            .#{$name}#{$suffix} { animation-duration: $duration; }
        }
        $list: '';
        @for $i from 1 through length($durations) {
            $duration: nth($durations, $i);
            $suffix: '';
            @if $duration != $default { $suffix: '-#{$duration}'; }
            @if ($i != length($durations)) { $suffix: '#{$suffix},'; }
            $list: '#{$list} .#{$name}#{$suffix}';
        }
        #{$list} {
            animation-name: $animation;
            animation-iteration-count: $iterations;
        }
    }
}

$durations: 2s, 4s, 8s, 16s;
@include inline-animation($durations, 'pulse') {
    from { background-color: yellow }
    to { background-color: red }
}
@include inline-animation($durations, 'pulse-blue') {
    from { background-color: blue }
    to { background-color: cyan }
}
CSS

Tipps und Tricks

An Stelle von repetitivem Code zu schreiben nutzt die Placeholder Selectors und @extend. Persönlich erstelle ich eine generics-Datei wo sich solche Placeholder Selectors befinden welche ich über verschieden Entrytypes oder auch Components nutzen will.

Nutzt die Mixins und Variablen, so können einfache anpassungen über die Definitionen gemacht werden (Dies gilt auch für eure LESS Projekte).
Zum Beispiel, gibt es in der definitions-Datei von SCSS das zusätzliche Mixin hover-only. So kann man Hover-Effekte ganz einfach damit wrappen und man hat keine komischen Farbänderungen etc. mehr auf Touchgeräten!

Ihr habt im vorigen Beispiel gesehen, SCSS-Code kann man ziemlich komplex schreiben. Versucht also euren Code leserlich zu halten und auch hier wie überall sonst, Kommentare im Code können durchaus hilfreich sein.

@import "../../generics";
.Entrytype__button {
    @extend %button;
    &--outline {
        @extend %button-outline;
    }
}

@mixin truncate($width, $height: auto) {
    white-space: nowrap;
    overflow:hidden;
    text-overflow: ellipsis;
    width: $width;
    height: auto;
}

a {
    color: $blue;
    @include truncate(toRem(320px));
    @include hover-only {
        &:hover {
            color: $red;
        }
    }
}

The End