Javascript / 2019-12-28 / Matthias Edler-Golla, CC BY-SA 4.0


|



 



Themen

Bisher nicht für die Studierenden – aktuell ein Nachschlagwerk für mich…

basierend auf dem Video-Kurs „Beginner Javascript“ von Wes Bos


Let und Const Variablen

Javascript

// Variable, von der sich der Wert nicht ändern wird
const meinParagraph = document.querySelector('article.demo');

// Variable, deren Wert innerhalb der Scripte geändert wird
let anzahl = 0;

function loop() {
  anzahl += 1; // zählt um eins hoch;
}

loop();

QuerySelector, einfacher Click-Event

Zum Auswählen eines Elementes auf einer HTML-Seite.

Es können dazu die gleichen Selectoren verwendet werden wie bei CSS!

Demo

Javascript

// der erste Paragraph wird ausgewählt
// querySelector holt immer das 1. Element, wenn es mehrere gibt
const p1 = document.querySelector("p");
p1.style.color = "blue";

// der zweite Paragraph
const p2 = document.querySelector("p:nth-of-type(2)");
p2.style.background = "#ddd";

// das span im dritten Paragraph
const span = document.querySelector("p span");
span.style.fontWeight = "bold";

// der Button soll die HG-Farbe ändern
const button = document.querySelector("button");
function hgFarbeAendern() {
  const html = document.querySelector("html");
  html.style.transition = "background-color 0.5s";
  html.style.backgroundColor = "#ddd";
}
button.addEventListener("click", hgFarbeAendern);

QuerySelector, Mouseenter, Mousemove, Mouseleave

Unterschiedliche Mouse-Events…

Demo

Javascript

const meinBild = document.querySelector(".meinBild img");
const ausgabe = document.querySelector("#ausgabe");

meinBild.addEventListener("mouseenter", function(e) {
  ausgabe.textContent = `mouseenter: Scr: ${e.target.src}`;
});

meinBild.addEventListener("mouseleave", function(e) {
  ausgabe.textContent = `mouseenter: Alt: ${e.target.alt}`;
});

// gibt die aktuelle Posion des Mousezeigers aus
meinBild.addEventListener("mousemove", logKey);

function logKey(e) {
  ausgabe.innerText = `
    mousemove:
    Screen X/Y: ${e.screenX}px, ${e.screenY}px
    Client X/Y: ${e.clientX}px, ${e.clientY}px`;
}

Klasse via Javascript umschalten

Für angehende Webdesigner*innen besonders geeignet: Javascript schaltet nur eine Klasse an/aus. Den Rest (incl. Animationen) erledigt CSS!

Schaut Euch die Demo an – oder ladet das gesamte Paket herunter

CodePen

See the Pen CSS-Klasse-via-Javascript-umschalten by Matthias Edler-Golla (@megolla) on CodePen.

HTML

<div id="wichtig">
    <h2>Überschrift</h2>
    <p>Lorem ipsum dolor…</p>
</div>
…
<footer>
    <button id="meinKnopf">Klasse ändern</button>
</footer>

CSS

#wichtig {
    margin: 1.5em 0;
    transition: all 0.3s ease-in-out;
}
…
.betont {
    font-size: 150%;
    padding: 1rem;
    border-radius: 10px;
    background-color: #ffea9a;
    transform: rotate(720deg);
}

die Klasse ".betont“ ist nicht im HTML-Code enthalten! Sie wird via Javascript bei "div#wichtig" hinzugefügt und wieder entfernt – also "getoggelt"

Javascript

function klassenToggle(element, klasse) {
  const ele = document.querySelector(element);
  ele.classList.toggle(klasse);
}

function klickKlasseEinAus(ausloeser, element, klasse) {
  document.querySelector(ausloeser).addEventListener("click", function() {
    klassenToggle(element, klasse);
  });
}

// Ihr müsst nur die untere Zeile (Zeile 14) anpassen und oben aufgeführten Code 1:1 übernehmen!
// Aufruf: klickKlasseEinAus(auslösendesElement, betroffenesElement, KlassenName);
klickKlasseEinAus("#meinKnopf", "#wichtig", "betont");

Custom Function

Funktionen können direkt innerhalb von Variablen oder Console.logs aufgerufen werden.

Die (neue) Schreibweise mit Backticks beachten! Diese befinden sich auf der Tastatur neben dem ß…

Demo

Javascript

function calculateBill(wert) {
  const total = wert * 1.19;
  return total;
}

// holt den Netto-Wert direkt aus dem HTML
const netto = document.querySelector("#netto");
const nettoWert = netto.textContent;

// das Script wird beim Laden der Website ausgeführt
// durch das "* 100 / 100" wird auf 2 Stellen hinter Komma gerundet
const myTotal = Math.round(calculateBill(nettoWert) * 100) / 100;

// die Backticks beachten!
console.log(`Mein Wert ist $ ${myTotal}`);

const brutto = document.querySelector("#brutto");
brutto.textContent = myTotal;

// auch interessant, man kann Funktionen direkt aufrufen!
// Backticks beachten!
const verbose = document.querySelector("#verbose");
const meinWert = 220;
verbose.textContent = `
Meine Gesamtsumme bei netto €${meinWert}
ist brutto €${calculateBill(meinWert)}
`;

Funktionen erzeugen

Demo

Javascript

// anonymize functions direkt als Variable
// beachte, die Funktion hat keinen Namen!
const doctorize = function(name = "") {
  return `Dr. ${name}`;
};

// Aufruf in Konsole
console.log(`Hallo ${doctorize("Heinz")}!`);

// Ein HTML-Element mit der ID "doctor"
const doctor = document.querySelector("#doctor");
doctor.textContent = `Hallo ${doctorize("Hurzelmann")}!`;

// Berechnung gleich im Return
// "5" ist der Default-Wert, falls kein Wert übergeben wird
function inchToCM(inches = "5") {
  return `${inches * 2.54}cm`;
}
console.log(inchToCM(10));

// anonymous function
const inchToCM2 = function(inches = "5") {
  return `${inches * 2.54}cm`;
};
console.log(`Das sind dann ${inchToCM2(100)}`);

// Arrow-functions
// kürzeste Schreibweise, wenn nur ein Parameter abgefragt wird
const inchToCM4 = inches => inches * 2.54;
console.log(`Die Umrechnung ist ${inchToCM4(12)}cm`);

setTimeout-Funktion

Die jeweiligen Anweisungen werden jeweils nach dem angegebenen Zeitintervall (nach Laden der Seite) einmalig ausgeführt:

Demo

Javascript

const myTimeOut = document.querySelector("#timeOut");
myTimeOut.style.opacity = "0";

// ext. Funktion, die bei Zeile 11 aufgerufen wird
function handleTimes() {
  console.log("Hello!");
  myTimeOut.style.opacity = "1";
}

// Timer Callback
setTimeout(handleTimes, 10000);

// Timer Callback mit anonymer Funktion
setTimeout(function() {
  const myTimeOut2 = document.querySelector("#timeOut2");
  myTimeOut2.style.fontSize = "150%";
}, 5000);

// Timer Callback mit Arrow-Funktion
setTimeout(() => {
  document.body.style.backgroundColor = "#ccc";
  const myPic = document.querySelector("img");
  myPic.style.width = "200px";
  myPic.style.borderRadius = "50%";
  myPic.src = "https://picsum.photos/200";
}, 3000);

setTimeout-Funktion mit Anhalten

  • Via Button eine TimeOut-Funktion anhalten
  • eine selbstablaufende Animation (via SetInterval) nach best. Zeit automatisch anhalten (ebenfalls mit TimeOut)

Demo

Javascript

const paragraph = document.querySelector("p");
const stopButton = document.querySelector("#stop");

function austauschen() {
  paragraph.textContent = "AUSGETAUSCHT!";
  paragraph.classList.add("ausgetauscht");
}

const austausch = setTimeout(austauschen, 5000);

stopButton.addEventListener("click", function() {
  // hier die Variable "austausch" ansprechen
  clearTimeout(austausch);

  paragraph.innerHTML += "<span style='color:red'>--- Timeout gestoppt!</span>";
});

// ===================== Animation des roten Kreises =====================

const kreis = document.querySelector("#kreis");

const div = document.querySelector("div");
// console.log(div.offsetWidth);
const divBreite = div.offsetWidth;

let x = 0;

function kreisAnimieren() {
  x = x >= divBreite ? (x = 0) : (x = x + 50);
  kreis.setAttribute("style", `--x: ${x}px;`);
}

// falls die Sache sofort ablaufen soll, einmal beim Laden aufrufen
// nächster Aufruf dann bei "setInterval"
kreisAnimieren();

const kreisAni = setInterval(kreisAnimieren, 2000);

function kreisAniStoppen() {
  clearInterval(kreisAni);
  kreis.textContent = "automatisch angehalten!";
}

// damit hört die kreisAni nach 20sek auf
const kreisAniStop = setTimeout(kreisAniStoppen, 20000);

Text eines Elements abfragen bzw. ändern

Es gibt sowohl die Möglichkeit, reinen Text oder auch HTML abzufragen bzw. einzufügen

Demo

Javascript

// Abfrage des ausgangsText
const ausgangsText = document.querySelector("#ausgangsText");
const ausgangsTextInhalt = ausgangsText.textContent;
console.log(ausgangsTextInhalt);

// Ausgangstext bei Zieltext als reinen Text einfügen
const zielText = document.querySelector("#zielText");
zielText.textContent = `Kopiert: ${ausgangsTextInhalt}`;

// in Ziel HTML einfügen
const zielHTML = document.querySelector("#zielHTML");
zielHTML.innerHTML = `<strong>Kopiert:</strong> <em style="background:#ccc">${ausgangsTextInhalt}</em>`;

// HTML-Tag durch outerHTML ersetzen
// Beachte, hier wird das "span"-Element durch das "em"-Element ersetzt!
const ausgangsHTML = document.querySelector("#ausgangsHTML");
const ausgangsHTMLInhalt = ausgangsHTML.outerHTML;
console.log(ausgangsHTMLInhalt);

const zielHTML2 = document.querySelector("#zielHTML2");
zielHTML2.outerHTML = ausgangsHTMLInhalt;

// bei Zieltext Text davor und danach einfügen
const zielText2 = document.querySelector("#zielText2");
zielText2.insertAdjacentText("beforebegin", "Achtung: ");
zielText2.insertAdjacentText("afterend", ": Ende!");

// bei Zieltext Text einfügen
const zielHTML3 = document.querySelector("#zielHTML3");
zielHTML3.insertAdjacentHTML("afterbegin", "<em>Achtung: </em>");
zielHTML3.insertAdjacentHTML("beforeend", "<strong> ENDE</strong>");

(Data-)Attribute eines Elementes abfragen bzw. ändern

Viele Attribute lassen sich direkt via JS abfragen bzw. setzen! Das kann man beim jeweiligen Elemente mit folgendem Console-Aufruf abfragen:

const bild1 = document.querySelector("figure:nth-of-type(1) img");
console.dir(bild1); 

Demo

Javascript

const bild1 = document.querySelector("figure:nth-of-type(1) img");

const bild1figcaption = document.querySelector(
  "figure:nth-of-type(1) figcaption"
);

//CSS-Breite verändern
bild1.style.width = "300px";

bild1figcaption.innerHTML = `
  Klassen: ${bild1.classList}<br>
  Alt-Text: ${bild1.alt}<br>
  Natürliche Größe: Breite ${bild1.naturalWidth}px, Höhe ${bild1.naturalHeight}px<br>
  Dataset: ${bild1.dataset.vorname}, ${bild1.dataset.nachname}, ${bild1.dataset.geburtstag}
`;

CSS eines Elementes ändern

Demo

Javascript

const p1 = document.querySelector("p:nth-of-type(1)");

// Schreibweise von "backgroundColor" beachten!
p1.style.backgroundColor = "rgba(0,0,0,.1)";
p1.style.color = "red";
p1.style.padding = "1em";
p1.style.borderRadius = "5px";

const div = document.querySelector("div");
// Backticks beachten -- hier wieder normale CSS-Schreibweise
// mit Bindestrichen!
div.style.cssText = `
  background-color: blue;
  color: #fff;
  padding: 1em;
  border-radius: 10px;
  box-shadow: 0 0 12px 6px rgba(0,0,0,.5);
`;

HTML erzeugen und einfügen

Demo

Javascript

const myParagraph = document.createElement("p");
myParagraph.textContent = `
  Ich bin ein via JS erzeugter Paragraph,
  der sich im von JS erzeugten <main>-Element befindet.
  Auch das Bild ist via JS eingefügt worden
  `;
myParagraph.classList.add("spezial");
console.log(myParagraph);

const myImage = document.createElement("img");
myImage.src = "https://picsum.photos/500";
myImage.alt = "Tolles Foto!";
myImage.style.width = "300px";
myImage.style.display = "block";
console.log(myImage);

// hier sollen myParagraph und myImage eingefügt werden
const myMain = document.createElement("main");
myMain.classList.add("wrapper");
console.log(myMain);

// "appendChild" fügt die erzeugten Elemente am Ende des jeweiligen Elements (hier body) an!
// die erzeugten Elemente werden innerhalb andere erzeugter Elemente "verschachtelt"
// besser das innere zuerst in das äußere verschachteln -- dann kann Browser das besser darstellen
myParagraph.appendChild(myImage);
myMain.appendChild(myParagraph);
document.body.appendChild(myMain); // erst hier wird der Browser veranlasst, die Seite neu zu zeichnen -> weniger Unruhe

// footer soll ganz unten gezeigt werden
const footer = document.createElement("footer");
footer.style.cssText = `
  background-color: #000;
  color: #fff;
  padding: .2em 1em;
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
`;
footer.textContent = "Ich bin der via JS erzeugte Footer!";
document.body.insertAdjacentElement("beforeend", footer);

HTML-Element clonen und einfügen

Demo

Javascript

// hier soll der Clone eingefügt werden
const myMain = document.createElement("main");
document.body.appendChild(myMain);

// Clonen eines kompletten HTML-Elementes incl. Inhalt
const myH1 = document.querySelector("header h1"); // das Original
const myH1Clone = myH1.cloneNode(true); // der Clone incl. Inhalt und HTML-Tags
myH1Clone.style.background = "hsl(170, 50%, 90%)";
myH1Clone.style.padding = "1em";
myH1Clone.style.margin = "1em 0";

// einfügen des Clones bei myMain
myMain.insertAdjacentElement("beforeend", myH1Clone);

// verschieben eines HTML-Elementes (nicht clonen!)
myMain.insertAdjacentElement("beforeend", document.querySelector("h2"));

HTML aus Strings und Variablen erzeugen

Demo

Javascript

const meinArticle = document.querySelector("#first");

// Hiermit wird der komplette Inhalt des Artikels ausgetauscht
// mit den Backticks kann man das HTML sogar gut lesbar formatieren

const meineH3 = "generierte Überschrift";
const meinP = "Das geht ja <em>echt</em> super!";
const breite = 500; //naturalWidth des Bildes
const mySrc = `https://picsum.photos/${breite}`;
const myNames = ["Hugo", "Paula", "Sepp"];

