HTML/Tutorials/Adventskalender

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
2019 bot SELFHTML wieder einen Adventskalender an, den wir nun ein bisschen näher vorstellen wollen. Dabei stellen wir einerseits Best practice-Methoden vor, erkären andererseits aber auch, warum wir manch vermeintlich einfacheren Weg nicht beschritten haben.

Aus Gründen der Einfachheit (z. B. der Einbindung in ein CMS) beschränken wir uns in diesem Tutorial auf clientseitige Technologien. Ein zweiter Teil bietet eine serverseitige Variante mit PHP.

HTML

Ein Monatskalender ist häufig eine Tabelle, da die Wochentage untereinander in einer Beziehung stehen. Bei einem Adventskalender sind die Türchen allerdings nur nummeriert – die Anordnung ist erst einmal egal. Wir entscheiden uns aus Gründen der Semantik für eine geordnete Liste. Schließlich sollte man die Türchen eines Adventskalenders auch der Reihe nach öffnen. Aber ganz ehrlich – wer hat das in der Kindheit immer so gemacht?

Die einzelnen Listenelemente enthalten genau einen Link. Entweder zum Artikel, zur Spendenseite oder den Hinweis, dass dieses Kästchen noch nicht geöffnet werden kann.

HTML-Markup des Adventskalenders
<ol>
<li>
  <a href="Link/zum/Artikel">
    <h2>Überschrift</h2>
    <p>Autor</p>
    <p>Teaser</p>
  </a>
</li>
<li>
  <a class="donate" href="https://selfhtml.org/spenden.html">
    <h2>Es ist noch zu früh.</h2>
    <p>Wussten Sie, dass Sie <span class="self">self</span>HTML unterstützen können?</p>
  </a>
</li>
<li>
  <a class="coming_soon">
    Dieses Türchen öffnet sich erst am 3.&nbsp;Dezember
  </a>
</li>
</ol>

Damit es innerhalb der Datumsangabe zu keinen Umbrüchen kommen kann, wird das Leerzeichen durch ein festes Leerzeichen &nbsp; ersetzt.

CSS

Mit CSS wird nun die Anordnung und das Aussehen festgelegt.

Grid Layout

Wie oben bereits erwähnt, haben die meisten Adventskalender eine Matrix, innerhalb der die einzelnen Türchen unregelmäßig verteilt werden. Mit Grid Layout kommen wir (fast) ohne media queries aus:

Matrix in Grid Layout
ol {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  max-width: 120rem;
}
@media (min-width: 80rem) { /* 4×6 */ 
  ol {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (min-width: 120rem) { /* 6×4 */ 
  ol {
    grid-template-columns: repeat(6, 1fr);
  } 
}

Die geordnete Liste wird mit display:grid zum responsiven Raster. Dabei werden mit grid-template-columns Spalten angelegt, die keine feste Breite haben, sondern sich mittels der repeat(auto-fill, minmax(20rem, 1fr))-Funktion an die vorhandene Viewportgröße anpassen. Dabei ist jedes Rasterelement mindestens 20rem – bei 39rem Viewportbreite würde es noch eine Spalte geben, bei 40rem dann automatisch zwei – bei 59em noch zwei, bei 60rem dann drei Spalten.

Dabei kommt uns zugute, dass die 24 recht viele Teiler besitzt:

1 × 24
2 × 12
3 × 8
4 × 6
5 × 4 Rest 4
6 × 4
7 × 3 Rest 3

Um zu verhindern, dass es in unserem Raster einen Rest an Feldern gibt, legen wir mit media queries für die maximalen Breiten 80em und 120em eine feste Spaltenanzahl mit grid-template-columns: repeat(4, 1fr); bzw. 6 Spalten fest.

zufällige Anordnung

Mit der CSS-Eigenschaft order können Sie die Reihenfolge innerhalb der Liste durcheinanderbringen.

Beispiel
li:nth-of-type(1) {
  order: 5;
}
li:nth-of-type(4) {
  order: 7;
}
li:nth-of-type(5) {
  order: 21;
}
li:nth-of-type(6) {
  order: 18;
}
li:nth-of-type(8) {
  order: 5;
}
li:nth-of-type(9) {
  order: 7;
}

Unschön ist hier die Vielzahl von CSS-Regelsätzen. Eine zufällige Sortierung lässt sich mit JavaScript oder PHP erreichen.

Nummerierung

Eine geordnete Liste stellt jedem Listenelement eine Ordinalzahl in der jeweiligen Formatierung voran. Um diese Zahl individuell gestalten zu können, bietet sich die CSS-Eigenschaft counter-increment an:

Nummerierung der Türchen ansehen …
html {
  --bgcolor: #e6f2f7;
  --linkcolor: #306f91;
  --bordercolor: #c32e04;
}
ol {
  counter-reset: advents-counter;
  list-style: none;  
}
li {
  padding: 0;
  border: thin solid var(--bordercolor);
  counter-increment: advents-counter;
  position: relative;
}

li::after {
  --size: 2rem;
  content: counter(advents-counter);
  position: absolute;
  color: var(--bgcolor);
  font-size: calc(var(--size) * .75);
  font-weight: bold;
  right: calc(var(--size) * .25);
  top: calc(var(--size) * .25);
  line-height: var(--size);
  width: var(--size);
  height: var(--size);
  padding: calc(var(--size) * .2);
  transform: rotate(-10deg);
  background-color: var(--bordercolor);
  border-radius: 50%;
  text-align: center;
  box-shadow: calc(var(--size) * .05) calc(var(--size) * .05) 0 #999;
}

Zuerst wird die Listenformatierung mit list-style: none; ausgeblendet und durch counter-reset: advents-counter; ersetzt.

Jedes Listenelement wird nun mit counter-increment: advents-counter; gezählt. Diese Nummer erscheint nun als Inhalt eines Pseudoelements content: counter(advents-counter) und wird entsprechend positioniert und gestaltet.

Um spätere Änderungen zu erleichtern, wurden die Farb- und Größenangaben in CSS-Variablen notiert, die so zentral geändert werden können. Für den IE 9-11, der dies noch nicht versteht, werden die Zahlen trotzdem angezeigt (Graceful degradation).

Gestaltung der Türen

Der Inhalt der Türchen, die noch nicht geöffnet werden können, soll hübsch zentriert sein,

Gestaltung der Türchen ansehen …
li > .donate, li > .coming_soon {
  margin: 0;
  align-self: center;
  text-align: center;
}

aber dennoch soll die ganze Fläche maussensitiv sein.

Beispiel
li > .donate::after, li > .coming_soon::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
Da das a-Element keine von static abweichende Positionierung besitzt, nimmt das Pseudoelement den ganzen Platz des li-Elements ein, das schon wegen der Nummerierung relative positioniert ist.

Die Türchen sollen sich ansprechend öffnen und genauso ansprechend auch wieder schließen.

Türchen versteckt und auf Klick öffnend ansehen …
li > a {
  text-decoration: none;
  color: var(--linkcolor);
}
li > :not(.open) {
  color: var(--bgcolor);
  transition: color ease .5s;
}
li > :not(.open):hover, li > :not(.open):focus {
  color: var(--linkcolor);
  transition: color ease .5s;
}
li > .coming_soon:hover {
  color: inherit;
}

Einfarbig == langweilig?

Wem die einfarbigen Türchen zu langweilig sind, ist aufgefordert, hübsch gestaltete Adventskalender via projekt@selfhtml.org einzureichen. Alle eingehenden und für gut befundenen Vorschläge werden veröffentlicht. Gegebenenfalls könnte man sogar ein paar Preise ausschreiben – vielleicht Schokoladenosterhasen?

JavaScript

Besuchte Türchen zeigen

Es wäre schön, wenn man die bereits besuchten und geöffneten Türchen sichtbar lassen könnte. Eigentlich gibt es dafür mit :visited eine Pseudoklasse, die bereits besuchte Elemente selektiert. Da dies in der Vergangenheit aber zum History Stealing missbraucht wurde, kann auf diese Weise nicht zwischen besuchten und unbesuchten Türchen unterschieden werden.

Deshalb wird dies nun mit JavaScript nachgebaut:

Besuchte Türchen bleiben offen ansehen …
'use strict';

function init () {
  if (localStorage) { 
     
    // read opened doors
    for (let k = 1; k < 25; k++) {
      if (localStorage.getItem('SELFday'+k)) {
        document.querySelector('li:nth-child('+k+') > a').classList.add('open');
      }
    }
  }

  const calendar = document.querySelector('ol');
  calendar.addEventListener('click', saveTheDoor);
}

function saveTheDoor (event) {
  const elem = event.target,
      link = elem.closest('a'),
      listitem = link.parentElement,
      day = Array.prototype.indexOf.call(calendar.children, listitem) + 1;
  
  if (link.classList.length == 0) {
    link.classList.add('open');
    if (localStorage) {
      localStorage.setItem('SELFday'+day,'open');
    }
  }
}

document.addEventListener('DOMContentLoaded', init);

Bei Laden der Seite wird nun in der Funktion setOpenDays() überprüft, ob es bereits ein LocalStorage-Objekt mit bereits geklickten Türchen gibt. Diese erhalten nun mit classList.add die Klasse open und sind von Anfang an sichtbar.

Fazit

Dieser Kalender muss täglich neu mit den aktuellen Links gefüttert werden. Schöner wäre es natürlich, wenn Sie mit PHP täglich das Datum ermitteln und die URLs dynamisch einfügen können.

Weblinks