function zufallsGanzZahl(min, max) {
  return Math.round(Math.random() * (max - min) + min);
}

// beachte, dass innerhalb der Backticks auch Variablen funktionieren
// die innerhalb "normaler" Anführungsstriche eingefügt sind!
const myHTML = `
  <h3 style="color:red">Ich bin eine ${meineH3}!</h3>
  <ul>
    <li>${myNames[0]}</li>
    <li>${myNames[1]}</li>
    <li>${myNames[2]}</li>
  </ul>
  <!-- Breite wird zufällig generiert -->
  <img style="width:${zufallsGanzZahl(30,60)}vw" src="${mySrc}" alt="cooles Fotos!">
  <p>${meinP}</p>
`;

// ab hier ist das generierte HTML erst im DOM eingebaut
// und kann angesprochen werden!
meinArticle.innerHTML = myHTML;

// das geht erst, wenn das Bild generiert ist!
const myImg = document.querySelector("#first img");
myImg.style.borderRadius = "50%";

Traversing und Removing Nodes

Demo

Javascript

const thias = document.querySelector('.thias');

// zählt alle HTML-Elemente auf, die sich bei "thias" befinden
console.log(thias.children);

// zählt alle Node-Elemtent auf - das sind auch die Textbausteine OHNE HTML-Tages!
// console.log(thias.childNodes);

// ============== Text aus dem firstElementChild ============

const myFirstElementChildText = thias.firstElementChild.textContent;
console.log(`Text des FirstElementChild: ${myFirstElementChildText}`);

// ============== Text aus dem lastElementChild ============

const myLastElementChildText = thias.lastElementChild.textContent;
console.log(`Text des LastElementChild: ${myLastElementChildText}`);

// ============ Text aus dem vorhergehenden Element =============

const prevEleSibling = thias.previousElementSibling;
const prevEleSiblingText = prevEleSibling.textContent;

console.log(`Text des vorherigen Elementes: ${prevEleSiblingText}`);

// ============ Text aus dem nachfolgendem Element ===============

// fragt erst ab, ob es das "nextEleSibling" überhaupt gibt
const nextEleSibling = thias.nextElementSibling;
let nextEleSiblingText;

// "null" heißt, dass es das Element nicht gibt
// "!= null" bedeutet, dass das Element existiert
if (nextEleSibling != null) {
  nextEleSiblingText = nextEleSibling.textContent;
} else {
  nextEleSiblingText = 'kein nextElementSibling gefunden!';
}

console.log(`Text des nachfolgendem Elementes: ${nextEleSiblingText}`);

// ============ HTML aus dem Parent Element ===============

const myParentElementHTML = thias.parentElement.outerHTML;
console.log(`outerHTML des ParentElementes:\n${myParentElementHTML}`);

// ============= "verschachtelte" Traverse =============

// nicht unbedingt sinnvoll, aber machbar!
const headerH1 = thias.parentElement.previousElementSibling.firstElementChild;
console.log(`headerH1 textContent: ${headerH1.textContent}`);

Element aus dem DOM entfernen

const myH2 = document.querySelector('h2');
myH2.remove();

querySelectorAll – ForEach

das forEach loopt durch alle Elemente, die via querySelectorAll ausgewählt wurden.

Demo

Javascript

// es werden hiermit nur die "ungeraden" Paragraphen angesprochen
const myPs = document.querySelectorAll("p:nth-of-type(2n-1)");

// "ereignis" ist wieder ein beliebiger Name, könnte auch "e" oder "xyz" sein!
myPs.forEach(function(ereignis) {
  // Ausgabe des Inhalts jedes der Elemente
  console.log(ereignis.textContent);

  ereignis.style.cssText = `
    border: 1px solid red;
    padding: 0.5em;
    background-color: #fff;
    border-radius: 5px;
  `;
});

querySelectorAll – ForEach – Click

Bei den via JS ausgewählten Paragraphen wird ein "Klick" eingebaut, der ein Alert auslöst und den jeweiligen Inhalt zeigt

Demo

Javascript

// es werden hiermit nur die "geraden" Paragraphen angesprochen
const myPs = document.querySelectorAll("p:nth-of-type(2n)");

// hier muss nicht die gleiche Variabe "ereignis" stehen!
// man kann auch was anderes verwenden…
function tuWas(ereignis) {
  // das entscheidende ist das "target"!
  const myContent = ereignis.target.innerHTML;
  alert(`Folgenden Inhalt gefunden: ${myContent}`);
}

// "ereignis" ist wieder ein beliebiger Name, könnte auch "e" oder "xyz" sein!
myPs.forEach(function(ereignis) {
  ereignis.style.cssText = `
    cursor: pointer;
    border: 1px solid red;
    padding: 0.5em;
    background-color: #fff;
    border-radius: 5px;
  `;
  ereignis.addEventListener("click", tuWas);
});

querySelectorAll - Button-Menü

Darstellen der inaktiven/aktiven Button durch einfachen Klassentausch…

Demo

Javascript

// alle buttons
const myButtons = document.querySelectorAll("nav button");

function klasseWeg(welche) {
  // nächste Zeile so kompliziert, weil "querySelector" nach ".aktiv" sucht!
  const klasseElement = document.querySelector(`.${welche}`);

  // abfragen, ob es so ein Element gibt!
  if (klasseElement != null) {
    klasseElement.classList.remove(welche);
  }
}

function buttonAction(ereignis) {
  // Entfernen der Klasse "aktiv" bei dem Element, das aktuell diese Klasse hat
  klasseWeg("aktiv");

  // das entscheidende ist das "currentTarget"!
  // console.log(ereignis.currentTarget.innerHTML);
  ereignis.currentTarget.classList.add("aktiv");
}

myButtons.forEach(function(ereignis) {
  ereignis.addEventListener("click", buttonAction);
});

CSS

:root {
  --radius: 20px;
}

button {
  border: none;
  padding: 0.5em;
  font-size: 90%;
  background: linear-gradient(hsl(50, 90%, 80%), hsl(50, 90%, 50%));
  cursor: pointer;
  text-shadow: 1px 1px rgba(255, 255, 255, 0.6);
}

button:nth-of-type(1) {
  border-radius: var(--radius) 0 0 var(--radius);
}

button:nth-last-of-type(1) {
  border-radius: 0 var(--radius) var(--radius) 0;
}

button.aktiv {
  background: linear-gradient(hsl(20, 90%, 80%), hsl(20, 90%, 50%));
}

querySelectorAll – ForEach – Click-Button generieren

Bei den via JS ausgewählten Ps wird ein "Klick-Button" eingebaut, der die Transparenz des jeweiligen Eltern-Elementes ändert

Demo

Javascript

// es werden hiermit nur die "geraden" Paragraphen angesprochen
const myPs = document.querySelectorAll("p:nth-of-type(2n)");

// "ereignis" ist wieder ein beliebiger Name, könnte auch "e" oder "xyz" sein!
myPs.forEach(function(ereignis) {
  ereignis.style.cssText = `
    cursor: pointer;
    border: 1px solid #ccc;
    padding: 0.5em;
    background-color: #fff;
    border-radius: 5px;
  `;

  // einen Button generieren und einfügen
  const myButton = document.createElement("button");
  myButton.textContent = "opak";
  myButton.style.cssText = `
    display: block;
    font-size: 80%;
    border: none;
    border-radius: 6px;
    padding: 0.3em 1em;
    margin: .5em 0;
    cursor: pointer;
    box-shadow: 0 0 6px 2px rgba(0,0,0,.15);
    background-color: hsl(30, 80%, 80%);
  `;

  ereignis.appendChild(myButton);
});

// in einem 2. Schritt die Click-Funktionen der Buttons erzeugen
const myButtons = document.querySelectorAll("button");

function durchsichtigMachen(eee) {
  const meinZiel = eee.target.closest("p");
  meinZiel.style.transition = "opacity 0.8s";
  if (meinZiel.style.opacity === "0.2") {
    meinZiel.style.opacity = "1";
  } else {
    meinZiel.style.opacity = "0.2";
  }
}

myButtons.forEach(function(aaa) {
  aaa.addEventListener("click", durchsichtigMachen);
});

querySelectorAll – Menü erzeugen

  • das Navi-Menü mit den internen Links wird automatisch erzeugt
  • die IDs bei den Artikeln werden ebenfalls automatisch via JS hinzugefügt und als "targets" im Nav-Menü verwendet
  • Nett wäre noch, wenn sich die Navi-Leite anpasst, wenn man rauf- oder runterscrollt…

Demo

CSS

html {
  scroll-behavior: smooth;
}

Javascript

const header = document.querySelector("header");

// erzeugen der Elemente, die dann die seiten-interne Navi darstellen
const meinNav = document.createElement("nav");
const meinMenu = document.createElement("ul");

// alle Article aus "main"
const meineArticle = document.querySelectorAll("main article");

// Umwandlung in ein Array
const meineArticleArray = Array.from(meineArticle);

// Zusammenbau der Navi-Ul
for (let i = 0; i < meineArticleArray.length; i++) {
  // das steht dann im Nav-Menü
  const meinText = `Top ${i + 1}`; // +1 weil Array mit 0 anfängt!

  // die ID des jeweiligen Articles wird hier automatisch erzeugt
  const meinID = `art_${i}`;

  // und im HTML eingefügt
  meineArticleArray[i].id = meinID;

  // gleichzeigt in dem erzeugten Navi-Menü bei den Links eingebaut
  const meinLi = `<li><a href="#${meinID}">${meinText}</a></li>`;
  meinMenu.innerHTML += meinLi;
}

// Einfügen der Nav-Sachen ins Nav
meinNav.appendChild(meinMenu);

// Einhängen im Header
header.insertAdjacentElement("beforeend", meinNav);

//=========== markieren des angeklickten Nav-Punktes ====

// entfernen dieser Klasse vorab,
// falls schon ein Link so markiert ist
function markierungEntfernen() {
  const angeklickt = document.querySelector("header .angeklickt");
  if (angeklickt !== null) {
    angeklickt.classList.remove("angeklickt");
  }
}

function linkMarkieren(ereignis) {
  markierungEntfernen();
  ereignis.target.classList.add("angeklickt");
}

function linkMarkierungEntfernen() {
  markierungEntfernen();
}

const interneLinks = document.querySelectorAll("header nav a");
interneLinks.forEach(function(ereignis) {
  ereignis.addEventListener("click", linkMarkieren);
});

// der Link "nach oben" sowie der Link bei "Smooth Scrolling"
const topScrollLinks = document.querySelectorAll("[href='#main']");
topScrollLinks.forEach(function(ereignis) {
  ereignis.addEventListener("click", linkMarkierungEntfernen);
});

HTML

<header>
    <h1><a href="#main">Smooth Scrolling</a></h1>
    <!-- Navi wird via JS erzeugt! -->
</header>

<main id="main">

    <!-- emmet: (article>h2{Überschrift $}+img[src="https://picsum.photos/400?random=$"]+p>lorem30)*8 -->

    <article>
        <h2>Überschrift 1</h2>
        <div class="grid">
            <img src="https://picsum.photos/400?random=1" alt="" />
            <p>Lorem ipsum…</p>
        </div>
    </article>

    <article>
        <h2>Überschrift 2</h2>
        <div class="grid">
        …

querySelectorAll – IntersectionObserver

Bis auf den IntersectionObserver weitgehend identisch mit dem vorherigen Beispiel. Nur werden die Nav-Elemente im Header nicht mehr rot, wenn man dort draufklickt, sondern wenn der dazugehörige Article sich im sichtbaren Bereich befindet. So werden die Nav-Elemente auch markiert, wenn man direkt durch das Dokument scrollt.

  • das Navi-Menü mit den internen Links wird automatisch erzeugt
  • die IDs bei den Artikeln werden ebenfalls automatisch via JS hinzugefügt und als "targets" im Nav-Menü verwendet
  • die Navi-Leite passt sich automatisch an, wenn man rauf- oder runterscrollt…*

Demo

Javascript

const header = document.querySelector("header");

// erzeugen der Elemente, die dann die seiten-interne Navi darstellen
const meinNav = document.createElement("nav");
const meinMenu = document.createElement("ul");

// alle Article aus "main"
const meineArticle = document.querySelectorAll("main article");

// Umwandlung in ein Array
const meineArticleArray = Array.from(meineArticle);

// Zusammenbau der Navi-Ul
for (let i = 0; i < meineArticleArray.length; i++) {
  // das steht dann im Nav-Menü
  const meinText = `T ${i + 1}`; // +1 weil Array mit 0 anfängt!

  // die ID des jeweiligen Articles wird hier automatisch erzeugt
  const meinID = `art_${i}`;

  // und im HTML eingefügt
  meineArticleArray[i].id = meinID;

  // der Inhalt der jeweiligen als Title im Link
  meinTitle = meineArticleArray[i].firstElementChild.textContent;

  // gleichzeigt in dem erzeugten Navi-Menü bei den Links eingebaut
  // der Klassenname (z.B. "art_0") ist identisch mit der ID(!) des verlinkten Artikels
  // damit lässt sich dieser beim Scrollen und Abfragen via "IntersectionObserver" einfach ein/ausschalten
  const meinLi = `<li><a href="#${meinID}" class="${meinID}" title="springe zu ${meinTitle}">${meinText}</a></li>`;
  meinMenu.innerHTML += meinLi;
}

// Einfügen der Nav-Sachen ins Nav
meinNav.appendChild(meinMenu);

// Einhängen im Header
header.insertAdjacentElement("beforeend", meinNav);

/*
=========== Markierung entfernen des Nav-Punktes,
dessen zugehöriger Artikel gerade im sichtbaren Bereich ist =======
*/

// entfernt dieser Klasse,
// falls schon ein Link so markiert ist
function markierungEntfernen() {
  const imFocus = document.querySelector("header .imFocus");
  if (imFocus !== null) {
    imFocus.classList.remove("imFocus");
  }
}

// ===== InterSectionObserver ======

// übernommen von https://24ways.org/2019/beautiful-scrolling-experiences-without-libraries/

// verwandelt die NodeList in einem Schritt in ein Array
const sections = [...document.querySelectorAll("main article")];
// console.log(sections[1]);

// wieviel vom jeweiligen Artikel muss sichtbar sein
// noch auf kleinem Screen testen und evtl. anpassen!
const grenze = 0.8;

let options = {
  rootMargin: "0px",
  threshold: grenze
};

const callback = (entries, observer) => {
  entries.forEach(entry => {
    const { target } = entry;
    // console.log(entry, target);

    if (entry.intersectionRatio >= grenze) {
      // target.classList.add("is-visible");
      // console.log(target.id);
      const myID = target.id;
      const myA = document.querySelector(`header a.${myID}`);
      myA.classList.add("imFocus");
    } else {
      // target.classList.remove("is-visible");
      markierungEntfernen();
    }
  });
};

const observer = new IntersectionObserver(callback, options);

sections.forEach((section, index) => {
  observer.observe(section);
});

The closest element, parentElement, previousElementSibling

Closest: sehr praktisch, um ein "Vorfahren-Element" zu finden – überspringt auch Ebenen…

Demo

Javascript

// hier wird nach etwas "oberhalb im DOM" gesucht, das die Klasse "karten" hat
// praktisch, wenn das nicht direkt das parentElement ist!
const buttonParentElement = deleteButton.closest('.karten');

Javascript

// Entfernt von Elementen, die die jeweiligen Klassen haben, diese Klasse
function elementKlasseEntfernen(klasse) {
  const aktivElement = document.querySelector(`.${klasse}`);
  // console.log(aktivKlasse);
  // Abfrage, ob es so ein Element aktuell gibt
  // am Anfang gibt es dies ja nicht
  if (aktivElement != null) {
    aktivElement.classList.remove(klasse);
  }
}

// alle Buttons ==================

// hier muss nicht die gleiche Variabe "ereignis" stehen!
// man kann auch was anderes verwenden…
function tuWas(ereignis) {
  // erst mal die Klasse "angeklickt" und "aktiv" entfernen
  // falls diese schon vorhanden sind
  // damit stellt man sicher, dass immer nur ein Artikel/Button aktiv ist
  elementKlasseEntfernen("angeklickt"); // Button-Klasse
  elementKlasseEntfernen("aktiv"); // Artikel-Klasse

  // das entscheidende ist das "target"!
  // durch "closest" wird nur der "Eltern-Artikel" angesprochen
  const myArticle = ereignis.target.closest("article");
  myArticle.style.transition = "all .3s ease-in-out";
  myArticle.classList.add("aktiv");

  const myP = ereignis.target.previousElementSibling;
  const myPText = myP.textContent;
  // console.log(myPText);

  //const myH2 = ereignis.target.parentElement.firstElementChild;
  const myH2 = myArticle.querySelector("h2");
  const myH2Text = myH2.textContent;
  // console.log(myH2Text);

  // der Button selbst bekommt Klasse "angeklickt" getoggelt
  ereignis.target.style.transition = "all .3s ease-in-out";
  ereignis.target.classList.add("angeklickt");
}

const myButtons = document.querySelectorAll("button");
// console.log(myButtons);

myButtons.forEach(function(ereignis) {
  // console.log(ereignis.textContent);

  // const myArticle = ereignis.closest("article");
  // myArticle.style.border = "1px solid red";
  // console.log(myArticle.innerHTML);

  //const myP = ereignis.previousElementSibling;
  //console.log(myP.textContent);

  ereignis.addEventListener("click", tuWas);
});

Create Cards incl. Delete-Button

Demo

Javascript

// das div.cards wird erzeugt und innerhalb des main eingefügt
const cardParent = document.createElement('div');
cardParent.classList.add('cards');

const myMain = document.querySelector('main');
myMain.insertAdjacentElement('afterbegin', cardParent);

// die jeweiligen Karten werden erzeugt
function generatePlayerCard(cardName, name, age, height) {
  const myHTML = `
  <div class="karten ${cardName}">
    <h2>${name} - ${age} Jahre alt</h2>
    <p>Diese Person ist ${height}cm groß und ${age} Jahre alt. In Hunde-Jahren wäre sie ${age * 7}!</p>
    <button type="button">&times; löschen</button>
  <div>
  `;
  return myHTML;
}

// das Löschen via Button "Delete Card"
function deleteButtonClick(cardName) {
  const deleteButton = cardParent.querySelector(`.${cardName} button`);

  // das jeweilge ParentElement (also die Karte) wird entfernt
  deleteButton.addEventListener('click', function() {
    // hier wird nach etwas "oberhalb im DOM" gesucht, das die Klasse "karten" hat
    // praktisch, wenn das nicht direkt das parentElement ist!
    const buttonParentElement = deleteButton.closest('.karten');
    buttonParentElement.style.opacity = 0.1;
    // buttonParentElement.remove();
  });
}

function players(cardName, name, age, height) {
  let myCardName = cardName;
  myCardName = document.createElement('div');
  // bei "outerHTML"muss diese Reihenfolge sein,
  // weil sonst das Element "playerCard" noch nicht im DOM exisiert
  // und somit auch nicht das "outerHTML" ausgetauscht werden kann!
  cardParent.insertAdjacentElement('beforeend', myCardName);
  myCardName.outerHTML = generatePlayerCard(cardName, name, age, height);

  // Aufruf, die Löschfunktion einzubauen
  deleteButtonClick(cardName);
}

// Aufruf zum Erzeugen der Karten
players('playerCard1', 'Estelle', 22, 179);
players('playerCard2', 'Leander', 20, 190);
players('playerCard3', 'Gina', 54, 180);
players('playerCard4', 'Thias', 55, 189);

window addEventListener

Bei Klicken auf ein beliebiges Element wird eine rote Outline sichtbar, im "Hover" und im "console.log" wird die Bezeichnung ausgegeben.

Demo

Javascript

// super praktisch, um rauszubekommen, wo man gerade draufgeklickt hat!
window.addEventListener("click", function(thias) {
  // der rote Rahmen um angeklickte Elemente wird
  // beim Anklicken des nächsten Elementes wieder entfernt
  const clicked = document.querySelector(".clicked");
  if (clicked != null) {
    clicked.style.outline = "none";
    clicked.classList.remove("clicked");
  }

  // das outerHTML des angeklickten Elementes
  const infoText = thias.target.outerHTML;

  // es soll nur der HTML-Code bis zum ersten ">" ausgegeben werden
  const infoTextArray = infoText.split(">");
  const infoTextFirstChunk = `${infoTextArray[0]}>`;
  // Ausgabe im Title, um was es sich gerade handelt
  thias.target.title = infoTextFirstChunk;
  console.log(`gerade angeklickt: ${infoTextFirstChunk}`);

  // um das angeklickte Element wird ein roter Rahmen gezogen
  thias.target.style.outline = "1px solid red";
  thias.target.classList.add("clicked");
});

Bildertausch querySelectorAll

Beim Klicken auf die Listen-Elemente werden beim Bild "Platzhalter" die Bilder ausgetauscht

Demo

Javascript

const listItems = document.querySelectorAll("nav a");

function aktivEntfernen() {
  const aktivListItem = document.querySelector("nav a.aktiv");
  if (aktivListItem != null) {
    aktivListItem.classList.remove("aktiv");
  }
}

function bilderTausch(ereignis) {
  // das bisher aktive Element wieder normal stellen
  aktivEntfernen();

  // angeklicktes Nav-Element markieren
  ereignis.target.classList.add("aktiv");

  // bei diesem Bild sollen die Src ausgetauscht werden
  const austauschBild = document.querySelector("#austauschbild img");
  const neuSrc = ereignis.target.href; // was steht in der jeweiligen Verlinkung?
  austauschBild.src = neuSrc;

  // figCaption anpassen
  const austauschBildCaption = document.querySelector(
    "#austauschbild figcaption"
  );
  const neuCaption = ereignis.target.title;
  austauschBildCaption.textContent = neuCaption;

  // damit der Browser nicht dorthin wechselt
  ereignis.preventDefault();
}

listItems.forEach(function(ereignis) {
  ereignis.addEventListener("click", bilderTausch);
});

setInterval-Animation

Selbstablaufende Animation, die mit Klicken auf "Stop Animation" angehalten werden kann

Demo

Javascript

// das zu animierende Element
const animiert = document.querySelector("#animiert");

// Breite und Höhe des Browsers
// ACHTUNG: Wird aktuell nicht abgefragt,
// wenn die Größe des Browserfensters geändert wird!
const browserBreite = window.innerWidth;
const browserHoehe = window.innerHeight;

// die Wachstumsschritte
let x = 2;
let y = 2;

function quadratAnimation() {
  const rect = animiert.getBoundingClientRect();

  // akt. X- und Y-Position des div#animiert
  const horizontalAktuell = rect.x;
  const vertikalAktuell = rect.y;

  // Breite & Hoehe des div#animiert
  const animiertBreite = rect.width;
  const animiertHoehe = rect.height;
  // console.log(animiertBreite, animiertBreite);

  // wenn das Div an die Grenzen des Browsers stößt, dreht die Animation um
  if (
    horizontalAktuell > browserBreite - animiertBreite ||
    horizontalAktuell < 0
  ) {
    x *= -1;
  }

  if (vertikalAktuell > browserHoehe - animiertHoehe || vertikalAktuell < 0) {
    y *= -1;
  }

  // Größe des div#animiert passt sich an
  const horizontalNeu = horizontalAktuell + x;
  const vertikalNeu = vertikalAktuell + y;

  animiert.style.left = `${horizontalNeu}px`;
  animiert.style.top = `${vertikalNeu}px`;
  animiert.style.width = `${horizontalNeu}px`;
  animiert.style.height = `${vertikalNeu}px`;
}

// nur wenn das so geschrieben ist, kann man mit "clearInterval"
// die Animation wieder anhalten
const myAni = setInterval(quadratAnimation, 50);

// Animation anhalten
function quadratAnimationStop() {
  window.clearInterval(myAni);
}
const stopAniKnopf = document.querySelector("#stopAni");
stopAniKnopf.addEventListener("click", quadratAnimationStop);

setInterval-Text-Copy

Werte, die links angegeben werden, werden rechts verwendet. Via „window.setInterval“ werden viele Funktionen definiert, die automatisch alle 200ms ausgeführt werden und die Textinhalte links abfragen und rechts anwenden.

Demo

Javascript

// anonyme Funktion mit vielen "Sub-Funktionen"
window.setInterval(function() {
  ueberschriftInhaltAendern();
  ueberschriftPostionAendern();
  ueberschriftGroesseAendern();
  …
}, 200);

Javascript

const ueberschrift = document.querySelector("#ueberschrift");

function ueberschriftInhaltAendern() {
  const inputTextInhalt = document.querySelector("#inputText").innerText;

  if (inputTextInhalt !== "Hier Text eingeben") {
    ueberschrift.innerText = inputTextInhalt;
  }
}

function ueberschriftPostionAendern() {
  const xPosition = parseInt(document.querySelector("#xPosition").innerText);
  const yPosition = parseInt(document.querySelector("#yPosition").innerText);

  ueberschrift.style.left = `${xPosition}px`;
  ueberschrift.style.top = `${yPosition}px`;
}

function ueberschriftFarbeAendern() {
  const schriftFarbe = document.querySelector("#schriftFarbe").innerText;
  ueberschrift.style.color = `${schriftFarbe}`;
}

function HGFarbeAendern() {
  const HGFarbe = document.querySelector("#HGFarbe").innerText;
  document.querySelector("#ausgabe").style.backgroundColor = `${HGFarbe}`;
}

function HGBildAendern() {
  const HGAktiv = document.querySelector(".HGBilder .aktiv");
  const ImageSource = HGAktiv.getAttribute("src");
  // Zerlegen der Source und finden des BildNamens
  const ImageSourceArray = ImageSource.split("/");
  // der Name des Img ohne Pfad
  const HGBild = ImageSourceArray[3];
  // das HGBild wird als HintergrundBild beim div#ausgabe festgelegt
  document.querySelector(
    "#ausgabe"
  ).style.backgroundImage = `url(../p/gross/${HGBild})`;
}

function ueberschriftGroesseAendern() {
  const schriftGroesse = parseInt(
    document.querySelector("#schriftGroesse").innerText
  );
  ueberschrift.style.fontSize = `${schriftGroesse}px`;
}

function ueberschriftDrehen() {
  const rotation = parseInt(document.querySelector("#rotation").innerText);
  ueberschrift.style.transform = `rotate(${rotation}deg)`;
}

function ueberschriftTransparenz() {
  const transparenz = parseFloat(
    document.querySelector("#transparenz").innerText
  );
  ueberschrift.style.opacity = transparenz;
}

// anonyme Funktion mit vielen "Sub-Funktionen"
window.setInterval(function() {
  ueberschriftInhaltAendern();
  ueberschriftPostionAendern();
  ueberschriftGroesseAendern();
  ueberschriftFarbeAendern();
  HGFarbeAendern();
  HGBildAendern();
  ueberschriftDrehen();
  ueberschriftTransparenz();
}, 200);

// ein zufällig ausgewähltes Bild wird gleich beim Start der Seite sichtbar
// ohne diese Zeile würde das Browserfenster erst mal leer bleiben
function getRandomNumberInRange(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

// zufälliges Bild beim Laden der Seite
document
  .querySelector(`.HGBilder img:nth-of-type(${getRandomNumberInRange(1, 4)})`)
  .classList.add("aktiv");

function klasseEntfernen(klasse) {
  const aktEle = document.querySelector(`.HGBilder .${klasse}`);
  aktEle.classList.remove(klasse);
}

function klickKlasseEin(ereignis) {
  klasseEntfernen("aktiv");
  ereignis.target.classList.add("aktiv");
}

// die Vorschau-Bilder links unten
const meineHGBilder = document.querySelectorAll(".HGBilder img");
meineHGBilder.forEach(function(ereignis) {
  ereignis.addEventListener("click", klickKlasseEin);
});

Slider-Interface

Via Slider die Schriftgröße verändern…

Demo

Bei "/Users/thias/docs/thiasJobs/hm/~web/scripte/content/9-wise19/1-webdesign/7-interaction-webfonts-webtypo/18-variable-fonts-eigene-experimente/variable-font-slider" gibt es ein ausführlicheres (nicht von mir) erstelltes Beispiel…

Javascript

const mySlider = document.querySelector(".slider");
const myButton = document.querySelector("button");
const myOutput = document.querySelector("#output");
const myOutputAnzeige = document.querySelector(".outputAnzeige");

// Anzeige zieht beim Start den "value"-Wert, der bei "input" eingestellt ist
myOutputAnzeige.innerHTML = `${mySlider.value}&thinsp;px`;

// verschiebt man den Slider, wird der Value verändert
// und entsprechend auch die Schriftgröße
function werteAnpassen() {
  // statt "this" könnte man hier auch "mySlider" schreiben
  myOutput.style.fontSize = `${this.value}px`;
  // "thinSpace" eingefügt - schaut besser aus…
  myOutputAnzeige.innerHTML = `${this.value}&thinsp;px`;
}

// alternative, ältere (?) Schreibweise
// mySlider.oninput = function() {};
mySlider.addEventListener("input", werteAnpassen);

// Funktion des Reset-Buttons
function wertZurueckSetzen() {
  const resetWert = 20;
  mySlider.value = `${resetWert}`;
  myOutputAnzeige.innerHTML = `${resetWert}&thinsp;px`;
  myOutput.style.fontSize = `${resetWert}px`;
}
myButton.addEventListener("click", wertZurueckSetzen);

// damit wird gleich beim Laden der Seite der bei "wertZurueckSetzen"
// eingestellte Wert (hier "20")für die Schriftgröße verwendet
wertZurueckSetzen();

PopUp-Image

Slideshow mit aufspringenden Groß-Bildern – noch hartes „Aufploppen“ und kein Weiterspringen zum nächsten Bild möglich!

Demo

Javascript

const myFigures = document.querySelectorAll("figure");

function abDeckerWeg() {
  document.querySelector(".abDecker").remove();
}

function grossBildZeigen(ereignis) {
  const mySrc = ereignis.target.src;

  // Pfad zu den Bildern im Ordner "gross" ändern
  const newSrc = mySrc.replace("klein", "gross");

  // steht bei "data-beschreibung" im HTML
  const myBeschreibung = ereignis.target.dataset.beschreibung;

  // Erzeugen des abdeckenden Divs, das sich über alles legt
  // CSS-Angaben bei style.css
  const abDecker = document.createElement("div");
  abDecker.classList.add("abDecker");

  const myFigure = document.createElement("figure");
  const myImg = document.createElement("img");
  myImg.src = `${newSrc}`; // Pfad mit "p/gross"
  const myFigcaption = document.createElement("figcaption");
  myFigcaption.textContent = myBeschreibung;
  // ein kleiner × zum Anzeigen des Schliess-Buttons
  const mySchliessX = document.createElement("p");
  mySchliessX.textContent = "×";
  mySchliessX.title = "Grossbild-Ansicht schließen";
  mySchliessX.classList.add("schliessButton");

  // die Sachen zuerst beim abDecker anhängen und dann erst am body!
  abDecker.appendChild(myFigure);
  myFigure.appendChild(myImg);
  myFigure.appendChild(myFigcaption);
  myFigure.appendChild(mySchliessX);
  document.body.appendChild(abDecker);
  abDecker.addEventListener("click", abDeckerWeg);
}

myFigures.forEach(function(ereignis) {
  ereignis.addEventListener("click", grossBildZeigen);
});

PopUp-Image mit Href

Slideshow mit aufspringenden Groß-Bildern – eigentlich mit Gracefull Degradation, weil die Links zu den Großbildern als echtes Links eingebaut sind…

Macht die Sache aber deutlich komplizierter, weil das "href" nicht ausgelesen werden kann -- unklar warum!

Demo

Javascript

const myFigures = document.querySelectorAll("main figure");

function abDeckerWeg() {
  document.querySelector(".abDecker").remove();
}

function grossBildZeigen(ereignis) {
  ereignis.preventDefault();

  // das Abfragen der href klappt leider nicht!
  // es kommt nur "undefined" raus!
  // deswegen habe ich bei den Datasets "data-gross" eingefügt
  // wo der Link zum Großbild eingebaut ist -- umständlich!
  const testHref = ereignis.target;
  console.log(testHref);

  const grossBildSource = ereignis.target.dataset.gross;
  console.log(grossBildSource);

  // steht bei "data-beschreibung" im HTML
  const myBeschreibung = ereignis.target.dataset.beschreibung;

  // Erzeugen des abdeckenden Divs, das sich über alles legt
  // CSS-Angaben bei style.css
  const abDecker = document.createElement("div");
  abDecker.classList.add("abDecker");

  const myFigure = document.createElement("figure");
  const myImg = document.createElement("img");
  myImg.src = `${grossBildSource}`; // Pfad mit "p/gross"
  const myFigcaption = document.createElement("figcaption");
  myFigcaption.textContent = myBeschreibung;
  // ein kleiner × zum Anzeigen des Schliess-Buttons
  const mySchliessX = document.createElement("p");
  mySchliessX.textContent = "×";
  mySchliessX.title = "Grossbild-Ansicht schließen";
  mySchliessX.classList.add("schliessButton");

  // die Sachen zuerst beim abDecker anhängen und dann erst am body!
  abDecker.appendChild(myFigure);
  myFigure.appendChild(myImg);
  myFigure.appendChild(myFigcaption);
  myFigure.appendChild(mySchliessX);
  document.body.appendChild(abDecker);
  abDecker.addEventListener("click", abDeckerWeg);
}

myFigures.forEach(function(ereignis) {
  ereignis.style.border = "2px dotted red";
  const ereignisImg = ereignis.querySelector("img");
  const ereignisFigcaption = ereignis.querySelector("figcaption");
  console.log(ereignisImg.dataset.gross);
  ereignisImg.addEventListener("click", grossBildZeigen);
  // unklar, warum das nicht ohne geht -- vielleicht was mit Vererbung?
  ereignisFigcaption.addEventListener("click", function(e) {
    e.preventDefault();
    alert("Bitte direkt auf das Foto klicken!");
  });
});

prompt - preventDefault

Lässt erst das Weitergehen auf eine andere Seite zu, wenn der Benutzer das bestätigt hat.

Demo

Javascript

const sz = document.querySelector(".sz");

sz.addEventListener("click", function(ereignis) {
  // aufspringender Dialog, der entweder "true" oder "false" zurückgibt
  const sollSiteSichAendern = confirm("Wirklich weitergehen?");
  // console.log(sollSiteSichAendern);

  // wenn auf "Cancel" geklick wurde, wird das Weitergehen geblockt!
  // lange Schreibweise:
  // if (sollSiteSichAendern == false)
  if (!sollSiteSichAendern) {
    ereignis.preventDefault();
  }
});

Events: focus, blur, keydown, keyup

Beim Eingeben werden verschiedene Events ausgelöst

Demo

Javascript

const testInput = document.querySelector('[name="testInput"]');

const focusOutput = document.querySelector("#ausgabe1");
const blurOutput = document.querySelector("#ausgabe2");
const keydownOutput = document.querySelector("#ausgabe3");
const keyupOutput = document.querySelector("#ausgabe4");

// wenn man innerhalb des input#ausgabe klickt
testInput.addEventListener("focus", function(ereignis) {
  focusOutput.textContent = testInput.value;
});

// wenn man das input#ausgabe verlässt (es den Focus verliert)
testInput.addEventListener("blur", function(ereignis) {
  blurOutput.textContent = testInput.value;
});

// wenn man innerhalb input#ausgabe auf der Tastatur ein Taste RUNTER drückt
testInput.addEventListener("keydown", function(ereignis) {
  keydownOutput.textContent = testInput.value;
});

// wenn man innerhalb input#ausgabe auf der Tastatur eine Taste loslässt
testInput.addEventListener("keyup", function(ereignis) {
  keyupOutput.textContent = testInput.value;
});

Accessibility via Tabbing

Die verschiedenen Elemente im Browser können sowohl angeklickt als auch via "Tabbing" erreicht und ausgelöst werden

Demo

Javascript

const myLink = document.querySelector("a.demo");
const myButton = document.querySelector("button[name='mybutton']");
const myDiv = document.querySelector("div");
const mySpan = document.querySelector("span");

const ausgabe = document.querySelector("#ausgabe");

function handleEvent(ereignis) {
  ereignis.preventDefault();
  // console.log(event.target.dataset.aussage);
  if (ereignis.type === "click" || ereignis.key === "Enter") {
    ausgabe.textContent = event.target.dataset.aussage;
  }
}

myLink.addEventListener("click", handleEvent);
myButton.addEventListener("click", handleEvent);
myDiv.addEventListener("click", handleEvent);
mySpan.addEventListener("click", handleEvent);

myLink.addEventListener("keyup", handleEvent);
myButton.addEventListener("keyup", handleEvent);
myDiv.addEventListener("keyup", handleEvent);
mySpan.addEventListener("keyup", handleEvent);

Etch-A-Sketch (Canvas)

Beginner Javascript (Wes Bos) | Etch a Sketch | Film 33

  • diverse Keydown-Events
  • auch hier preventDefault() wo notwendig

Demo

Javascript

const canvas = document.querySelector("#etch-a-sketch");
const ctx = canvas.getContext("2d"); // 2D Darstellung im canvas

const shakebutton = document.querySelector(".shake");

const MOVE_AMOUNT = 80;

// Grundeinstellungen
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.lineWidth = MOVE_AMOUNT;

// Farbe des Striches festlegen
let hue = 0;
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;

// Kursform, wenn man als Variablen die gleichen Bezeichnungen
// verwendet, die das Element auch hat
// normale Schreibweise: const width = canvas.width;
const { width, height } = canvas;

// zufällige Anfangsposition
let x = Math.floor(Math.random() * width);
let y = Math.floor(Math.random() * height);

// Anfangspunkt zeichnen
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y);
ctx.stroke();

// zeichen-funktion, options ausführlich
/*
function draw(options) {
  console.log(options.key);
}
*/
function draw({ key }) {
  // console.log(key);

  // Strichfarbe anpassen
  hue += 1;
  ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;

  // das eigentliche zeichnen
  ctx.beginPath();
  ctx.moveTo(x, y);

  switch (key) {
    case "ArrowUp":
      y = y - MOVE_AMOUNT;
      break;
    case "ArrowDown":
      y = y + MOVE_AMOUNT;
      break;
    case "ArrowRight":
      x = x + MOVE_AMOUNT;
      break;
    case "ArrowLeft":
      x = x - MOVE_AMOUNT;
      break;
    default:
      break;
  }

  ctx.lineTo(x, y);
  ctx.stroke();
}

// function beim Drücken der Arrow-keys
function handleKey(ereignis) {
  // damit kann man dann aber nicht mehr scrollen oder Seite neu laden!
  //ereignis.preventDefault();

  // nur wenn einer der "Arrow"-Keys gedrückt wurde
  if (ereignis.key.includes("Arrow")) {
    ereignis.preventDefault(); // kein Scrollen!
    // ein Objekt, das auch mehr Optionen haben könnte -- später!
    draw({ key: ereignis.key });
    // console.log(ereignis.key);
  }

  // löschen mit der "Backspace"-Taste (rechts oben <-)
  if (ereignis.key === "Backspace") {
    ereignis.preventDefault();
    clearCanvas();
  }

  // gibt die Bezeichnung (z.b. "ArrowUp" oder "Enter") aus
  // console.log(ereignis.key);
}

function clearCanvas() {
  // "shake" enthält eine CSS-Animation -- siehe CSS!
  canvas.classList.add("shake");

  // das ist super, damit kann man abwarten,
  // bis die Animation zu Ende ist und dann die Klasse entfernen
  // sonst geht das beim nächsten Mal nicht mehr, weil die Klasse "shake"
  // ja schon gesetzt ist!
  // es gibt auch "transitionend" - ausprobieren!
  canvas.addEventListener(
    "animationend",
    function() {
      console.log("Animation beended!");
      canvas.classList.remove("shake");
      // die Zeichnungen im Canvas ausradieren
      // beachte, dass die erst ausgelöscht wird, wenn die "shake"-animation
      // beendet ist!
      ctx.clearRect(0, 0, width, height);
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

shakebutton.addEventListener("click", clearCanvas);

// bei window, damit der key-Events überall bemerkt wird
window.addEventListener("keydown", handleKey);

CSS-Animationen via JS starten

Die 2. Animation startet, sobald die 1. fertig ist. Das wird via JS ('addEventListener("animationend")') abgefragt.

Demo

Javascript

const button = document.querySelector("button");

const animiert1 = document.querySelector("#animiert1");
const animiert2 = document.querySelector("#animiert2");

function aniStarten() {
  animiert1.classList.add("aktiv");

  animiert1.addEventListener(
    "animationend",
    function() {
      console.log("Animation 1 beended!");
      animiert1.classList.remove("aktiv");
      // 2. Animation fängt an, wenn die 1. fertig ist!
      animiert2.classList.add("aktiv");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );

  animiert2.addEventListener(
    "animationend",
    function() {
      console.log("Animation 2 beended!");
      animiert2.classList.remove("aktiv");
    },
    { once: true }
  );
}

button.addEventListener("click", aniStarten);

CSS-Transitions hintereinander via JS starten

Beim Ein- und Ausblenden des Bildes werden 2 Transitions hintereinander ausgeführt. Das Zeigen & Verbergen des Großbildes ist auch über Tabbing und Enter möglich!

Demo

Javascript

const button = document.querySelector("button");

const abdecker = document.querySelector("#abdecker");
const grossBild = document.querySelector("#grossBild");
const schliesser = document.querySelector("#schliesser");

function grossBildZeigen() {
  abdecker.classList.add("sichtbar");

  abdecker.addEventListener(
    "transitionend",
    function() {
      console.log("Abdecker-Transition beended!");
      grossBild.classList.add("sichtbar");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

function grossBildWeg() {
  grossBild.classList.remove("sichtbar");

  grossBild.addEventListener(
    "transitionend",
    function() {
      console.log("grossBild-Transition beended!");
      abdecker.classList.remove("sichtbar");
    },
    { once: true }
  );
}

button.addEventListener("click", grossBildZeigen);

// "×" zum Schliessen des grossBild
schliesser.addEventListener("click", grossBildWeg);
abdecker.addEventListener("click", grossBildWeg);

// mit irgendeiner Taste das
schliesser.addEventListener("keyup", grossBildWeg);

CSS-Transitions – Popup-Elemente via JS generiert

Wie Beispiel vorher, nur die Elemente werden via JS generiert, die für die Großbild-Darstellung benötigt werden. Eigenes CSS-Datei (popup.css) für die Popup-Sachen…

Demo

Javascript

const button = document.querySelector("button");

// ============= Elemente generieren =====================

// die für die Großbild-Darstellung benötigt werden
// CSS-Anweisungen befinden sich bei "c/popup.css"

const abdecker = document.createElement("div");
abdecker.id = "abdecker";

const grossBild = document.createElement("figure");
grossBild.id = "grossBild";

const grossBildImg = document.createElement("img");
grossBildImg.src = "https://picsum.photos/600";
grossBildImg.alt = "Cooles Bild";

const grossBildFigcaption = document.createElement("figcaption");
grossBildFigcaption.textContent = "Coole Bildunterschrift";

const schliesser = document.createElement("span");
schliesser.id = "schliesser";
schliesser.textContent = "×";
schliesser.role = "button";
schliesser.tabIndex = "0"; // Schreibweise in JS beachten!
schliesser.title = "Großbild schliessen";

// zusammenbauen und einfügen
grossBild.appendChild(grossBildImg);
grossBild.appendChild(grossBildFigcaption);
grossBild.appendChild(schliesser);
abdecker.appendChild(grossBild);
document.body.appendChild(abdecker);

// ============== Ende der Generierung ===============

function grossBildZeigen() {
  abdecker.classList.add("sichtbar");

  abdecker.addEventListener(
    "transitionend",
    function() {
      console.log("Abdecker-Transition beended!");
      grossBild.classList.add("sichtbar");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

function grossBildWeg() {
  grossBild.classList.remove("sichtbar");

  grossBild.addEventListener(
    "transitionend",
    function() {
      console.log("grossBild-Transition beended!");
      abdecker.classList.remove("sichtbar");
    },
    { once: true }
  );
}

button.addEventListener("click", grossBildZeigen);

// "×" zum Schliessen des grossBild
schliesser.addEventListener("click", grossBildWeg);
abdecker.addEventListener("click", grossBildWeg);

// mit irgendeiner Taste das
schliesser.addEventListener("keyup", grossBildWeg);

PopUp-Images mit stufenweisen Transitions

Slideshow mit aufspringenden Groß-Bildern – incl. stufenweiser Transitions und „Loading-Animation“! Noch kein Weiterspringen zum nächsten Bild möglich!

Leider sind nur die Bilder direkt anklickbar – nicht die figCaptions. Ist mir noch nicht klar, warum das nicht geht!

Demo

Javascript

const myLinks = document.querySelectorAll("main figure");

// ============= Elemente vorab generieren =====================

/* CSS-Angaben für diese Elemente bei "popup.css" */

const abdecker = document.createElement("div");
abdecker.id = "abdecker";

// die animierte "Sanduhr", die anzeigt, dass ein großes Bild geladen wird
const abdeckerSanduhr = document.createElement("img");
abdeckerSanduhr.id = "abdeckerSanduhr";
abdeckerSanduhr.src = "p/waiting_white.svg";

const grossBild = document.createElement("figure");
grossBild.id = "grossBild";
const grossBildImg = document.createElement("img");
const grossBildFigcaption = document.createElement("figcaption");

const schliesser = document.createElement("span");
schliesser.id = "schliesser";
schliesser.textContent = "×";
schliesser.role = "button";
schliesser.tabIndex = "0"; // Schreibweise in JS beachten!
schliesser.title = "Großbild schliessen";

// zusammenbauen und einfügen
grossBild.appendChild(grossBildImg);
grossBild.appendChild(grossBildFigcaption);
grossBild.appendChild(schliesser);
abdecker.appendChild(abdeckerSanduhr);
abdecker.appendChild(grossBild);
document.body.appendChild(abdecker);

// ============== Ende der Generierung ===============

function grossBildZeigen(ereignis) {
  ereignis.preventDefault();

  const mySrc = ereignis.target.src;
  // Pfad zu den Bildern im Ordner "gross" ändern
  const newSrc = mySrc.replace("klein", "gross");
  // console.log("newSrc: " + newSrc);
  grossBildImg.src = newSrc;

  const myBeschreibung = ereignis.target.dataset.beschreibung;
  grossBildImg.alt = myBeschreibung;
  grossBildFigcaption.textContent = myBeschreibung;

  abdeckerSanduhr.classList.add("sichtbar");
  abdecker.classList.add("sichtbar");

  abdecker.addEventListener(
    "transitionend",
    function() {
      // console.log("Abdecker-Transition beended!");
      grossBild.classList.add("sichtbar");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

function grossBildWeg() {
  abdeckerSanduhr.classList.remove("sichtbar");
  grossBild.classList.remove("sichtbar");

  grossBild.addEventListener(
    "transitionend",
    function() {
      // console.log("grossBild-Transition beended!");
      abdecker.classList.remove("sichtbar");

      // "leeres" Bild - damit nicht beim nächsten "grossBildZeigen"
      // noch teilweise das alte Bild gezeigt wird und dann während
      // der Transition ausgetauscht wird - das stört sehr!
      // besonders merkbar, wenn die Bilder vom Server geladen werden 
      // und eine langsame Internet-Verbindung besteht
      grossBildImg.src = "";
    },
    { once: true }
  );
}

myLinks.forEach(function(ereignis) {
  ereignis.addEventListener("click", grossBildZeigen);
});

// "×" zum Schliessen des grossBild
schliesser.addEventListener("click", grossBildWeg);
abdecker.addEventListener("click", grossBildWeg);

// mit irgendeiner Taste das
schliesser.addEventListener("keyup", grossBildWeg);

PopUp-Images mit PHP

Vom Javascript und CSS identisch mit dem vorherigem Beispiel. Hier nur eine Automatisierung eingebaut, dass die Bilder automatisch via PHP eingelesen werden – egal wie viele…

Demo

HTML mit PHP

<body>
    <header>
        <h1>Popup-Images | PHP Integration</h1>
        <h2>Es werden automatisch alle Bilder des jeweiligen Ordners eingelesen</h2>
    </header>

    <?php
        $ordner = './p/klein';
        include './php/bildersammlung.php';  // externes php-script zum autom. einfuegen von bilder aus $ordner
    ?>

    <script src="js/script.js?v=1.0"></script>
</body>

Javascript

identisch mit dem vorherigem Beispiel!

PHP

<?php
// $ordner wird bei "index.php" festgelegt
$verzeichnis = dir($ordner);

// nächste Zeile sorgt dafür, dass das Array entleert wird und man es somit mehrfach verwenden kann!
$files = array();

while($eintrag=$verzeichnis->read())
$files[] = $eintrag; // ein array!
$verzeichnis->close();

sort($files); // alphabetisch sortieren

$pfad = $ordner . '/';
// echo($pfad);

$clength = count($files)-2;
echo "<p class='hinweis'>Das Verzeichnis enthält <strong style='color:red;'>$clength</strong> Bilder.</p>\n\n";

echo "      <main>\n";

foreach ($files as $eintrag){
    // http://www.php.net/manual/de/language.types.string.php
    // naechste zwei Zeilen fragen ab, ob Eintrag mit einem "." anfängt!
    $first = $eintrag[0];
    if ($first != "."){
        $dateinamenTeile = explode(".", $eintrag);
        $anfang = $dateinamenTeile[0];

        $bild = $pfad . $eintrag;
        $grossbild = './p/gross/' . $eintrag;

        // http://php.net/manual/de/function.exif-read-data.php
        $exif = exif_read_data($bild, 0, true);
        // Herauslesen eines einzelnen Wertes aus dem Array
        $aufnahmeDatum = $exif['EXIF']['DateTimeOriginal'];

        echo "
            <figure>
                <a href='$grossbild' title='Bild in Original-Größe zeigen'>
                    <img src='" . $bild . "' data-beschreibung='aufgenommen: " . $aufnahmeDatum . "' alt='" . $eintrag . "'>
                </a>
                <figcaption>$aufnahmeDatum</figcaption>
            </figure>
        \n";
    }
}

echo "      </main>\n\n";
?>

PopUp-Images mit PHP und Slideshow

incl. selbst ablaufender Slideshow und dazugehörigen Buttons

Achtung:

HTML/CSS/PHP/JS-Code nicht identisch mit dem vorherigem Beispiel!

eingebaute KeyEvents

  • ArrowDown, ArrowRight: Grossbild eins weiter (nur wenn GrossBild sichtbar)
  • ArrowUp, ArrowLeft: Grossbild eins zurück (nur wenn GrossBild sichtbar)
  • Escape, Backspace: Grossbild-Ansicht und Slideshow beenden
  • SPACE (Leertaste): automatische Slideshow starten/beenden (nur wenn GrossBild sichtbar)

Demo

HTML mit PHP

<body>
    <header>
        <h1>Popup-Images | PHP Integration</h1>
        <h2>Es werden automatisch alle Bilder des jeweiligen Ordners eingelesen</h2>
    </header>

    <?php
        $ordner = './p/klein';
        include './assets/php/bildersammlung.php';  // externes php-script zum autom. einfuegen von bilder aus $ordner
    ?>

    <script src="js/script.js?v=1.0"></script>
</body>

Javascript

// ============== automatische Slideshow ==============

// wie lange bleibt ein Slide stehen -- Sekunden
const standDauer = 5;

// Variable legt fest, ob Slideshow automatisch abläuft oder nicht
// hier erst mal pausiert, bis User auf "Space"-Taste oder "Play"-Symbol drückt
let slideShowLaufen = false;

// globale Variable, die speichert, auf welches kleines Bild
// man gerade geklickt hat
// wird für das Weiter- und Zurückgehen bei den Grossbildern benötigt
let kleinBildID;

// ===================== eingebaute KeyEvents =====================

/*

  ArrowDown, ArrowRight:    Grossbild eins weiter (nur wenn GrossBild sichtbar)
  ArrowUp, ArrowLeft:       Grossbild eins zurück (nur wenn GrossBild sichtbar)
  Escape, Backspace:        Grossbild-Ansicht und Slideshow beenden
  SPACE (Leertaste):        automatische Slideshow starten/beenden (nur wenn GrossBild sichtbar)

*/

// =============== erst Bilder zeigen, wenn alle geladen sind ===============

const loading = document.querySelector("#loading");
const galerie = document.querySelector(".galerie");

window.addEventListener("load", function() {
  loading.style.opacity = "0";
  galerie.classList.add("sichtbar");
});

// ============= Elemente für GrossBild-Sachen vorab generieren =====================

const abdecker = document.createElement("div");
abdecker.id = "abdecker";

// die animierte "Sanduhr", die anzeigt, dass ein großes Bild geladen wird
const abdeckerSanduhr = document.createElement("img");
abdeckerSanduhr.id = "abdeckerSanduhr";
abdeckerSanduhr.src = "./assets/p/waiting_white.svg";

const grossBild = document.createElement("figure");
grossBild.id = "grossBild";
const grossBildImg = document.createElement("img");
const grossBildFigcaption = document.createElement("figcaption");

const schliesser = document.createElement("span");
schliesser.id = "schliesser";
schliesser.textContent = "×";
schliesser.role = "button";
schliesser.tabIndex = "0"; // Schreibweise in JS beachten!
schliesser.title = "Großbild schliessen";

const weiterSymbol = document.createElement("img");
weiterSymbol.src = "./assets/p/forward.svg";
weiterSymbol.id = "weiterSymbol";
weiterSymbol.role = "button";
weiterSymbol.tabIndex = "0";
weiterSymbol.title = "nächstes Bild zeigen";

const zurueckSymbol = document.createElement("img");
zurueckSymbol.src = "./assets/p/back.svg";
zurueckSymbol.id = "zurueckSymbol";
zurueckSymbol.role = "button";
zurueckSymbol.tabIndex = "0";
zurueckSymbol.title = "vorheriges Bild zeigen";

const playSymbol = document.createElement("img");
playSymbol.src = "./assets/p/play.svg";
playSymbol.id = "playSymbol";
playSymbol.role = "button";
playSymbol.tabIndex = "0";
playSymbol.title = "automatische Slideshow starten/anhalten";

// zusammenbauen und einfügen
grossBild.appendChild(grossBildImg);
grossBild.appendChild(grossBildFigcaption);
grossBild.appendChild(schliesser);
grossBild.appendChild(weiterSymbol);
grossBild.appendChild(zurueckSymbol);
grossBild.appendChild(playSymbol);
abdecker.appendChild(abdeckerSanduhr);
abdecker.appendChild(grossBild);
document.body.appendChild(abdecker);

// ============== Ende der Generierung ===============

function grossBildZeigen(ereignis) {
  ereignis.preventDefault();

  const myTitle = ereignis.currentTarget.title;
  const myLink = ereignis.currentTarget.href;

  // zum Zeigen des nächsten bzw. vorherigen Grossbildes
  kleinBildID = ereignis.currentTarget.id;
  // console.log(`ID des gerade angeklickten Links: ${kleinBildID}`);

  grossBildImg.src = myLink;
  grossBildImg.alt = myTitle;
  grossBildFigcaption.textContent = myTitle;

  abdeckerSanduhr.classList.add("sichtbar");
  abdecker.classList.add("sichtbar");

  abdecker.addEventListener(
    "transitionend",
    function() {
      // console.log("Abdecker-Transition beended!");
      grossBild.classList.add("sichtbar");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

function grossBildWeg() {
  abdeckerSanduhr.classList.remove("sichtbar");
  grossBild.classList.remove("sichtbar");

  // Slideshow soll nicht weiterlaufen
  slideShowLaufen = false;
  playSymbol.src = "./assets/p/play.svg";

  grossBild.addEventListener(
    "transitionend",
    function() {
      // console.log("grossBild-Transition beended!");
      abdecker.classList.remove("sichtbar");

      // "leeres" Bild - damit nicht beim nächsten "grossBildZeigen"
      // noch teilweise das alte Bild gezeigt wird und dann während
      // der Transition ausgetauscht wird - das stört sehr!
      // besonders merkbar, wenn die Bilder vom Server geladen werden
      // und eine langsame Internet-Verbindung besteht
      grossBildImg.src = "";
    },
    { once: true }
  );
}

// die Links, in denen sich die Bilder der Galerie befinden
const myImageLinks = document.querySelectorAll(".galerie a");
myImageLinks.forEach(function(ereignis) {
  ereignis.addEventListener("click", grossBildZeigen);
});

// "×" zum Schliessen des grossBild
schliesser.addEventListener("click", grossBildWeg);
abdecker.addEventListener("click", grossBildWeg);

// ================ Weiterklicken bei sichtbaren Großbild ================

function grossBildTausch(id) {
  // console.log(ele);

  grossBild.classList.remove("sichtbar");

  grossBild.addEventListener(
    "transitionend",
    function() {
      // console.log("Abdecker-Transition beended!");
      grossBildImg.src = myImageLinksArray[id].href;
      grossBildFigcaption.textContent = myImageLinksArray[id].title;
      grossBild.classList.add("sichtbar");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

function naechstesBildZeigen(ereignis) {
  // sonst würde das Klicken zum "abdecker" durchgereicht,
  // der das Grossbild ausblendet…
  ereignis.stopPropagation();

  // jeweils einen Eintrag im Array weitergehen
  if (kleinBildID < myImageLinksArray.length - 1) {
    kleinBildID = parseInt(kleinBildID) + 1;
  } else {
    kleinBildID = 0;
  }

  grossBildTausch(kleinBildID);
}

function vorherigesBildZeigen(ereignis) {
  // sonst würde das Klicken zum "abdecker" durchgereicht,
  // der das Grossbild ausblendet…
  ereignis.stopPropagation();

  // jeweils einen Eintrag im Array weitergehen
  if (kleinBildID > 0) {
    kleinBildID = parseInt(kleinBildID) - 1;
  } else {
    kleinBildID = myImageLinksArray.length - 1;
  }

  grossBildTausch(kleinBildID);
}

// in ein Array umwandeln
const myImageLinksArray = Array.from(myImageLinks);

for (let i = 0; i < myImageLinksArray.length; i++) {
  // die ID des jeweiligen Links wird hier automatisch erzeugt
  // ACHTUNG: das ist nur eine Ziffer (0, 1, 2 …)! Eigentlich bei IDs nicht erlaubt!
  // es gab aber noch keine Probleme…
  const meinID = `${i}`;

  // und im HTML eingefügt
  myImageLinksArray[i].id = meinID;
}

// =============== selbstablaufende Slideshow ================

// lässt die Bilder hintereinander ablaufen
function weiterGehen() {
  // jeweils einen Eintrag im Array weitergehen
  if (kleinBildID < myImageLinksArray.length - 1) {
    kleinBildID = parseInt(kleinBildID) + 1;
  } else {
    kleinBildID = 0;
  }

  grossBildTausch(kleinBildID);
}

// startet/stoppt die Slideshow,
// ausgelöst durch "Space"-Taste oder playSymbol zum Anklicken
function slideShowAnAus() {
  slideShowLaufen = !slideShowLaufen;
  if (slideShowLaufen === true) {
    playSymbol.src = "./assets/p/pause.svg";
  } else {
    playSymbol.src = "./assets/p/play.svg";
  }
}

// wenn die Variable "slideShowLaufen" "true" ist, läuft die Slideshow
const cycle = setInterval(function() {
  if (slideShowLaufen == true) {
    weiterGehen();
  }
}, standDauer * 1000);

// ===================== KeyEvents =====================

function handleKey(ereignis) {
  // KeyEvents nur, wenn "abdecker" (schwarze Fläche) sichtbar ist
  if (abdecker.classList.contains("sichtbar")) {
    // console.log(ereignis.key);

    // weiterblättern
    ereignis.preventDefault();
    if (ereignis.key == "ArrowRight" || ereignis.key == "ArrowDown") {
      naechstesBildZeigen(ereignis);
    }

    // zurückblättern
    if (ereignis.key == "ArrowLeft" || ereignis.key == "ArrowUp") {
      vorherigesBildZeigen(ereignis);
    }

    // Bildershow beenden
    if (ereignis.key == "Escape" || ereignis.key == "Backspace") {
      grossBildWeg();
      // Slideshow soll dann natürlich nicht mehr laufen
      slideShowLaufen = false;
      playSymbol.src = "./assets/p/play.svg";
    }

    // automatische Slideshow starten/anhalten bei Drücken der "Space"-Taste
    if (ereignis.key == " ") {
      slideShowAnAus();
    }
  }
}

window.addEventListener("keydown", handleKey);

const weiterButton = document.querySelector("#weiterSymbol");
weiterButton.addEventListener("click", naechstesBildZeigen);

const zurueckButton = document.querySelector("#zurueckSymbol");
zurueckButton.addEventListener("click", vorherigesBildZeigen);

const playButton = document.querySelector("#playSymbol");
playButton.addEventListener("click", function(ereignis) {
  ereignis.stopPropagation();
  slideShowAnAus();
});

PHP

<?php
// $ordner wird bei "index.php" festgelegt
$verzeichnis = dir($ordner);

// nächste Zeile sorgt dafür, dass das Array entleert wird und man es somit mehrfach verwenden kann!
$files = array();

while($eintrag=$verzeichnis->read())
$files[] = $eintrag; // ein array!
$verzeichnis->close();

sort($files); // alphabetisch sortieren

$pfad = $ordner . '/';
// echo($pfad);

$clength = count($files)-2;
// echo "<p class='hinweis'>Das Verzeichnis enthält <strong style='color:red;'>$clength</strong> Bilder.</p>\n\n";

echo "<article class='galerie' id='top'>\n";

foreach ($files as $eintrag){
    // http://www.php.net/manual/de/language.types.string.php
    // naechste zwei Zeilen fragen ab, ob Eintrag mit einem "." anfängt!
    $first = $eintrag[0];
    if ($first != "."){
        $dateinamenTeile = explode(".", $eintrag);
        $anfang = $dateinamenTeile[0];

        $bild = $pfad . $eintrag;
        $grossbild = './p/gross/' . $eintrag;

        // http://php.net/manual/de/function.exif-read-data.php
        $exif = exif_read_data($bild, 0, true);
        // Herauslesen eines einzelnen Wertes aus dem Array
        $aufnahmeDatum = $exif['EXIF']['DateTimeOriginal'];

        echo "
            <a href='$grossbild' title='$aufnahmeDatum'>
                <img src='" . $bild . "' data-beschreibung='aufgenommen: " . $aufnahmeDatum . "' alt='" . $eintrag . "'>
            </a>
        \n";
    }
}

echo "  </article><!-- /galerie -->\n\n";
?>

Image Load Event

Erst wenn das Bild komplett geladen ist, wird es (animiert) sichtbar geschaltet

Demo

Javascript

const myImg = document.querySelector(".myImg");

function zufallsGanzZahl(min, max) {
  return Math.round(Math.random() * (max - min) + min);
}

myImg.src = `https://picsum.photos/${zufallsGanzZahl(1000, 1600)}`;

// erst wenn das Bild aus dem Web komplett geladen ist,
// wird die Klasse "geladen" hinzugefügt
// und das Bild somit sichtbar geschaltet
// beim Inspector/Network auf "Throttling" gehen und ein langsames Netz
// simulieren – dann kann man den Ladeprozess gut sehen!

myImg.addEventListener("load", function() {
  myImg.classList.add("geladen");
  console.log(myImg.naturalWidth, myImg.naturalHeight);
});

document.body.addEventListener("click", function() {
  myImg.classList.remove("geladen");
  myImg.src = `https://picsum.photos/${zufallsGanzZahl(400, 1200)}`;
});

document.body.addEventListener("keydown", function() {
  myImg.classList.remove("geladen");
  myImg.src = `https://picsum.photos/${zufallsGanzZahl(400, 1200)}`;
});

CSS

.myImg {
  margin-top: 1em;
  transform: translateY(-200vh);
  transition: all 0.2s;
  border-radius: 30px;
  box-shadow: 0 0 8px 3px rgba(0, 0, 0, 0.2);
}

.myImg.geladen {
  transform: translateY(0);
  transition: all 1s;
}

Windows Load Event

Erst wenn alle Bilder geladen sind, wird der Bereich <main> sichtbar

Demo

Javascript

const main = document.querySelector("main");
const loading = document.querySelector("#loading");

// erst wenn alle Bilder aus dem Web komplett geladen ist,
// wird bei "main" die Klasse "sichtbar" hinzugefügt
// und somit sichtbar geschaltet
// beim Inspector/Network auf "Throttling" gehen und ein langsames Netz
// simulieren – dann kann man den Ladeprozess gut sehen!

// wenn man testen will, ob ALLE Inhalte der jeweiligen Seite geladen sind:
window.addEventListener("load", function() {
  loading.style.opacity = "0";
  main.classList.add("sichtbar");
});

document.body.addEventListener("keydown", function(ereignis) {
  // console.log(ereignis.key);
  if (ereignis.key === "r" || ereignis.key === "R") {
    loading.style.opacity = "1";
    main.classList.remove("sichtbar");
    location.reload();
  }
});

CSS

#loading {
  text-align: center;
  width: 100%;
  position: absolute;
  top: 50vh;
  left: 0;
  transition: all 0.5s;
}
main {
  margin: 1.5em auto;
  width: 90%;
  max-width: 1200px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  grid-gap: 2vmin;
  transform: translateY(-250vh);
  transition: all 1.5s;
}
main.sichtbar {
  transform: translateY(0);
}

setInterval-Uhrzeit

Selbstablaufende Uhr (jede Sekunde aktualisiert)

Demo

Javascript

zeit = document.querySelector("#zeit");

// wenn der Wert einstellig ("3") ist, wird er aufgefällt ("03")
function zahlfuellen(wert) {
  // einfaches Umwandeln einer Zahl in einen String
  let t = wert + "";
  if (t.length < 2) {
    // "0" vorne anfügen, wird damit auch zu einem String!
    t = "0" + wert;
    return t;
  } else {
    // wenn nichts geändert werden soll, einfach den Ausgangswert zurückgeben
    return wert;
  }
}

// lässt die Borders ober- und unterhalb jede Sekunde Farbe wechseln
function blinkingCursor() {
  zeit.classList.toggle("aktiv");
}

function aktuelleZeit() {
  const today = new Date();
  let minute = today.getMinutes();
  let stunde = today.getHours();
  let sekunde = today.getSeconds();

  zeit.textContent = `${zahlfuellen(stunde)}:${zahlfuellen(minute)}:${zahlfuellen(sekunde)}`;

  blinkingCursor();
}

const myAni = setInterval(aktuelleZeit, 1000);

ScrollEvent & IntersectionObserver

Erst wenn man ganz nach Unten gescrollt hat, wird der "zustimmen"-Button anwählbar. Damit könnte man z.B. auch dafür sorgen, dass ein Video automatisch anfängt zu spielen, sobald dies im sichtbaren Bereich des Browsers ist…

Demo

Javascript

// dort werden scrollTop angezeigt
const scrollInfos = document.querySelector("#scrollInfos");

const bedingungen = document.querySelector(".bedingungen");

// sichergehen, dass es keine JS-Fehler gibt, weil das gesuchte Dokument nicht exisiert
function scrollToAccept() {
  // bedingungen = document.querySelector(".bedingungen-and-conditions");

  // wenn es das Element "bedingungen" nicht gibt,
  // wird hier aus der Funktion gesprungen ohne dass es eine JS-Fehler gibt
  if (!bedingungen) {
    return;
  }

  bedingungen.addEventListener("scroll", function(ereignis) {
    // scrollHeight ist die tatsächliche Höhe des zu scrollenden Inhalts
    scrollInfos.innerHTML = `scrollTop: ${ereignis.currentTarget.scrollTop}px; scrollHeight: ${ereignis.currentTarget.scrollHeight}px`;
  });
}

scrollToAccept();

// IntersectionObserver ========================

const beobachte = document.querySelector(".beobachte");

// beobachten des Img, ob es im Scrollbereich sichtbar ist oder nicht
function obCallback(payload) {
  // [0] weil "beobachte" das 1. Element in der "Beobachtungsliste ist"
  // sobald das gesuchte Element im sichtbaren Bereich ist, wird "true" ausgegeben
  /*
  console.log(
    `strong.beobachte im sichtbaren Bereich: ${payload[0].isIntersecting}`
  );

  console.log(
    `strong.beobachte intersectionRatio: ${payload[0].intersectionRatio}`
  );
  */
}

const ob = new IntersectionObserver(obCallback);

ob.observe(beobachte);

// beobachten, ob ganz nach unten gescrollt wurde ==============

const zustimmenButton = document.querySelector(".zustimmen");

function zustimmenCallback(ladung) {
  // [0] weil "beobachte" das 1. Element in der "Beobachtungsliste ist"
  // sobald das gesuchte Element im sichtbaren Bereich ist, wird "true" ausgegeben
  console.log(
    `bedingungen.lastElementChild im sichtbaren Bereich: ${ladung[0].isIntersecting}`
  );

  console.log(
    `bedingungen.lastElementChild intersectionRatio: ${ladung[0].intersectionRatio}`
  );

  // wenn der letzte Paragraph von "bedingungen" weitgehend
  // im Scrollbereich sichtbar ist, wird der Button aktiv geschalten
  if (ladung[0].intersectionRatio > 0.8) {
    zustimmenButton.disabled = false;
    // wenn nach einmal Runterscrollen der Button immer aktiv bleiben soll
    // also auch, wenn man wieder raufscrollt:
    zustimmen.unobserve(bedingungen.lastElementChild);
  }
  // ansonsten das "else" einbauen, dass den Button ein und ausblendet
  /*
  else {
    zustimmenButton.disabled = true;
  }
  */
}

// root: auf was soll sich der Observer beziehen
// threshold: Muss das Element komplett sichtbar sein? Dann "1"
const zustimmen = new IntersectionObserver(zustimmenCallback, {
  root: bedingungen,
  threshold: 0.8
});
// das letzte Element von "bedingungen"
zustimmen.observe(bedingungen.lastElementChild);

IntersectionObserver: Film abspielen

Erst wenn der Film komplett im sichtbaren Bereich ist, fängt dieser automatisch an zu spielen. Scrollt man den Film ansatzweise aus dem sichtbaren Bereich, hält er wieder an.

Demo

Javascript

// beobachten, ob das Video komplett sichtbar ist
// siehe auch: https://css-tricks.com/a-few-functional-uses-for-intersection-beobachter-to-know-when-an-element-is-in-view/

const myVideo = document.querySelector("video");

function beobachterCallback(ladung) {
  // [0] weil "video" das 1. Element in der "Beobachtungsliste ist
  // console.log(`Video intersectionRatio: ${ladung[0].intersectionRatio}`);

  // erst wenn das Video vollständig sichtbar ist, läuft es los
  if (ladung[0].intersectionRatio >= 1) {
    myVideo.play();
  } else {
    myVideo.pause();
  }
}

// "threshold: 1" Das Element muss komplett sichtbar sein, dann läuft Video an
const beobachter = new IntersectionObserver(beobachterCallback, {
  threshold: 1
});

// was soll beobachtet werden?
beobachter.observe(myVideo);

Accessible Tabs

Wechseln zwischen den Tabs geht auch mit „Tabbing“! Um die Sache möglichst „accessible“ zu machen, werden hier viele „aria-Codes“ verwendet…

Demo

Javascript

const tabs = document.querySelector(".tabs");
const tabButtons = tabs.querySelectorAll("[role='tab']");
const tabPanels = tabs.querySelectorAll("[role='tabpanel']");

function handleTabClick(ereignis) {
  // hide all tab panels – "lange" Schreibweise
  /*
  tabPanels.forEach(function(panel) {
    panel.hidden = true;
  });
  */

  // hide all tab panels – Arrow Function
  tabPanels.forEach(panel => {
    panel.hidden = true; // alle Panels bekommen das Atrribute "hidden"
  });

  // mark all tabButtons as unselected
  tabButtons.forEach(tabButton => {
    // "aria-selected" kann nicht direkt wie z.B. "style" oder "alt" angesprochen werden!
    tabButton.setAttribute("aria-selected", false);
  });

  // mark the clicked tab as selected
  ereignis.currentTarget.setAttribute("aria-selected", true);

  // find the associated panel and show it
  // lange Schreibweise:
  // const id = ereignis.currentTarget.id;
  // mehrere Sachen auf einmal abfragen!
  const { id, textContent } = ereignis.currentTarget;
  // console.log(id, textContent);

  const tabPanel = tabs.querySelector(`[aria-labelledby="${id}"]`);
  // console.log(tabPanel);
  tabPanel.hidden = false;
}

// bisherige, längere Schreibweise
/*
tabButtons.forEach(function(button) {
  button.addEventListener("click", handleTabClick);
});
*/

// Arrow Function
tabButtons.forEach(button => button.addEventListener("click", handleTabClick));

Vanilla Slideshow

siehe dazu auch: https://codepen.io/ezeherrera/pen/RGVqLQ

Oben ist ein Zustand dargestellt, wenn "overflow: hidden" ausgeschaltet ist und die Slideshow nur 30% Breite einnimmt. Dann kann man sehen, dass:

  • alle "normalen" Slides rechts vom eigentlich sichtbaren Bereich positioniert sind
  • der aktuelle Slide (.current) im sichtbaren Bereich liegt
  • der vorherige Slide (.prev) links vom sichtbaren Bereich ist

Der vorherige Slide befindet sich nur sehr kurz ("const ueberGangDauer") an dieser Stelle und wird dann wieder nach rechts auf den Stapel geschoben.

Die Slideshow geht (bisher) nur vorwärts! Für eine funktionierende Rückwärts-Animation müssten die Slides umgekehrt positioniert werden.

Folgende Events sind eingebaut:

  • Anhalten/Starten bei mouseenter/mouseleave
  • Anhalten/Starten bei Drücken der SPACE-Taste
  • Anhalten und Schritt für Schritt Weitergehen mit Pfeiltaste RECHTS

Die JS- und CSS-Datei ist ausgiebig kommentiert…

Demo

Javascript

// =========== grundlegende Settings ================

// wie lange bleibt ein Slide stehen -- Sekunden
const standDauer = 4.5;

// wie lange ist der Übergand zwischen den Slides -- Sekunden!
// Wert muss deutlich niedriger als der Wert von "standDauer" sein
const ueberGangDauer = 0.7;

// Variable legt fest, ob slideshow automatisch abläuft oder nicht
let slideShowLaufen = true;

/*
  die ".slide"-Elemente bekommen alle eine Transition angefügt
  die "ueberGangDauer" wird auch bei der setTimeout-Funktion
  innerhalb von "weiterGehen()" verwendet
  damit diese synchron selbstablaufen
  sonst werden Teile anderer Slides sichtbar, was nicht gut aussieht!
*/
const slidesNode = document.querySelectorAll("#slideshow .slide");
slidesNode.forEach(function(ereignis) {
  ereignis.style.transition = `all ${ueberGangDauer}s ease-in-out`;
});

// obere NodeList in Array unwandeln
const slides = Array.from(slidesNode);

// span-Element im Header, zeigt welcher Slide gerade gezeigt wird
const slideNummer = document.querySelector("#slideNummer");

// span-Element im Header, zeigt Anzahl der Slides
const slideAnzahl = document.querySelector("#slideAnzahl");
slideAnzahl.textContent = slides.length;

// ersten Slide sofort zeigen
// sonst komische Verzögerung über Dauer von Variable "standDauer"
slides[0].classList.add("current");

// Variable zum Hintereinander-Zeigen der Slides
// ACHTUNG: Wenn vorvorletzte Zeile ("slides[0].classList…") nicht gemacht wird
// hier "i = 0;" schreiben, sonst fängt Slideshow bei 2 an!
let i = 1;

function weiterGehen() {
  const currentSlide = document.querySelector(".current");

  /*
    nur "null", wenn Slideshow gerade angefangen hat
    ansonsten bekommt der gerade sichtbare ".slide" die Klasse ".prev"
    und wird somit nach links geschoben
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  if (currentSlide != null) {
    currentSlide.classList.remove("current");
    currentSlide.classList.add("prev");
  }

  /*
    nach einem kurzem Intervall wird bei ".prev" diese Klasse entfernt
    damit "wandert" der Slide wieder rechts neben den sichtbaren Bereich
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  setTimeout(function() {
    const prevSlide = document.querySelector(".prev");
    if (prevSlide != null) {
      prevSlide.classList.remove("prev");
    }
  }, ueberGangDauer * 1000); // Umrechnung in Millisekunden!

  /*
    das jeweilige "current"-Slide bekommt die Klasse "current"
    und wird somit in den sichtbaren Bereich geschoben
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  let current = slides[i];
  current.classList.add("current");

  // span-Element im Header, zeigt welcher Slide gerade gezeigt wird
  slideNummer.textContent = i + 1;

  i++;
  if (i == slides.length) {
    i = 0;
  }
}

// =============== selbstablaufende Slideshow ================

const cycle = setInterval(function() {
  if (slideShowLaufen == true) {
    weiterGehen();
  }
}, standDauer * 1000);

// ============= Zustand der Slideshow ===============

// Anzeige des Status im Header
const slideShowStatus = document.querySelector("#slideShowStatus");
// steht oder läuft die Slideshow am Anfang?
if (slideShowLaufen == true) {
  slideShowStatus.textContent = "läuft";
} else {
  slideShowStatus.textContent = "steht";
}

// ============== Button zum Weiterklicken =================

const button = document.querySelector("button");
// ACHTUNG: Damit geht die Slideshow weiter,
// wird aber nicht gestoppt!
button.addEventListener("click", weiterGehen);

// =============== mouseenter und mouseleave ================

// slideshow stoppt, wenn Cursor auf der slideshow
// und fängt wieder an, wenn Cursor weggeht
const slideshow = document.querySelector("#slideshow");
slideshow.addEventListener("mouseenter", function() {
  slideShowLaufen = false;
  slideShowStatus.textContent = "steht";
});
slideshow.addEventListener("mouseleave", function() {
  slideShowLaufen = true;
  slideShowStatus.textContent = "läuft";
});

// ========== Slideshow via Keyboard anhalten und weiterblättern ============

// toggelt, ob Slideshow läuft oder nicht
function slideShowAnAus() {
  slideShowLaufen = !slideShowLaufen;

  if (slideShowLaufen == true) {
    slideShowStatus.textContent = "läuft";
  } else {
    slideShowStatus.textContent = "steht";
  }
}

window.addEventListener("keydown", function(ereignis) {
  // console.log(ereignis.key);
  if (ereignis.key === " ") {
    slideShowAnAus();
    // console.log("Space-Taste!");
  }

  // Pfeiltaste n ach rechts blättern jeweils ein Slide weiter
  if (ereignis.key === "ArrowRight") {
    slideShowLaufen = false;
    slideShowStatus.textContent = "steht";
    weiterGehen();
  }

  // Pfeiltaste nach links blättern jeweils ein Slide zurück
  // ACHTUNG, das geht noch nicht!
  /*
  if (ereignis.key === "ArrowLeft") {
    slideShowLaufen = false;
    button.textContent = "Ani steht";
    slideShowWeiter("zurueck");
  }
  */
});

Minimal Slideshow

siehe dazu auch: https://codepen.io/ezeherrera/pen/RGVqLQ

Selbstablaufende Bildershow, die nur bei mouseenter die Slides anhält

Demo

Javascript

// =========== grundlegende Settings ================

// wie lange bleibt ein Slide stehen -- Sekunden
const standDauer = 6.5;

// wie lange ist der Übergand zwischen den Slides -- Sekunden!
// Wert muss deutlich niedriger als der Wert von "standDauer" sein
const ueberGangDauer = 0.7;

// Variable legt fest, ob slideshow automatisch abläuft oder nicht
let slideShowLaufen = true;

/*
  die ".slide"-Elemente bekommen alle eine Transition angefügt
  die "ueberGangDauer" wird auch bei der setTimeout-Funktion
  innerhalb von "weiterGehen()" verwendet
  damit diese synchron selbstablaufen
  sonst werden Teile anderer Slides sichtbar, was nicht gut aussieht!
*/
const slidesNode = document.querySelectorAll("#slideshow .slide");
slidesNode.forEach(function(ereignis) {
  ereignis.style.transition = `all ${ueberGangDauer}s ease-in-out`;
});

// NodeList in Array unwandeln
const slides = Array.from(slidesNode);

// ersten Slide sofort zeigen
// sonst komische Verzögerung über Dauer von Variable "standDauer"
slides[0].classList.add("current");

// Variable zum Hintereinander-Zeigen der Slides
// ACHTUNG: Wenn vorvorletzte Zeile ("slides[0].classList…") nicht gemacht wird
// hier "i = 0;" schreiben, sonst fängt Slideshow bei 2 an!
let i = 1;

function weiterGehen() {
  const currentSlide = document.querySelector(".current");

  /*
    nur "null", wenn Slideshow gerade angefangen hat
    ansonsten bekommt der gerade sichtbare ".slide" die Klasse ".prev"
    und wird somit nach links geschoben
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  if (currentSlide != null) {
    currentSlide.classList.remove("current");
    currentSlide.classList.add("prev");
  }

  /*
    nach einem kurzem Intervall wird bei ".prev" diese Klasse entfernt
    damit "wandert" der Slide wieder rechts neben den sichtbaren Bereich
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  setTimeout(function() {
    const prevSlide = document.querySelector(".prev");
    if (prevSlide != null) {
      prevSlide.classList.remove("prev");
    }
  }, ueberGangDauer * 1000); // Umrechnung in Millisekunden!

  /*
    das jeweilige "current"-Slide bekommt die Klasse "current"
    und wird somit in den sichtbaren Bereich geschoben
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  let current = slides[i];
  current.classList.add("current");

  i++;
  if (i == slides.length) {
    i = 0;
  }
}

// =============== selbstablaufende Slideshow ================

const cycle = setInterval(function() {
  if (slideShowLaufen == true) {
    weiterGehen();
  }
}, standDauer * 1000);

// =============== mouseenter und mouseleave ================

// header soll roten HG bekommen, wenn mouseenter Slideshow anhält
const header = document.querySelector("header");

// slideshow stoppt, wenn Cursor auf der slideshow
// und fängt wieder an, wenn Cursor weggeht
const slideshow = document.querySelector("#slideshow");
slideshow.addEventListener("mouseenter", function() {
  slideShowLaufen = false;
  header.style.background = "red";
});
slideshow.addEventListener("mouseleave", function() {
  slideShowLaufen = true;
  header.style.background = "black";
});

Minimal Slideshow mit PHP

siehe dazu auch: https://codepen.io/ezeherrera/pen/RGVqLQ

  • window.addEventListener eingebaut, der erst "main" zeigt, wann alle Bilder vom Server geladen sind!
  • Selbstablaufende Bildershow, deren Bilder automatisch via PHP eingelesen werden
  • via CSS so eingestellt, dass die Bilder Browser-füllend gezeigt werden
  • mouseenter/mouseleave hält die Slideshow an oder lässt sie weiterlaufen
  • am oberen Rand von "script.js" kann man diverse Settings festlegen

Demo

Javascript

// =========== grundlegende Settings ================

// wie lange bleibt ein Slide stehen -- Sekunden
const standDauer = 2.5;

// wie lange ist der Übergand zwischen den Slides -- Sekunden!
// Wert muss deutlich niedriger als der Wert von "standDauer" sein
const ueberGangDauer = 0.7;

// Variable legt fest, ob slideshow automatisch abläuft oder nicht
let slideShowLaufen = true;

// legt fest, ob die Slideshow in der Reihenfolge der Slides
// oder zufällig abläuft
let zufall = true;

// ==== kontrolliertes Loading beim Starten der Site ==========

// gut, weil die Bilder der Slideshow unfangreich sein können
// und via PHP importiert werden

const main = document.querySelector("main");
const loading = document.querySelector("#loading");

// erst wenn alle Bilder aus dem Web komplett geladen ist,
// wird bei "main" die Klasse "sichtbar" hinzugefügt
// und somit sichtbar geschaltet
// beim Inspector/Network auf "Throttling" gehen und ein langsames Netz
// simulieren – dann kann man den Ladeprozess gut sehen!

// wenn man testen will, ob ALLE Inhalte der jeweiligen Seite geladen sind:
window.addEventListener("load", function() {
  loading.style.opacity = "0";
  main.classList.add("sichtbar");
});

// ===== ab hier Sachen für die eigentliche Slideshow ============

/*
  die ".slide"-Elemente bekommen alle eine Transition angefügt
  die "ueberGangDauer" wird auch bei der setTimeout-Funktion
  innerhalb von "weiterGehen()" verwendet
  damit diese synchron selbstablaufen
  sonst werden Teile anderer Slides sichtbar, was nicht gut aussieht!
*/
const slidesNode = document.querySelectorAll("#slideshow .slide");
slidesNode.forEach(function(ereignis) {
  ereignis.style.transition = `all ${ueberGangDauer}s ease-in-out`;
});

// NodeList in Array unwandeln
const slides = Array.from(slidesNode);

// ersten Slide sofort zeigen
// sonst komische Verzögerung über Dauer von Variable "standDauer"
// erstes Bild zeigen
// slides[0].classList.add("current");

// zufällig ausgewähltes Bild zeigen
if (zufall === true) {
  slides[Math.floor(Math.random() * slides.length)].classList.add("current");
} else {
  slides[0].classList.add("current");
}

// Variable zum Hintereinander-Zeigen der Slides
// ACHTUNG: Wenn vorvorletzte Zeile ("slides[0].classList…") nicht gemacht wird
// hier "i = 0;" schreiben, sonst fängt Slideshow bei 2 an!
let i = 1;

function weiterGehen() {
  const currentSlide = document.querySelector(".current");

  /*
    nur "null", wenn Slideshow gerade angefangen hat
    ansonsten bekommt der gerade sichtbare ".slide" die Klasse ".prev"
    und wird somit nach links geschoben
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  if (currentSlide != null) {
    currentSlide.classList.remove("current");
    currentSlide.classList.add("prev");
  }

  /*
    nach einem kurzem Intervall wird bei ".prev" diese Klasse entfernt
    damit "wandert" der Slide wieder rechts neben den sichtbaren Bereich
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  setTimeout(function() {
    const prevSlide = document.querySelector(".prev");
    if (prevSlide != null) {
      prevSlide.classList.remove("prev");
    }
  }, ueberGangDauer * 1000); // Umrechnung in Millisekunden!

  /*
    das jeweilige "current"-Slide bekommt die Klasse "current"
    und wird somit in den sichtbaren Bereich geschoben
    um das zu sehen, im Stylesheet bei "#slideshow" "overflow:hidden" auskommentieren!
  */
  let current = slides[i];
  current.classList.add("current");

  /*
    zufällige Reihenfolge, zur Sicherheit Abfrage,
    ob gleicher Wert für ehemaliges i und i rauskommt
    falls das passiert, einfach i hochzählen
  */
  let ehem_i = i;

  if (zufall === true) {
    i = Math.floor(Math.random() * slides.length);
  } else {
    i = i + 1;
  }

  if (i === ehem_i) {
    console.log(`gleiches Bild:  ${i} / ${ehem_i}`);
    i++;
  }

  // einfaches Hochzählen der Slides
  // i++;

  if (i == slides.length) {
    i = 0;
  }
}

// =============== selbstablaufende Slideshow ================

const cycle = setInterval(function() {
  if (slideShowLaufen == true) {
    weiterGehen();
  }
}, standDauer * 1000);

// =============== Anhalten/Weiterspielen bei mouseenter/mouseleave ================

function playPauseZeigen() {
  if (slideShowLaufen === true) {
    playPauseAnzeige.textContent = "PLAY";
  } else {
    playPauseAnzeige.textContent = "PAUSE";
  }

  playPauseAnzeige.classList.add("sichtbarAni");

  playPauseAnzeige.addEventListener(
    "animationend",
    function() {
      // console.log("Animation beended!");
      playPauseAnzeige.classList.remove("sichtbarAni");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );
}

// Anzeige-Element für "PAUSE" und "PLAY" generieren
const playPauseAnzeige = document.createElement("p");
playPauseAnzeige.id = "playPauseAnzeige";
document.body.append(playPauseAnzeige);
playPauseZeigen();

// =============== mouseenter und mouseleave ================

// header soll roten HG bekommen, wenn mouseenter Slideshow anhält
const header = document.querySelector("header");

// slideshow stoppt, wenn Cursor auf der slideshow
// und fängt wieder an, wenn Cursor weggeht
const slideshow = document.querySelector("#slideshow");
slideshow.addEventListener("mouseenter", function() {
  slideShowLaufen = false;
  header.classList.add("sichtbar");
  playPauseZeigen();
});
slideshow.addEventListener("mouseleave", function() {
  slideShowLaufen = true;
  header.classList.remove("sichtbar");
  playPauseZeigen();
});

// === Anzeige der Bildanzahl im Header =====

// "bildAnzahl" befindet sich im header
// "bildZaehler" in Main und wird via PHP befüllt

const bildAnzahl = document.querySelector("#bildAnzahl");
const bildZaehler = document.querySelector("#bildZaehler");

bildAnzahl.innerHTML = bildZaehler.innerHTML;

PHP

<?php
// $ordner wird bei "index.php" festgelegt
$verzeichnis = dir($ordner);

// nächste Zeile sorgt dafür, dass das Array entleert wird und man es somit mehrfach verwenden kann!
$files = array();

while($eintrag=$verzeichnis->read())
$files[] = $eintrag; // ein array!
$verzeichnis->close();

sort($files); // alphabetisch sortieren

$pfad = $ordner . '/';

$clength = count($files)-2;

echo "<div id='slideshow'>\n";

foreach ($files as $eintrag){
    // http://www.php.net/manual/de/language.types.string.php
    // naechste zwei Zeilen fragen ab, ob Eintrag mit einem "." anfängt!
    $first = $eintrag[0];
    if ($first != "."){

        $bild = $pfad . $eintrag;

        echo "
            <figure class='slide'>
                <img src='" . $bild . "' alt=''>
            </figure>
        \n";
    }
}

echo "      </div><!-- /slideshow -->\n\n";
echo "      <p class='hinweis'>Das Verzeichnis enthält <strong style='color:red;'>$clength</strong> Bilder.</p>\n\n";
?>

Überschrift mit ausblendbaren Textblock danach

Überschrift mit ausblendbaren Textblock danach

Demo

Javascript


Liste zufällig sortieren

  • Sortiert eine beliebig lange Liste und erzeugt daraus eine zufällig sortierte Liste
  • die neue Liste wird in Gruppen von 3 Items dargestellt (nur via CSS)
  • der Copy-Button kopiert die komplette Liste (incl. ul und id) in den Arbeitsspeicher

Demo

Javascript

// Zufalls-Funktion
// https://www.w3resource.com/javascript-exercises/javascript-array-exercise-17.php
function shuffle(myArray) {
  let l = myArray.length;
  let temp;
  let index;

  // While there are elements in the array
  while (l > 0) {
    // Pick a random index
    index = Math.floor(Math.random() * l);
    // Decrease l by 1
    l--;
    // And swap the last element with it
    temp = myArray[l];
    myArray[l] = myArray[index];
    myArray[index] = temp;
  }
  return myArray;
}

function listeZufaelligSortieren() {
  // eine NodeList
  const listNodes = document.querySelectorAll("#ausgangsListe li");

  // Umwandlung in Array
  const listArray = Array.from(listNodes);

  // hier das Array zufällig sortieren und in eine neues Array "zufallsArray" einfügen
  const zufallsArray = shuffle(listArray);
  // console.log(zufallsListe[0].outerHTML);

  // hier soll das neu sortierte Array eingefügt werden
  const zufallsListeAusgabe = document.querySelector("#zufallsListeAusgabe");

  // AusgabeListe erst mal leeren, sonst mehrfache Listen
  // wenn man mehrmals auf den button#shuffle klickt
  zufallsListeAusgabe.innerHTML = "";

  // for-Schleife geht durch alle Listenelemente des Array "zufallsArray"
  // und fügt diese bei zufallsListeAusgabe hintereinander ein
  // wichtig ist das "+="
  for (let i = 0; i < zufallsArray.length; i++) {
    zufallsListeAusgabe.innerHTML += zufallsArray[i].outerHTML;
  }

  // unmittelbares Kopieren der neuen Liste in den Arbeitsspeicher
  // - ohne weiteren Button
  // kopieren("#zufallsListeAusgabe");

  // erst jetzt den Copy-Button sichtbar machen
  // wenn bei "zufallsListeAusgabe" Inhalt vorhanden ist
  copyButton.style.display = "block";
}

const shuffleButton = document.querySelector("#shuffle");
shuffleButton.addEventListener("click", listeZufaelligSortieren);

// die neu erzeugte Liste zuerst in ein Input-Feld kopieren
// und dann in den Arbeitsspeicher
function kopieren(ele) {
  const tempElement = document.createElement("input");
  tempElement.setAttribute("type", "text");
  tempElement.setAttribute("contenteditable", true);
  tempElement.id = "tempElement";
  // sonst wird das Element im Browser sichtbar
  tempElement.style.translate = "-1000vw";

  const copyListe = document.querySelector(ele);
  tempElement.value = copyListe.outerHTML;
  document.body.append(tempElement);

  tempElement.select();
  document.execCommand("copy");

  tempElement.remove();
}

function neueListeKopieren() {
  kopieren("#zufallsListeAusgabe");
}

const copyButton = document.querySelector("#copy");
copyButton.addEventListener("click", neueListeKopieren);

Liste zufällig sortieren – animiert

  • Animiert den Wechsel zwischen der Ausgangsliste und der zufällig generierten Liste

Demo

Javascript

// Zufalls-Funktion
// https://www.w3resource.com/javascript-exercises/javascript-array-exercise-17.php
function shuffle(myArray) {
  let l = myArray.length;
  let temp;
  let index;

  // While there are elements in the array
  while (l > 0) {
    // Pick a random index
    index = Math.floor(Math.random() * l);
    // Decrease l by 1
    l--;
    // And swap the last element with it
    temp = myArray[l];
    myArray[l] = myArray[index];
    myArray[index] = temp;
  }
  return myArray;
}

function listeZufaelligSortieren() {
  // das Div mit der Ausgangsliste
  const ausgangsBlock = document.querySelector("#ausgangsBlock");
  ausgangsBlock.classList.remove("sichtbar");

  // das Div mit der zufallsListe
  const zufallBlock = document.querySelector("#zufallBlock");

  ausgangsBlock.addEventListener(
    "transitionend",
    function() {
      // console.log("ausgangsBlock-Transition beended!");
      zufallBlock.classList.add("sichtbar");
    },
    { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
  );

  // eine NodeList
  const listNodes = document.querySelectorAll("#ausgangsListe li");

  // Umwandlung in Array
  const listArray = Array.from(listNodes);

  // hier das Array zufällig sortieren und in eine neues Array "zufallsArray" einfügen
  const zufallsArray = shuffle(listArray);
  // console.log(zufallsListe[0].outerHTML);

  // hier soll das neu sortierte Array eingefügt werden
  const zufallsListeAusgabe = document.querySelector("#zufallsListeAusgabe");

  // AusgabeListe erst mal leeren, sonst mehrfache Listen
  // wenn man mehrmals auf den button#shuffle klickt
  zufallsListeAusgabe.innerHTML = "";

  // for-Schleife geht durch alle Listenelemente des Array "zufallsArray"
  // und fügt diese bei zufallsListeAusgabe hintereinander ein
  // wichtig ist das "+="
  for (let i = 0; i < zufallsArray.length; i++) {
    zufallsListeAusgabe.innerHTML += zufallsArray[i].outerHTML;
  }

  // unmittelbares Kopieren der neuen Liste in den Arbeitsspeicher
  // - ohne weiteren Button
  // kopieren("#zufallsListeAusgabe");
}

const shuffleButton = document.querySelector("#shuffle");
shuffleButton.addEventListener("click", listeZufaelligSortieren);

// die neu erzeugte Liste zuerst in ein Input-Feld kopieren
// und dann in den Arbeitsspeicher
function kopieren(ele) {
  const tempElement = document.createElement("input");
  tempElement.setAttribute("type", "text");
  tempElement.setAttribute("contenteditable", true);
  tempElement.id = "tempElement";
  // sonst wird das Element im Browser sichtbar
  tempElement.style.translate = "-1000vw";

  const copyListe = document.querySelector(ele);
  tempElement.value = copyListe.outerHTML;
  document.body.append(tempElement);

  tempElement.select();
  document.execCommand("copy");

  tempElement.remove();
}

function neueListeKopieren() {
  kopieren("#zufallsListeAusgabe");
}

const copyButton = document.querySelector("#copy");
copyButton.addEventListener("click", neueListeKopieren);

// der Button "Neuauslosen" macht die normale Liste wieder sichtbar
// und lässt das Auslosen von vorne anfangen
const neuAuslosen = document.querySelector("#neuAuslosen");
neuAuslosen.addEventListener("click", function() {
  // das Div mit der Ausgangsliste
  const ausgangsBlock = document.querySelector("#ausgangsBlock");

  // das Div mit der zufallsListe
  const zufallBlock = document.querySelector("#zufallBlock");
  zufallBlock.classList.remove("sichtbar");

  zufallBlock.addEventListener(
    "transitionend",
    function() {
      ausgangsBlock.classList.add("sichtbar");
    },
    { once: true }
  );
});

Switch Case, animierter Fisch

  • Switch/Case-Beispiel
  • „lokale“ CSS-Variablen via JS verändert (setAttribute…)

Demo

CSS

/* CSS lokale Variable! */
.fish {
  width: 300px;
  --x: 0;
  --y: 0;
  /* legt fest, ob Fish nach links oder rechts schaut */
  --rotateY: 0;
  /* legt fest, ob Fish nach oben oder unten schaut */
  --rotate: 0;
  /* Reihenfolge wichtig! */
  transform: translateX(var(--x)) translateY(var(--y)) rotateY(var(--rotateY))
    rotate(var(--rotate));
  /* damit glatter beim Animieren via Tastatur */
  transition: transform 0.2s;
}

Javascript

const fish = document.querySelector(".fish");

// zum Verschieben des Fish
let x = 0;
let y = 0;

// Wie groß sind die Sprünge
const speed = 50;

// zum waagrechten Umdrehen des Fishes
let flipped = false;

// zum senkrechten Umdrehen des Fishes
let rotate = 0;

function handleKeydown(ereignis) {
  // falls etwas anderes als die Arrow-Tasten gedrückt wird
  // soll JS hier einfach aussteigen
  if (!ereignis.key.includes("Arrow")) return;
  //console.log(ereignis.key);

  switch (ereignis.key) {
    case "ArrowUp":
      y = y - 1;
      rotate = -90;
      break;
    case "ArrowDown":
      y = y + 1;
      rotate = 90;
      break;
    case "ArrowRight":
      x = x + 1;
      flipped = false;
      rotate = 0;
      break;
    case "ArrowLeft":
      x = x - 1;
      flipped = true;
      rotate = 0;
      break;
    default:
      console.log("ungültige Taste!");
      break;
  }

  // Ternary statt if/else
  // wenn "flipped" true ist, 180deg drehen sonst 0
  fish.setAttribute(
    "style",
    `
    --rotate: ${rotate}deg;
    --rotateY: ${flipped ? "180deg" : "0"};
    --x: ${x * speed}px;
    --y: ${y * speed}px;
  `
  );
}

window.addEventListener("keydown", handleKeydown);

Ajax

  • Läd bei Klicken auf den Button eine externe Datei und fügt deren Inhalt in ein Div ein
  • Dieses Div wird erst sichtbar, wenn die externe Datei erfolgreich geladen wurde

Demo

Tutorial

https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX/Getting_Started

Javascript

document.querySelector("#ajaxButton").addEventListener("click", makeRequest);

// diese Datei wird geladen! Aktuell im Ordner "ajax"…
const datei = "ajax/test.html";
const warnung = document.querySelector("#warnung");

let httpRequest;

function makeRequest() {
  httpRequest = new XMLHttpRequest();

  if (!httpRequest) {
    warnung.textContent = "Giving up :( Cannot create an XMLHTTP instance!";
    return false;
  }

  httpRequest.onreadystatechange = externenContentLaden;
  httpRequest.open("GET", datei);
  httpRequest.send();
}

function externenContentLaden() {
  let ajaxContent;
  const ajaxRein = document.querySelector("#ajaxRein");

  if (httpRequest.readyState === XMLHttpRequest.DONE) {
    if (httpRequest.status === 200) {
      ajaxContent = httpRequest.responseText;
      ajaxRein.innerHTML = ajaxContent;
      ajaxRein.classList.add("sichtbar");
    } else {
      warnung.textContent = `Die Datei ${datei} konnte nicht geladen werden!`;
    }
  }
}

Ajax Popup

  • Läd bei Klicken auf einen der Links die jeweils dazugehörige, externe Datei und fügt deren Inhalt in ein Popup ein
  • Dieses Popup wird erst sichtbar, wenn die externe Datei erfolgreich geladen wurde

Demo

Javascript

// ============= Elemente vorab generieren =====================

/* CSS-Angaben für diese Elemente bei "popup.css" */

const abdecker = document.createElement("div");
abdecker.id = "abdecker";

// die animierte "Sanduhr", die anzeigt, dass ein großes Bild geladen wird
const abdeckerSanduhr = document.createElement("img");
abdeckerSanduhr.id = "abdeckerSanduhr";
abdeckerSanduhr.src = "./assets/p/waiting_white.svg";

const ajaxContentArticle = document.createElement("article");
ajaxContentArticle.id = "ajaxContentArticle";

// hier soll der Content der externen Datei reingeladen werden
const ajaxContentArticleDiv = document.createElement("div");
ajaxContentArticleDiv.id = "ajaxContentArticleDiv";

const schliesser = document.createElement("span");
schliesser.id = "schliesser";
schliesser.textContent = "×";
schliesser.role = "button";
schliesser.tabIndex = "0"; // Schreibweise in JS beachten!
schliesser.title = "Popup schliessen";

// zusammenbauen und einfügen
ajaxContentArticle.appendChild(schliesser);
ajaxContentArticle.appendChild(ajaxContentArticleDiv);
abdecker.appendChild(abdeckerSanduhr);
abdecker.appendChild(ajaxContentArticle);
document.body.appendChild(abdecker);

// ====================== Ajax-Funktionalität ======================

let httpRequest;
let datei; // diese Datei soll via AJAX geladen werden
let ajaxContent;

function popupFuellen(ereignis) {
  ereignis.preventDefault();

  // gesuchte Dabei aus dem aktuell angeklickten Link herausholen
  datei = ereignis.currentTarget.href;
  // console.log(datei);

  httpRequest = new XMLHttpRequest();

  if (!httpRequest) {
    ajaxConten.textContent =
      "Sorry! Es konnte keine XMLHTTP instance etabliert werden!";
    return false;
  }

  httpRequest.onreadystatechange = externenContentLaden;
  httpRequest.open("GET", datei);
  httpRequest.send();
}

function externenContentLaden() {
  if (httpRequest.readyState === XMLHttpRequest.DONE) {
    if (httpRequest.status === 200) {
      ajaxContent = httpRequest.responseText;
    } else {
      ajaxContent = `<h2 style="color:red;padding:2em;">Sorry: Die Datei ${datei} konnte nicht geladen werden!</h2>`;
    }

    ajaxContentArticleDiv.innerHTML = ajaxContent;

    abdeckerSanduhr.classList.add("sichtbar");
    abdecker.classList.add("sichtbar");

    abdecker.addEventListener(
      "transitionend",
      function() {
        ajaxContentArticle.classList.add("sichtbar");
      },
      { once: true } // damit läuft dieser "addEventListener" nur einmal und addiert sich nicht auf
    );
  }
}

// alle <a>-Tags innerhalb der ul.ajaxLinks
const ajaxLinks = document.querySelectorAll(".ajaxLinks a");
// console.log(ajaxLinks);

ajaxLinks.forEach(function(ereignis) {
  ereignis.addEventListener("click", popupFuellen);
});

function popupWeg() {
  abdeckerSanduhr.classList.remove("sichtbar");
  ajaxContentArticle.classList.remove("sichtbar");

  ajaxContentArticle.addEventListener(
    "transitionend",
    function() {
      abdecker.classList.remove("sichtbar");
    },
    { once: true }
  );
}

// "×" zum Schliessen des popups
schliesser.addEventListener("click", popupWeg);
abdecker.addEventListener("click", popupWeg);

Weitere Vorträge: