Seite nicht gefunden – Medienbasteleien, Webdesign und Onlinekram https://medienmarmela.de Medienbasteleien, Webdesign und Onlinekram Wed, 29 Nov 2023 14:29:51 +0000 de-DE hourly 1 https://wordpress.org/?v=6.4.3 https://medienmarmela.de/wp-content/uploads/2018/11/cropped-mm_logo-e1541169319556-32x32.png Seite nicht gefunden – Medienbasteleien, Webdesign und Onlinekram https://medienmarmela.de 32 32 Ein Röhrenfernseher-3D-Modell … https://medienmarmela.de/fernseher/ https://medienmarmela.de/fernseher/#respond Tue, 07 Nov 2023 22:18:25 +0000 https://medienmarmela.de/?p=3801

Inhalt

Worum gehts?

Ich halte Formebenen in Adobe After Effects für ein völlig unterschätztes Gestaltungsmittel – vor allem, wenn es 3D-Ebenen incl. Extrusionen und Geometrieoptionen sind 🙂
Das Vorhaben: Einen alten Röhrenfernseher als (halbwegs) detailliertes 3D-Objekt basteln, um das man mit der Kamera herumfahren kann und dessen Bedienelemente animierbar sind:

Formebenen in a nutshell

Formebenen können standardisierte Formen (Rechtecke, Ellipsen, Sterne) und freie Pfade enthalten. Diese können – ähnlich wie Masken – in verschiedenen Modi, z.B. "Zusammenfügen" oder "Abziehen" miteinander kombiniert werden und mit Füllung und Kontur versehen werden. Durch die Möglichkeit, Objekte zu gruppieren und je Gruppe andere Zusammenführungsmodi anzuwenden, gibt es erstaunlich viele Möglichkeiten, mit geometrischen oder freien Pfaden diverse Formen zu erstellen:

Rechteck, Ellipse, Stern und freier Pfad in einer Formebene, Modus: "Hinzufügen"

Rechteck, Ellipse und Stern in einer Formebene, Modus: "Abziehen" erzeugt Aussparungen

Von der Ellipse wird die Gruppe mit Rechteck und Stern (mit gruppeninternem Modus "Abziehen") abgezogen


Das Beste daran: Es handelt sich um vektorisierte Pfade, die entsprechend nicht verpixeln.
Nach Umwandlung in eine 3D-Ebene können verschiedene Geometrieoptionen angewendet werden, u.a. Kantenform, Kantentiefe und Extrusion:

Extrudierte und beleuchtete 3D-Formebene

Kanten "konkav" (= Rinne), "konvex" (= Beule) und "eckig", v.l.n.r.


Die einzelnen Eigenschaften der Einzelteile können über Keyframe-Animationen manipuliert und animiert werden:

Animiert: Pfad, Rundheit, Extrusionstiefe, Kantentiefe, Position, Drehung.
Artefakte sind dem Dateiformat (*.gif) geschuldet.


Im Rahmen dieser Möglichkeiten habe ich also versucht, ein 3D-Modell eines Röhrenfernsehers zu basteln, um das man mit der Kamera herumfahren kann und deren Regler beweglich und animierbar sind. Das habe ich aufgezeichnet und nachträglich kommentiert:

Der Fernseherbau als Video Walkthrough

Den gesamten Produktionsprozess gibt es hier im recht stark beschleunigten, kommentierten Walkthrough. Die Kommentierung und ein paar Basis-Skills in AFX sollten ausreichen, um den Fernseher nachzubauen (Wenn nicht: Gerne melden!).

Download dew AFX-Projektes

Die Datei TV-Model.aep enthält das gesamte AFX-Projekt ohne Animationen. Die AFX-Projektdatei ist mit "Adobe After Effects Version 24.0.2 (Build 3)" erstellt. Wie weit das abwärtskompatibel ist, weiß ich nicht.

 

Der Artikel "Ein Röhrenfernseher-3D-Modell …“ von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht, darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier. In dem Walkthrough-Video wird als Musik "Freedom – Inspired Cinematic Background Music For Video" von lesfm, Lizenz: Pixabay, genutzt, außerdem Beispielbilder von Röhrenfernsehern, die von RIO Requisiten Interieur Objektdesign stammen.

]]>
https://medienmarmela.de/fernseher/feed/ 0
Schieberegler-Experimente https://medienmarmela.de/schieberegler-experimente/ https://medienmarmela.de/schieberegler-experimente/#respond Sat, 10 Jun 2023 17:23:21 +0000 https://medienmarmela.de/?p=3757 In meinem letzten Beitrag zu Audacity Themes habe ich nach einer Möglichkeit eines Vorher-Nachher-Vergleichs von Bildern gesucht. Und dann einfach selber eine gebaut. Hier die dazugehörige Doku…

Worum gehts?

Hier eine erste Basisversion: Mit dem Schieberegler an der Unterseite lassen sich zwei Bilder miteinander vergleichen:

 
 

Wie funktionierts?

Das Ganze basiert auf einem Standard HTML-Element aus der <input>-Gruppe (MDN Docs: input) vom type="range" (MDN Docs: input type 'range'). Eigentlich gedacht für Schieberegler, kann man den Wert mittels jQuery live auslesen und für die Bildanzeige weiterverwenden. Dafür benötigt man ein schlichtes HTML-Gerüst:

HTML

<div id="imgslider" class="compare">
  <div class="imgcontainer">
    <div id="img01">&nbsp;</div>
    <div id="img02">&nbsp;</div>
  </div>
  <input id="slider" max="100" min="0" name="slider01" step="any" type="range" value="50">
</div>

Das Ganze wird mittels CSS gestaltet: Das Elternelement #imgslider[nr] ist lediglich dazu da, dem Gesamtgebilde eine Größe und Ausrichtung mitzugeben. Der .imgcontainer darin muss relativ positioniert sein, um die seine beiden Kinder, die eigentlichen Bild-Container #img[nr] absolut einpassen zu können. Außerdem kann hier mittel aspect-ratio ein Seitenverhältnis angegeben werden. Die Bild-Container werden beide auf 50% Breite gesetzt, die Grafiken als Hintergrundbilder mit background-size:cover; eingebaut, Das eine allerdings mit background-position: left center; das andere mit right center;. Passend dazu werden sie mit left:0; bzw. right:0; positioniert. Das input type="range"-Element darunter wird auf 100% Breite gesetzt:

CSS

#imgslider {
   width: 1000px;
   max-width: 100%;
   margin: 1rem 0;
}
.imgcontainer {
   position: relative;
   margin: 0;
   width:100%;
   aspect-ratio: 1 / 1;
}
.compare #img01, .compare #img02 {
   position: absolute;
   top: 0;
   height: 100%;
   width: 50%;
   max-width:100%;
}
.compare #img01 {
   background: transparent url(https://medienmarmela.de/wp-content/uploads/2023/06/europe_topography.png) no-repeat left center / cover;
   left: 0;
}
.compare #img02 {
   background: transparent url(https://medienmarmela.de/wp-content/uploads/2023/06/europe_administration.png) no-repeat right center / cover;
   right: 0;
}
.compare #slider {
   width: 100%;
   height:auto;
}

Um nun den Slider-Wert abzugreifen und auf die Bilder anzuwenden, benötigt man ein kleines jQuery-Skript:

jQuery

$(document).on('input', '.compare #slider', function() {
    var imgWidth = $('.compare #slider').val(),
        secondImgWidth = 100 - imgWidth;
    $('.compare #img01').css('width',imgWidth + '%');
    $('.compare #img02').css('width',secondImgWidth + '%');
});

Ich nutze den Event Listener .on(), da ein $('input[type="range"]').change() erst dann feuert, wenn das Verschieben des Sliders abgeschlossen ist. Ansonsten werden bei jeder Änderung der Sliderposition zwei Variablen gesetzt und aktualisiert (imgWidth ist wer Wert des Sliders, secondImgWidth 100 – imgWidth), die mittels css() und angereichert um das % als Breite für #img01 und #img02 gesetzt werden.

Eigentlich schon zu einfach.

Was geht sonst noch?

Auf der Basis lassen sich natürlich auch andere Bildübergänge erstellen, z.B. eine

Überblendung
 
 

Abweichend vom ersten Beispiel heißt hier die Klasse des #imgslider passenderwise .opacity statt .compare, außerdem ist sind der .imgcontainer mit aspect-ratio:16 / 9; und beide Bilder mit width:100%;, left:0; und background-position:center center; angelegt, das zweite Bild außerdem mit opacity:0;:

CSS

.opacity .imgcontainer {
   position: relative;
   margin: 0;
   width:100%;
   aspect-ratio: 16 / 9;
}
.opacity #img01, .opacity #img02 {
   position: absolute;
   top: 0;
   left:0;
   height: 100%;
   width: 100%;
   max-width:100%;
}
.opacity #img01 {
   background: url(https://medienmarmela.de/wp-content/uploads/2023/06/skeleton.png) no-repeat center / cover;
}
.opacity #img02 {
   background: url(https://medienmarmela.de/wp-content/uploads/2023/06/muscles.png) no-repeat center / cover;
   opacity:0;
}

Un das jQuery benötigt lediglich eine Übersetzung von Sliderposition in Deckkraft:

jQuery

$(document).on('input', '.opacity #slider02', function() {
   var imgOpacityPos = $('.opacity #slider02').val(),
       imgOpacity = imgOpacityPos / 100;
   $('.opacity #img02').css('opacity',imgOpacity);
});
Verschiebung

Als weitere Variante könnte man einen Verschiebe-Effekt basteln:

 
 

Hier heißt die Klasse des #imgslider nun .scroll, der .imgcontainer benötigt ein overflow:hidden; (damit die herausgeschobenen Bildteile nicht angezeigt werden), und #img01 wird mit left: -100%; positioniert. Das jQuery sieht so aus:

jQuery

$(document).on('input', '.scroll #slider03', function() {
   var imgScroll = $('.scroll #slider03').val(),
       secondImgScroll = imgScroll - 100;
   $('.scroll #img02').css('left',imgScroll + '%');
   $('.scroll #img01').css('left',secondImgScroll + '%');
});

So weit meine kleinen Schieberegler-Experimente 🙂
Als zukünftige Versionen werde ich – bestimmt, irgendwann – das Styling der Slider (oder einen JS-Nachbau von Slidern) und eine Variante mit vertikalen Slidern versuchen. Wenn Ihr eher fertig seid, lasst es mich gerne wissen (als Kommentar oder Mail).

Das Ganze gibt es als Bastelgrundlage hier zum Download. Wie immer, ist die HTML-Datei mit dem meisten CSS und JS ausgestattet oder lädt diesen von medienmarmela.de und ist ’stand alone' lauffähig.

Der Artikel "Schieberegler-Experimente“ von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht, darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.
Die genutzen Bilder basieren auf

]]>
https://medienmarmela.de/schieberegler-experimente/feed/ 0
Hübsche(re) Audacity Themes https://medienmarmela.de/audacity-themes/ https://medienmarmela.de/audacity-themes/#respond Fri, 09 Jun 2023 19:46:51 +0000 https://medienmarmela.de/?p=3570 Nichts gegen Audacity, als Einsteigenden-Audiotool hat es – auch jenseits einiger Scharmützel mit der Open Source-Community und dadurch ausgelöster Forks – sicherlich seine Berechtigung. Die Benutzendenoberfläche ist designmäßig aber nahe an einer Zumuntung und kann Schmerzen verursachen. Die verfügbaren Darstellungsmöglichkeiten, ab Werk "Klassisch", "Dunkel", "Hell" und "Hoher Kontrast", sind wirklich keine Freude:

Anlässlich des Release der Version 3.3.x habe ich daher das dringende Bedürfnis verspürt, das zu ändern. Wie immer, zuerst eine Vorschau auf

Das Ergebnis

Im Vergleich zu den mitgelieferten können Custom Themes einigermaßen hübsch aussehen. Hier eine Vorschau (m)einer Variante in Anlehnung an Adobe Audition mit dunkler Oberfläche und grünen und violetten Highlights:

 
 

Die Erstellung eines eigenen Themes für Audacity ist nur "so mittel" dokumentiert, vor allem seitdem das Handbuch und die Doku überarbeitet wurden und immer noch werden. Aber mittels viel "Versuch und Irrtum" findet man die richtigen Grafiken, Icons und Farben, die man austauschen möchte. Meine Erkenntnisse gliedern sich so:

Ein Custom Theme?

Um ein Theme zu erstellen, kann man entweder den Code ändern (s. hier) oder – wie ich – den Hinweis

"It is recommended to only use the ImageCache.png to distribute themes, not to author them. This is due to the ImageCache breaking whenever buttons are added or removed from Audacity, which may happen more often in the coming releases."

ignorieren 😆 und ein Design mittels der Grafik "ImageCache.png" erstellen. Dass dieses Vorgehen tatsächlich problembehaftet ist und der "ImageCache" nach Updates der Software oft fehlerhaft ist, wissen diejenigen Menschen (incl. mir) bereits, denen z.B. nach dem Enfernen der Cut/Copy/Paste/Delete-Toolbar (ab Version 3.2.0) und dem Re-Integrieren (ab Version 3.2.3) plötzlich die entsprechenden Buttons fehlen 🙄 – und in dem aus den Dev Resources herunterladbaren ImageCache (Link) fehlen diese Buttons weiterhin…

Einen ImageCache für das Theme "Hell" incl. der Cut-, Copy-, Paste- & Delete-Buttons, kompatibel mit Audacity v.3.3.2 habe ich mal hier abgelegt. Diese Grafik kann man für gewünschte Änderungen als Basis nutzen oder direkt meine modifizierte Version herunterladen:

ImageCache „Hell“ als Grundlage für Änderungen ([slb_exclude]Direktlink[/slb_exclude])

… oder direkt meine Version hier herunterladen ([slb_exclude]Direktlink[/slb_exclude]).


Wenn man nun Audacitiy startet und via > Bearbeiten > Einstellungen > Schnittstelle > Thema ein "Eigenes" einstellt, wird unter Windows – Infos für weitere Betriebssysteme hier – ein Ordner unter C:\Users\<username>\AppData\Roaming\Audacity\Theme\custom\ angelegt. Dort legt man die Grafik ab und startet Audacity einmal neu. (Der Ordner AppData ist standardmäßig ausgeblendet, den kann man aber sichtbar machen.)

Die png-Datei muss bei allen Änderungen ihren Namen "ImageCache.png" behalten!

Ab jetzt kann man in einem beliebigen Grafikprogramm Änderungen an dem ImageCache vornehmen. Audacity muss nun nicht mehr jedes Mal neu gestarte werden, allerdings müssen einmal die Einstellungen bestätigt werden. Das geht am schnellsten mit

  1. [Strg] + [P] (öffnet die Einstellungen, die stehen immer noch auf "Schnittstelle" und das Thema auf "Eigenes") und
  2. [Enter], was dem OK-Button entspricht.

Dann wird die Oberfläche im laufenden Betrieb neu erstellt und zeigt die Änderungen:

So. Dann kanns losgehen:

Abspielbuttons

Recht zentral sind die großen Abspielbuttons ("Transport Toolbar"). Für jeden Button werden vier Hintergründe in zwei Varianten (Windows/Linux vs. MacOC) und jeweis mind. zwei Icons ("active" und "disabled") benötigt. Um sich im ImageCache zurecht zu finden, habe ich diese folgendermaßen durchnummeriert:

  1. Hintergrundgrafiken Windows in den Stati "normal" (1a), "active" (1b), "hover" (1c) und "active hover" (1d),
  2. Hintergrundgrafiken Mac in den Stati "normal" (2a), "active" (2b), "hover" (2c) und "active hover" (2d),
  3. Icon "Pause" in "available" (3a) und "disabled" (3b),
  4. Icon "Play" in "available" (4a) und "disabled" (4b),
  5. Icon "Schleife" in "available" (5a) und "disabled" (5b),
  6. Icon "Play, Markierung überspringen" in "available" (6a) und "disabled" (6b),
  7. Icon "Stop" in "available" (7a) und "disabled" (7b),
  8. Icon "Rewind" in "available" (8a) und "disabled" (8b),
  9. Icon "Fast Forward" in "available" (9a) und "disabled" (9b),
  10. Icon "Aufnahme" in "available" (10a) und "disabled" (10b),
  11. Icon "Aufnahme in neuer Spur" in "available" (11a) und "disabled" (11b),
  12. Icon "Aufnahme im Anschluss" in "available" (12a) und "disabled" (12b).

Diese Nummerierung hilft bei der Ortung, diese Bestandteile findet man mit entsprechenden Nummerierung im ImageCache hier.

Ich habe mich für ein schlichtes, dunkles Design entschieden. Die Photoshop-Nutzer:innen werden die Ebenenstile Kontur, Schatten nach innen, Verlaufsüberlagerung, Schlagschatten erkennen, mit denen der räumliche Eindruck generiert wird. Der Pause-Button ist hier "active", das Stop-Icon ist "disabled", beim "Rewind"-Button sieht man den hover-Effekt:

Die Transport Toolbar im neuen Gewand

Werkzeug- & Bearbeitungsbuttons

Neben den Abspielbuttons spielen die Bearbeitungsbuttons ("Tools Toolbar", Edit Toolbar", "Cut/Copy/Paste Toolbar") und die Pegelanzeigen ("Meter Toolbars") eine zentrale Rolle. Auch hier: Für jeden Button vier Hintergründe in zwei Varianten (eigentlich bescheuert, da hier Mac- und Win-/Lnux-Buttons die gleichen Größen haben 🙄 ) und jeweils zwei Icons. Ausnahme: Tools Toolbar und Meter Toolbars; hier wird nur ein Icon benötigt:

  1. Hintergrundgrafiken Windows in den Stati "normal" (13a), "active" (13b), "hover" (13c) und "active hover" (13d),
  2. Hintergrundgrafiken Mac in den Stati "normal" (14a), "active" (14b), "hover" (14c) und "active hover" (14d),

Tools Toolbar

  1. Icon "Auswahl",
  2. Icon "Hüllkurve",
  3. Icon "Verschieben"
  4. Icon "Zeichnen" und
  5. Icon "Mehrfachwerkzeug",

Meter Toolbars

  1. Icon "Aufnahmepegel" und
  2. Icon "Wiedergabepegel",

Edit Toolbar

  1. Icon "Ganzes Projekt zeigen" (23a. available & 23b. disabled),
  2. Icon "Zoom in" (23a. available & 23b. disabled),
  3. Icon "Zoom out" (23a. available & 23b. disabled),
  4. Icon "Auswahl an Fenster anpassen" (23a. available & 23b. disabled),
  5. Icon "Zoomstufen umschalten" (23a. available & 23b. disabled),
  6. Icon "Audio außerhalb der Auswahl abschneiden" (24a. available & 24b. disabled),
  7. Icon "Auswahl in Stille umwandeln" (25a. available & 25b. disabled),
  8. Icon "Undo" (26a. available & 26b. disabled) und
  9. Icon "Redo" (27a. available & 27b. disabled),

Cut/Copy/Paste Toolbar wieder da seit v3.3.2

  1. Icon "Ausschneiden" (31a. available & 31b. disabled),
  2. Icon "Kpoieren" (32a. available & 32b. disabled),
  3. Icon "Einfügen" (33a. available & 33b. disabled) und
  4. Icon "Löschen" (34a. available & 34b. disabled).

Diese Bestandteile findet man mit entsprechenden Nummerierung im ImageCache hier.

Passend zu den Abspielbuttons sehen die so aus:

Die "neuen" Tools, Edit, Cut/Copy/Paste-Toolbars

Trackbuttons

Die dritte Buttongruppe sind diejenigen der einzelnen Spuren ("Mute", "Solo", "Effekte", etc.); auch hier: Vier Hintergründe in zwei Varianten, diesmal ohne Icons, da sie nur mit Text beschriftet sind:

  1. Hintergrundgrafiken Windows in den Stati "normal" (35a), "active" (35b), "hover" (35c) und "active hover" (35d) und
  2. Hintergrundgrafiken Mac in den Stati "normal" (36a), "active" (36b), "hover" (36c) und "active hover" (36d).

Diese Bestandteile findet man mit entsprechenden Nummerierung im ImageCache hier.

Im meinen Theme sehen die so aus:

Hintergrundgrafiken der Track Buttons

Weitere GUI-Elemente und das Effekte-Panel

  1. Schieberegler ("Slider", horizontal und vertikal, je in standard und hover) für Wiedergabe- und Aufnahmepegel, Panorama- und Spurverstärkung,

    Schieberegler für Pegel (links, oben mit hover), Spurlautstärke (rechts oben) und Panorama (r. u., hover)

  2. Abspielköpfe ("Playheads") für Wiedergabe und Aufnahme, jeweils beweglich und angepinnt,

    Wiedergabe- (grün) und Aufnahmeköpfe (rot), beweglich (Dreieck) und angepinnt (Raute).

  3. Marker für Textspuren (insg. 12 verschiedene Icons, bei denen mir die Hover-Effekte nur begrenzt klar sind 🙄 – daher habe ich die Darstellung auf 4 verschiedene vereinfacht),

    Marker für Textspuren, Punkt 1 mit zentralem, Punkt 2 mit linkem hover.

  4. "Clock"- und "Lock"-Icons für "Spuren synchron halten"-Funktion,

    Uhren- (Track, links) und Schloss-Icon (Clip, rechts)

  5. "Scrub & Seek"-Icons,

    Suchleisten-Buttons, rechts active (grüne Kontur)

  6. Icons für das Echtzeit-Effekte-Fenster

    Power-Icons für Effekte (Phaser: aus), Buttons für Effektnamen (Phaser mit hover) und DropDowns (oberer mit hover).

  7. und verschiedene Setup- und App-Elemente: Audiogeräte-Einrichtung (43a), Share-Button (43b), Logos (43c), Zeitfilter (43d) und "mehr" (43e).

    Buttons für die Audioeinrichtung (links), Share-Button (rechts)

Diese Bestandteile findet man mit entsprechenden Nummerierung im ImageCache hier.

Nicht zuordbare Icons und unveränderliche Cursor

Neben den oben genannten beinhaltet der ImageCache noch eine Reihe weiterer Icons und eine Palette von Cursors. Allerdings sind mir die Icons nie untergekommen, und Änderungen an den Cursors sind wirkungsfrei 🙁 Daher lasse ich diese außen vor. Wenn dazu jemand Details weiß, bitte gerne berichten!

Die (ziemlich chaotische) Farbpalette

Alles weitere des Themes basiert auf verschiedenen Farben, die erstaunlich besch%&#$!n organisiert sind 😡 … Denn in dem ImageCache sind neben den GUI-Elementen noch 110 (in Worten EINHUNDERTZEHN!) Farben hinterlegt, von denen viele, aner nicht alle 🙄 an der ein- oder anderen Stelle in der Audacity-Oberfläche vorkommen. Einen Teil konnte ich auch durch viel Versuch und Irrtum nicht orten (= pink), die anderen sind hier mit Zeile (Z1 – Z4) und Spalte(S01 – P36) aufgelistet:

Die Farbpalette des ImageCache mit Zeilen und Spaltenangaben. Pink sind für mich nicht zuordbaren Farben…

Hintergrundfarben

Zuerst einmal die wichtigsten Hintergrundfarben für die Arbeitsfläche und die Menüs, außerdem Menü-Highlights:

Die fünf wichtigsten Hintergrundfarben

  • Z3 / S  8: Fensterhintergrund
  • Z2 / S29: Menü, Hintergrund
  • Z2 / S28: Menü, Griff, Highlight
  • Z2 / S30: Menü, Griff, Schatten
  • Z2 / S32: Menü, Griff hover
Text- und Counterfarben

Nachdem der Hintergrund eingefärbt ist, sollte man sich um die Textfarben kümmern:

Text- und Counterfarben

  • Z1 / S12: Textfarbe (Menü, Lineal, Track)
  • Z1 / S13: Textfarbe von Textspuren
  • Z3 / S34: Textfarbe der Clip Header
  • Z2 / S11: Textfarbe der Zeitangaben
  • Z2 / S12: Hintergrundfarbe Zeitangabe
  • Z2 / S13: Textfarbe der Zeitangaben markiert
  • Z2 / S14: Hintergrundfarbe Zeitangabe markiert
  • Z3 / S13: Hintergrundfarbe Zeiteinheiten
Spurfarben

Nachdem Hintergründe und Text halbwegs zusammenpassen, geht es nun um die Farben der Spuren:

Farben für Spuren, Spurinfos und Lineal

  • Z1 /   S1: Leere Spur, Hintergrund
  • Z3 /   S4: Leere Spur, Hintergrund, markiert
  • Z2 / S26: Spurinfo, Hintergrund (und: Lineal Hintergrund)
  • Z2 / S27: Spurinfo, Hintergrund, markiert
  • Z2 / S22: Spur, ausgewählt, Kontur innen
  • Z2 / S23: Spur, ausgewählt, Kontur mitte
  • Z2 / S24: Spur, ausgewählt, Kontur außen
Clipfarben

In diesen Spuren befinden sich die einzelnen Audioclips; diese werden folgendermaßen gefärbt:

Farben für Cliphintergrund, -rahmen und -header

  • Z1 /   S2: Clip, Hintergrund (auch: Zeitpur, Hintergrund)
  • Z1 /   S3: Clip, Hintergrund, ausgewählt
  • Z1 / S29: Clip, Rahmenfarbe
  • Z1 / S32: Clip, Rahmenfarbe beim Bewegen
  • Z1 / S30: Clip, Header Hintergrund
  • Z1 / S31: Clip, Header Hintergrund active
  • Z1 / S35: Clip, Header, Hintergrund für ausgewählten Text
Sample- und Waveformfarben

In diesen Clips werden die Audiosamples visualisiert und optional Übersteuerungen angezeigt:

Farben für Samples, Zeit- und Hüllkurve

  • Z1 /   S8: Sample-Farbe
  • Z1 /   S4: Sample-Farbe außen
  • Z1 /   S6: Einzelnes Sample
  • Z1 /   S7: Sample-Farbe, muted
  • Z1 /   S9: Sample-Farbe außen, muted
  • Z2 / S34: Übersteuerung
  • Z2 / S35: Übersteuerung, muted
  • Z1 / S35: Zeitspurlinie und Hüllkurve
Textspur-Farben

Eine spezielle Form der Spuren sind Textspuren. Ihre Farben sind diese:

Farben der Textspuren

  • Z2 / S17: Textspur, Hintergrund
  • Z2 / S18: Textspur, Hintergrund, markiert
  • Z2 / S21: Textspur, Vertikale Linien, Kontur der Horizontalen Linien
  • Z2 / S15: Textspur, Horizontale Linie (und Texthintergrund)
  • Z2 / S16: Textspur, Vertikale Linie hover (und Texthintergrund bei Textauswahl)
Lineal, Schieberegler und Pegel

Diese Kategorie von Farben färben die Lineale, Schieberegler und Pegelanzeigen.

Farben von Linealen, Schiebereglern und Pegelanzeigen

  • Z1 / S29: Lineal, Schleifenhintergrund
    Info: Das Lineal und die Schleife werden bei Überlappung mit einer Markierung jeweils mit ca. 20% Deckkraft der Schriftfarbe  (Z1 / S12, s.o.) überlagert!
  • Z3 / S12: Scrubleiste Hintergrund
  • Z3 /   S6: Schieberegler Linienfarbe
  • Z3 /   S7: Schieberegler Teilstrich Highlight
  • Z3 /   S5: Schieberegler Teilstrich Schatten
  • Z1 / S14: Ein- und Ausgangspegel Peak
  • Z1 / S20: Eingangspegel übersteuert
  • Z1 / S26: Ausgangspegel übersteuert
Cursor- und Abspielköpfe

Es gibt vier Farben für die vertikalen Linien für Cursor, Abspiel- und Aufnahmepositon und die "Einrasthilfe" beim Verschieben von Clips.

Vier Arten vertikaler Linien mit unterschiedlichen Farben

  • Z2 / S  2: Cursorlinie
  • Z2 / S  4: Abspielkopflinie
  • Z2 / S  3: Aufnahmekopflinie
  • Z2 / S25: Linie "Einrasthilfe"
Farben des Echtzeit-Effekte-Fensters

Für das Effekte-Fenster stehen – neben den Effekte-Buttons – nur dire Farben zur Verfügung:

Farben für das Effekte-Rack

  • Z4 / S  1: Effekthintergrund
  • Z4 / S  2: Effektliste Trennlinie
  • Z3 / S36: Drag and Drop-Hinweis
Diagramme und Graphen

Ein paar Farben für die Darstellung in zusätzlichen Effektefenstern konnte ich noch identifizieren:

Farben für Diagramme und Graphen

  • Z3 / S10: Achsenbeschriftung (z.B. bei > Analyse > Spektrum zeichnen)
  • Z1 / S34: FFT-Spektrum (z.B. bei > Analyse > Spektrum zeichnen)
  • Z1 / S33: Tonänderungsanalyse (z.B. bei > Analyse > Spektrum zeichnen, Korrelationsdarstellung)
  • Z1 / S31: Graphen, Linie (z.B. beim Filterkurve-Equalizer)
  • Z1 / S32: Graphen, Bezier-Linie (z.B. beim Filterkurve-Equalizer)
  • Z1 / S11: "Über"-Fenster, Hintergrundfarbe
Midi-Tracks

Und dann habe ich noch zwei Farben für Midi-Tracks gefunden:

  • Z3 / S16: Midi "Zebra", zweite Hintergrundfarbe
  • Z3 / S17: Midi horizontale Linien
Der Rest: Unbekannte (nicht genutzte?) Farben

Der Rest der Farbpalette ist für mich nicht zuzuordnen. Ich glaube ein Teil davon ist einfach veraltet und/oder wird nicht mehr genutzt. Es gibt z.B. diverse Farben, die in der HTML-Map z.B. "Spectro1 – 5" benannt sind. In der Spektrogramm-Ansicht werden diese aber nicht mehr genutzt. Wenn noch jemand weiß, welche der nicht zugeordneten Farben wofür zuständig ist, bitte gerne in die Kommentare 🙂

Ein Photoshop-Template für eigene Themes

Ich habe mein Theme in Photoshop gebastelt (ja, ich weiß, keine FOSS, aber die alten Gewohnheiten 🙄 …). Es hat leider 246 Ebenen in 37 Gruppen (!) und man benötigt evtl. etwas zeit, sich darin zurecht zu finden. Ich hoffe, die Ordner- und Ebenennamen sind halbwegs selbsterklärend, ansonsten gerne nachfragen 😉

Die Photoshop-Datei kann man hier herunterladen: Download AudacityTheme.psd
Erstellt mit PS v24.5.0 (20230512.r.500), hoffentlich kompatibel mit Euren Versionen.

So weit meine Erkenntnisse zur Erstellung von Audacity Themes.
Vielleicht hilft es ja dem ein- oder der anderen.

p.s.: Hinweise auf hübsche (fremde oder selbst erstellte) Designs nehe ich natürlich gerne entgegen.

Hübsche(re) Audacity Themes“ von Martin Smaxwil ist incl. aller Abbildungen, Videos und dem Photoshop-Template unter einer CC BY 4.0-Lizenz veröffentlicht, darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier. Die genutzte Musik in den Videos ist "Dramatic Hip Hop, 24 seconds" von DMD_Production, Lizenz: Pixabay Lizenz.

]]>
https://medienmarmela.de/audacity-themes/feed/ 0
CSS-Transformationen https://medienmarmela.de/css-transform/ https://medienmarmela.de/css-transform/#comments Sun, 02 Apr 2023 12:51:36 +0000 https://medienmarmela.de/?p=3342 Worum gehts?

Ich stolpere immer wieder über 3D-Transformationen von HTML-Elementen, die via CSS mittels transform realisiert sind. So richtig habe ich nie verstanden, wie das genau funktioniert, also ist es Zeit, damit mal etwas herumzuspielen. Damit kann man z.B. "Wendekarten" basteln …

hover / tap cards!
vorne
hinten
vorne
hinten
 
vorne
hinten
vorne
hinten

… oder sogar einen interaktiven 3D-Würfel:





front
right
back
left
top
bottom
 

Auf dieser Seite habe ich mal alles zusammengeschrieben, was ich über 2D- und 3D-CSS-Transformationen weiß. Die Bastelanleitungen für die Beispiele oben findet man weiter unten.

2D-Transformationen mit CSS

Mit der CSS-Eigenschaft transform kann man Objekte in 2D (und 3D, s.u.) z.B. drehen (rotate), verschieben (translate), skalieren (scale) und verzerren (skew). Das Original-Element ist zur Veranschaulichung halbtransparent im Hintergrund zu sehen, sobald die (hier als CSS-Animation – s. hier und hier – umgesetzte) Transformation gestartet ist:

on 

 

off

translateX(1.5rem)

 
 

translateY(1.5rem)

 
 

translate(1.5rem,1.5rem)

 
 

scale(.8)

 
 

skewX(15deg)

 
 

skewY(15deg)

 
 

skew(15deg,15deg)

 
 

rotate(15deg)

 
 

So weit, so gut. Alle 2D-Transformationen vertragen auch negative Werte. Während bei vielen CSS-Eigenschaften, vgl. margin, top, left u.ä., positive Werte nach rechts und unten, negative nach links und oben wirken, ist das für 2D-Transformationen nicht generell richtig. Vielmehr hängt die Transformation am "Ursprung", engl. origin.

Dreh- und Angelpunkt: transform-origin

Mittels transform-origin kann für das jeweils transformierte Element derjenige Punkt festgelegt werden, der den Mittelpunkt der Transformation bilden soll. Nutzenden von Grafik- und Videosoftware mit entsprechenden Transformationsoptionen wird das Konzept als "Ankerpunkt" geläufig sein. Bei transform:rotate() wird das besonders gut sichtbar, es funktioniert aber auch bei allen anderen Transformationen. Zur Verdeutlichung sind die "Origins" rot gekennzeichnet:

on 

 

off

transform-origin:top left;

 
 
 

transform-origin:center;

 
 
 

transform-origin:3rem 3rem;

 
 
 

transform-origin:bottom right;

 
 
 

Mehrere Transformationen und "die Matrix"

Wenn man mehrere Transformationen kombinieren möchte, werden diese ohne Trennzeichen (aber mit Leerzeichen 😉 ) hintereinander weggeschrieben, z.B.
transform: scale(0.75) translate(-2rem,3rem) skew(5deg,-30deg); 

Dabei ist die Reihenfolge wichtig, da sie nacheinander (und von hinten nach vorne!) abgearbeitet werden.

Mit transform:matrix(); steht eine Art shorthand-Code für mehrere gleichzeitigeTransformationen zur Verfügung:
matrix(scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY());

Hier ein paar Beispiele für Mehrfach- und Matrix-Transformationen:

  • Die Beispiele "rot" und "gelb" haben die gleichen Transformationen, diese werden aber in abweichender Reihenfolge gerendert, wodurch die Ergebnisse voneinander abweichen.
  • Die Beispiele "blau" und "grün" sind die Versuche, die Transformationen von "rot" und "gelb" in einer Matrix-Transformation nachzuahmen. Eine exakte Umrechnung von einzelnen Transformationswerten in die Matrix-Werte kann ich nicht nachvollziehen, aber hier, hier und hier findet der/die interessierte Leser:in evtl. Ansätze.

scale(0.75)
translate(-2rem,3rem)
skew(5deg,-30deg);

 
 

matrix(0.75, -0.4, 0.05, 0.75, -23, 40);

 
 
 

skew(5deg,-30deg)
translate(-2rem,3rem)
scale(0.75);

 
 

matrix(0.75, -0.4, 0.05, 0.75, -27, 68);

 
 

Transformieren in 3D

Wie bei allen anderen CSS-Koordinaten auch, ist die X-Achse horizontal, die Y-Achse vertikal und die Z-Achse für die Tiefe zuständig. Sich das zu vergegenwärtigen, scheint sinnvoll, da sonst leicht Verwirrung aufkommt. Positive Werte verschieben nach rechts (+X), unten (+Y) und vorne (+Z), negative nach links (-X), oben (-Y) und hinten (-Z):

Schematische Darstellung des 3D-Raums für CSS-Transformationen.

Die 2D-Transformationen scaleX(), scaleY() und scale(x,y) sowie translateX(), translateY() und transtale(x,y) meinen also Verzerrungen bzw. Verschiebungen auf der X- und Y-Achse, davon leicht kontra-intuitiv abweichend meint eine 2D-Transformation mittels rotate() also ein Drehung um die Z-Achse, also eigentlich rotateZ().

Im 3D-Raum stehen darüber hinaus die transform-Werte

  • rotateX,
  • rotateY und
  • rotate3D(x,y,z,deg)

zur Verfügung. Der Vollständigkeit halber müssen hier auch translateZ() und scaleZ() erwähnt werden, diese erreichen aber den gleichen (Vergößerungs-/Verkleinerungs-) Effekt wie scale().

Perspektive einstellen (via perspective)

Um die 3D-Perspektive sicherzustellen muss dem Elterncontainer die Eigenschaft perspective mit einem Abstandsmaß (px, em, rem, o.ä.) mitgegeben werden. Die Perspektive meint dabei so etwas wie den Abstand der Betrachter:innen zu dem transformiertem Objekt: Je näher (= kleinerer perspective-Wert), desto krasser die Transformation:

on 

 

off

Eltern-Eigenschaft perspective:    

rotateX(25deg)

 
 

rotateY(25deg)

 
 

rotateZ(25deg)

 
 

rotate3d(1,.8,.5,80deg)

 
 

Perspektive von Kindern (via transform:perspective();)

Um das Ganze noch ein wenig weiter zu verkomplizieren, gibt es neben der Eltern-Eigenschaft perspective: value; noch eine Kind-Eigenschaft transform: perspective(value);

Der Unterschied zwischen den beiden ersten Beispielen verdeutlicht einen zentralen Unterschied. Das Kind-Element ist in beiden Fällen um 15° um die Y-Achse gedreht. Im "roten" Beispiel ist die Perspektive auf Ebene des Kindes definiert. Die Betrachtendenposition ist "von vorne mittig". Wenn die Perspektive dagegen "am Elternteil hängt" ("blaues" Beispiel), schlagen die Maße des Elternteils durch: Die Höhe des (grauen) Eltern-Containers bewirkt, dass die Blickrichtung eher "von vorne oben" wirkt.

Das dritte Beispiel soll verdeutlichen, dass die Perspektive auf Kind-Ebene für jedes Kind individuell eingestellt werden kann. Obwohl beide Kinder um jew. 30° gedreht sind, haben Sie unterschiedliche "Betrachtungswinkel".

Und im vierten Beispiel zeigt sich, wie sich die Perspektiven von Eltern und Kindern "addieren": Beide Flächen haben die gleiche Drehung, bei Kind 1 wirkt diese aber krasser, da die Eltern- und die Kindperspektive beide greifen, bei Kind 2 nur die Elternperspektive:

Kind:
transform: perspective(5rem) rotateY(15deg);

 
 

Eltern:
perspective:5rem;
Kind:
transform: rotateY(15deg);

 
 

Kind 1 (rot):
transform: perspective(25rem) rotateY(-30deg);
Kind 2 (gelb):
transform: perspective(7.5rem) rotateY(-30deg);

 
 

Eltern:
perspective: 7.5rem;
Kind 1 (rot):
transform: perspective(5rem) rotateY(15deg);
Kind 2 (grün):
transform: rotateY(15deg);

 
 

Verhältnis mehrerer 3D-Elemente (via transform-style)

Um das 3D-Verhalten mehrerer Objekte zueinander innerhalb eines 3D-Raumes zu gestalten, kann man der transform-style-Eigenschaft des Eltern-Elements die Werte flat oder preserve3d mitgeben:

on 

 

off

transform-style:flat;

 

transform-style:preserve-3d;

 
 

transform-style:flat;

 

transform-style:preserve-3d;

 

Der Wert flat (Standard) sorgt dafür, dass Kind-Objekte nicht in eigenem 3D-Raum dargestellt werden, sondern platt im 3D-Raum des Elternelementes. Der Wert preserve-3d dagegen rendert die Objekte innerhalb eines jew. eigenen 3D-Raumes, wobei die Überschneidungen, Verdeckungen und Verschachtelungen mehrerer Elemente entsprechend berücksichtigt werden.

Bei obigen Beispielen scheint preserve-3d immer das Mittel der Wahl zu sein, bei nicht beweglichen Objekten hat flat aber durchaus eine Daseinsberechtigung. Es kommt nämlich vor allem auf die Anordnung und Eigenschaften der Elemente im HTML-Code an. Bei den obigen (animierten) Beispielen ist die farbige Fläche Kind der/des grauen Rahmens/Fläche. Der transform-style hängt am Elternelement:

HTML

<div class="container">
  <div class="grau" style="transform-style:[...];">
    <div class="bunt"></div>
  </div>
</div>

Bei den hier drunter folgenden nicht-animierten Beispielen dagegen sind die grauen Rahmen/Flächen und die farbigen Flächen gleichberechtigte Kinder eines Containers, der den transform-style mitbringt:

HTML

<div class="container"style="transform-style:[...];">
  <div class="grau"></div>
  <div class="bunt"></div>
</div>

Dass bei den unteren "blauen" und "grünen" Beispielen die Flächen ab der Mitte "abgeschnitten" werden, liegt u.a. an der hellgrauen Füllung des Hintergrundes! Denn die transformierten Quadrate (alle mit transform:rotateY(); und transform-origin:center; transformiert) schneiden mittig die Hintergrundfläche. Wenn die Füllung ausgeschaltet wird, sieht man die gedrehten Flächen/Rahmen komplett.

Eltern-Eigenschaft background:  

transform-style:flat;

 
 

transform-style:preserve-3d;

 
 
 

transform-style:flat;

 
 

transform-style:preserve-3d;

 
 

Also: Beim Aufsetzen eines 3D-Raumes sollte man immer etwas herumexperimentieren und

  • die Reihenfolge des stacking context bzw. die Reihenfolge der Elemente im HTML-Code sowie
  • die Transparenz bzw. Füllung aller beteiligten Elemente berücksichtigen als auch
  • das "Träger-Element" des transform-style sinnvoll auswählen.

Bei wenig komplexen 3D-Transformationen und/oder farbigen Hintergründen kann dabei transform-style:flat; sinnvoll sein, bei animierten Elementen oder komplexen 3D-Szenen sollte transform-style:preserve-3d; gewählt und ans "richtige" Element gehängt werden.

Sichtbarkeit von verdeckten (Rück-) Seiten (via backface-visibility)

Sowohl bei 2D- als auch bei 3D-Objekten kann man den stacking context und die Einhaltung der Elemente-Reihenfolge mit der Eigenschaft backface-visibility:hidden; durchbrechen. Das kann sinnvoll sein, wenn z.B. bei den "Wendekarten" Vorder- und Rückseite zwei gleichwertige HTML-Elemente sind:

HTML

<div class="card">
  <div class="cardfront"></div>
  <div class="cardback"></div>
</div>

Die Reihenfolge der HTML-Elemente bedingt, dass die Rückseite immer die Vorderseite verdecken würde, da die HTML-Elemente in der Reihenfolge gerendert werden, in der sie im Quelltext stehen. backface-visibility:hidden;  kann hier die räumliche Anordnung höher als die Reihenfolge oder den stacking context der HTML-Elemente priorisieren.

In dem Würfel-Beispiel wird durch backspace-visibility:hidden; sogar die Halbtransparenz der Würfelseiten ausgehebelt und die verdeckten, im 3D-Raum positionierten Flächen werden unsichtbar:

on 

 

off

backface-visibility:visible;

front
back

backface-visibility:hidden;

front
back

backface-visibility:visible;

front
right
left
back
top
bottom

backface-visibility:hidden;

front
right
left
back
top
bottom

Nach diesen 4km langen Grundlagen gibts jetzt endlich die Bastelanleitungen:

Bastelanleitung für die Wendekarten

Das HTML

In einem <div class="container"> liegt für jede Karte ein <div class="card"> und darin zwei weitere divs (.cardfront und .cardback):

HTML

<div class="container">
  <div class="card card1">
    <div class="cardfront">vorne</div>
    <div class="cardback">hinten</div>
  </div>
  <div class="card card2">
    <div class="cardfront">vorne</div>
    <div class="cardback">hinten</div>
  </div>
  <div class="card card3">
    <div class="cardfront">vorne</div>
    <div class="cardback">hinten</div>
  </div>
  <div class="card card4">
    <div class="cardfront">vorne</div>
    <div class="cardback">hinten</div>
  </div>
</div>

Das CSS

Den Container habe ich als Flexbox umgesetzt, um die Karten einfach und regelmäßig positionieren zu können. Eine float-Umsetzung ist sicherlich ebenfalls möglich. Die .card-Elemente sind relativ, die Kartenseiten .cardfront und .cardback absolut positioniert. So kann ich die Kartengröße über width und height von .card festlegen und die Größen für die Kartenseiten mit jew. width und height:100%; und top:0; und left:0; einpassen. Die .cards sind auch die Träger-Elemente für transform-style:preserve-3d; und perspective:30rem;.

Auch die Kartenseiten sind Flexboxes, aber lediglich, um mit flex-direction:column; den Text vertikal mittig zu positionieren. An die Kartenseiten muss die backface-visibility:hidden; gehängt werden.

.cardfront wird in der Ausgangslage um 0° um die Y-Achse gedreht, .cardback um 180°. Im .card:hover wird .cardfront von 0° auf -180° und .cardback von 180° auf 0° gedreht. Damit das einigermaßen geschmeidig aussieht, erhalten die .cardfronts und .cardbacks eine transition (s. auch hier und hier) für die :hover-Transformation: transform .5s ease;.

Der Rest sind paddings, margins und Farbgebung.

CSS

.container {
  display: flex;
  flex-flow: row wrap;
  justify-content: space-around;
  padding: 2rem 1rem;
  align-items: center;
  max-width: calc(100% - 2rem);
  background: #fff;
  margin: 1rem auto;
  box-shadow: 0 0 9rem #888 inset;
  min-height: 12rem;
}
.card {
  position: relative;
  width: 12rem;
  height: 12rem;
  margin: 0 1rem 1rem;
  transform-origin: center;
  transform-style: preserve-3d;
  perspective: 30rem;
}
.cardfront, .cardback {
  position: absolute;
  width: 100%;
  height: 100%;
  top:0;
  left:0;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transition: transform .5s ease;
  backface-visibility: hidden;
}
.cardfront {
  transform: rotateY(0deg);
}
.cardback {
  transform:rotateY(180deg);
}
.card:hover .cardfront {
  transform: rotateY(-180deg);
}
.card:hover .cardback {
  transform: rotateY(0deg);
}
.card1 .cardfront {background: #bf304a;}
.card1 .cardback  {background: #8c182b;}
.card2 .cardfront {background: #30a7bf;}
.card2 .cardback  {background: #18798c;}
.card3 .cardfront {background: #f29524;}
.card3 .cardback  {background: #b36a12;}
.card4 .cardfront {background: #86bf30;}
.card4 .cardback  {background: #5e8c18;}

Das Ergebnis

front
back
front
back
front
back
front
back

Eine Standalone-Version der Karten mit dem gesamten CSS in einer HTML-Datei gibt es hier. Bitte gerne herunterladen und für die eigenen Bedürfnisse anpassen 😉

Bauanleitung für 3D-Würfel

Das HTML

Basis ist ein Container-div (.container) mit Bühne (.stage) und darin befindlichem Würfel (.cube). Theoretisch wäre auch nur ein Elterncontainer (.container oder .stage) möglich, aber damit Hintergrundflächen und -farben die 3D-Szene nicht durchschneiden (s. Beispiel oben), nutze ich .container für Positionierung und Hintergrundgestaltung und .stage für die 3D-Szene. divs kosten nichts 😉

Der Würfel enthält sechs .cubefaces, die jew. als zweite Klasse .front, .right, .left, etc. haben. So kann man generelle CSS-Definitionen an .cubeface zuweisen und seitenspezifische CSS-Definitionen an die "Seiten-Klassen".

HTML

<div class="container">
  <div class="stage">
    <div class="cube">
      <div class="cubeface front">front</div>
      <div class="cubeface right">right</div>
      <div class="cubeface left">left</div>
      <div class="cubeface back">back</div>
      <div class="cubeface top">top</div>
      <div class="cubeface bottom">bottom</div>
    </div>
  </div>
</div>

Das CSS

Der Würfel soll 12rem*12rem groß werden, der .container erhält ein padding von 3rem und daher eine min-width und min-height von je 18rem (12rem + (2 * 3rem) = 18rem). Die max-width sollte daher auf calc(100% - 6rem) gesetzt werden. Um den Würfel zu zentrieren, ist der Container erneut eine Flexbox. Er hat eine hellgraue Hintergrundfarbe und einen grauen Schatten nach innen:

CSS

.container {
  min-height: 18rem;
  min-width: 18rem;

  margin: 3rem auto;
  padding: 3rem;
  max-width: calc(100% - 6rem);

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  background: #fafafa;
  box-shadow: 0 0px 9rem #888 inset;
}

Die 3D-Bühne .stage erhält die perspective-Angabe von 25rem (s. oben). Der Würfel .cube bekommt die gewünschten Maße von 12 * 12rem und wird relative positioniert, außerdem wird hier der transform-style:preserve-3d; angehängt (s. oben)..

Die .cubefaces werden innerhalb des Würfel-divs absolute positioniert und mit width:100%;, height:100%;, top:0; und left:0; in das .cube-div eingepasst. Die Flexbox der Würfelseite ist lediglich zur mittigen Positionierung des Textes notwendig, auch font-size und color sind nur bei Beschriftung notwendig.

Zur leichteren Positionierung werden die Würfelseiten mit opacity:.75; halb-, eigentlich vierteltransparent und erhalten zum Schluss eine Farbe:

CSS

.stage {
  perspective: 25rem;
}
.cube {
  width: 12rem;
  height: 12rem;
  position: relative;
  transform-style: preserve-3d;
  /*transform: rotate3d(-.65, .9, -.15, 40deg);*/
}
.cubeface {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  opacity: .75;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: #fff;
  font-size: 1.75rem;
}
.front  {background: #bf304a;}
.right  {background: #30a3bf;}
.left   {background: #f29524;}
.back   {background: #88bf87;}
.top    {background: #aaaaaa;}
.bottom {background: #cccccc;}

Hier mal ein Zwischenergebnis. Zur Verdeutlichung habe ich die Würfelseiten leicht gegeneinander verschoben und den Würfel animiert:

front
right
left
back
top
bottom

Man sieht, dass die sechs Seiten einfach nacheinander weg, aufgrund ihrer absolute-Positionierung deckungsgleich gerendert werden. Als nächstes also die

Platzierung der Würfelseiten

Alle Seiten werden um eine "halbe Würfelbreite" (=6rem;) verschoben und entsprechend gedreht. Um die Platzierung der Würfelseiten besser nachvollziehen zu können, habe ich mal ein interaktives Seitenschubs-Dingsi gebastelt:

front
right
left
back
top
bottom
CSS: .front  {transform: rotateY(0deg)   translateZ(6rem);}.right  {transform: rotateY(90deg)  translateZ(6rem);}.left   {transform: rotateY(-90deg) translateZ(6rem);}.back   {transform: rotateY(180deg) translateZ(6rem);}.top    {transform: rotateX(90deg)  translateZ(6rem);}.bottom {transform: rotateX(-90deg) translateZ(6rem);}

Achtung:
Auch hier ist die Reihenfolge der Mehrfach-Transformationen zu beachten: Ich nehme immer erst die Drehung und dann die Verschiebung vor. Der Browser rendert die Transformationen aber von hinten nach vorne, berechnet also erst die Verschiebung und dann die Drehung. Wenn die Transformationen in anderer Reihenfolge erfolgen, müssen sie entsprechend angepasst werden (s. oben)!

Nachdem wir die entsprechenden Transformationen der Seiten herausgefunden haben, ergeben sich also folgende – hier mit den Farben angereicherten – Styles für die Würfelseiten:

CSS

.front  {background: #bf304a; transform: rotate(0deg)    translateZ(6rem);}
.right  {background: #30a3bf; transform: rotateY(90deg)  translateZ(6rem);}
.left   {background: #f29524; transform: rotateY(-90deg) translateZ(6rem);}
.back   {background: #88bf87; transform: rotateY(180deg) translateZ(6rem);}
.top    {background: #aaaaaa; transform: rotateX(90deg)  translateZ(6rem);}
.bottom {background: #cccaaa; transform: rotateX(-90deg) translateZ(6rem);}

Das Ergebnis

So sieht er nun aus, der 3D-Würfel. Zum Pausieren der Animation bitte hovern oder antippen:

front
right
left
back
top
bottom

Man kann .cube jetzt also drehen (mittels rotateX();, rotateY();, rotateZ(); oder rotate3d();) oder animieren (bei Interesse s. hier oder hier), dauerhaft oder mit einem Trigger, z.B. :hover oder via JavaScript.

Eine Standalone-Version des animierten Würfels mit dem gesamten CSS in einer HTML-Datei gibt es hier. Bitte gerne herunterladen und für die eigenen Bedürfnisse anpassen 😉

 

So weit meine Erkenntnisse zu CSS-Transformationen 😉
Vielleicht hilft das.

Fragen, Fehler und Optimierungsvorschläge gerne in die Kommentare – Danke!

CSS-Transformationen“ von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/css-transform/feed/ 7
Eine Farbwähler-Bastelanleitung https://medienmarmela.de/colorpicker/ https://medienmarmela.de/colorpicker/#respond Tue, 07 Jun 2022 21:25:00 +0000 https://medienmarmela.de/?p=3071 Natürlich gibt es bereits mehrere Spezillionen von Online-Farbwählern, z.B. hier, hier oder hier. Und auch entspr. jQuery-Plugins. Aber darum geht es ja gar nicht 🙂 sondern um die Bastelarbeit – und die Einsicht, dass man mit rudimentären Kenntnissen von HTML, CSS und JS lustige und praktische Web-Apps bauen kann.

1. Worum gehts?

Um einen webbasierten, interaktiven Farbwähler, der die Farbwerte in verschiedenen Farbräumen & -modellen ausgibt. Zuerst das Ergebnis:

 
 
HSB:
hue: 0°,
saturation: 0%,
brightness: 100%
HSL:
hue: 0°,
saturation: 100%,
brightness: 100%
RGB:
red: 0,
green: 0,
blue: 0
HEX: #ffffff
 

Einsatzmöglichkeiten sind z.B. UIs, in denen Benutzende Farben auswählen können – oder einfach ein kleines Nachschlage- und Übersetzungstool für Farbwerte.

Kleine Einschränkungen: Da die Farbwerte auf Basis von Koordinaten beweglicher UI-Elemente berechnet werden, kann es zu Rundungsfehlern und kleinen Abweichungen von anderen Farbwählern kommen. Es gibt bisher nur eine rudimentäre responsive Anpassung (von dieser Seite und dem Color Picker) an die Bildschirmgröße und es ist (noch) keine manuelle Eingabe von Farbwerten möglich. Aber für die v2 müssen ja auch noch Features über bleiben…

2. Die Bestandteile

Der Color Picker hat – genauso wie z.B. der von Photoshop oder anderen Umgebungen – zwei Interaktionsmöglichkeiten. Die eine ist das "große" Farbfeld, in dem die Helligkeit und Sättigung durch Verschieben eines "Kringels" eingestellt werden kann. Die andere ist ein Schieberegler für den Farbwert. In Kombination von Farbwert, Helligkeit und Sättigung ergibt sich die endgültige Farbe. Dem liegt das gängige HSV-Modell zur Farbbeschreibung zu Grunde. Ohne hier zu tief in die Welt der Farbräume und -modelle abzutauchen, eine kurze Auffrischung: Farbwert-Sättigung-Hellwert-Farbräume (HSV für hue, saturation und value; mit entweder brightness – dann HSB – oder lightness – dann HSL – als Helligkeitswert) sind trotz komplexer Theorie recht einfach in der Handhabung und lassen sich im Webdesign via CSS als CSS-Wert hsl([hue],[saturation]%,[lightness]%) für Farb-Parameter nutzen, z.B. als background-color:hsl(349,59%,46%);. Neben HSV-Modellen ist vor allem das subtraktive Farbmischmodell RGB (Mischung von red, green und blue) oder das additive Mischmodell wie CMYK (Mischung von cyan, magenta, yellow und schwarz bzw. key) verbreitet. Letzteres wird vor allem im Druck angewandt und ist für leuchtende Medien wie Bildschirme eher ungeeignet, wird daher hier vernachlässigt. Die RGB-Angabe ist allerdings in CSS als rgb([rotwert],[grünwert],[blauwert]) als Eigenschaft nutzbar – z.B. background-color:rgb(191,48,72);. Eine Variante der RGB-Codierung ist die Darstellung der drei zu mischenden Farbwerte in hexadezimaler Darstellung (HEX) als #RRGGBB, z.B. als background-color:#bf3048;. Alle drei Beispiele, background-color:hsl(349,59%,46%);, background-color:rgb(191,48,72); und background-color:#bf3048; beschreiben übrigens die gleiche Farbe, nämlich "medienmarmela.de-rot" 🙂 Der Color Picker soll also die eingestellte Farbe in HSB, HSL, RGB und HEX ausgeben. Eine Eingabe numerischer Farbwerte und Rückübersetzung in Farbe und Position des Color Picker ist "geplant".

2.1 Das Farbfeld für Helligkeit und Sättigung

Um Helligkeit und Sättigung einer Grundfarbe zu visualisieren, liegen im <div id="colorpicker"> drei verschachtelte <divs> übereinander: Eins mit der (über den Schieberegler einstellbaren, s.u.) Grundfarbe (#basecolor), ein weiteres mit einem Sättigungsverlauf (#saturation, mit einem linear-gradient von links, weiß, nach rechts, transparent) und ein drittes mit einem Helligkeitsverlauf (#brightness, ebenfalls mit einem linear-gradient von unten, schwarz, nach oben, transparent):

HTML
<div id="colorpicker">
  <div id="basecolor">
    <div id="saturation">
      <div id="brightness"></div>
    </div>
  </div>
</div>

Alle drei werden absolute übereinander positioniert und mit Grundfarbe bzw. den Verläufen bestückt:

CSS
#colorpicker {
    background:#333435;
    max-width:500px;
    min-height:300px;
    position:relative;
    margin:0 auto;
    display: flex;
    flex-flow: row wrap;
    width: calc(100% - 20px);
    padding:10px;
    min-width: 330px;
}
#basecolor {
    background:#ff0000;
    position:relative;
    width:300px;
    height:300px;
    top:0;
    left:0;
}
#saturation {
    position:absolute;
    width:100%;
    height:100%;
    top:0;
    left:0;
    background-image:linear-gradient(to right,white,rgba(255,255,255,0));
}
#brightness {
    position:absolute;
    width:100%;
    height:100%;
    top:0;
    left:0;
    background-image:linear-gradient(to top,black,rgba(255,255,255,0));
}

Das Ergebnis sieht so aus:

 
 

2.2 Der Schieberegler für den Farbwert

Neben das Farbfeld basteln wir den Schieberegler für die Grundfarbe: Dafür wird ein weiteres <div> mit der id="slider" in den #colorpicker eingefügt:

HTML
<div id="colorpicker">
  <!-- #basecolor, #saturation, #brightness, s.o. -->
  <div id="hue"></div>
</div>

Der #hue-Slider wird schmaler, ebenfalls absolut positioniert und mit einem etwas exotischeren Farbverlauf gestaltet: Um einmal "um den Farbkreis" zu kommen, werden die Farben Rot (#ff000), Gelb (#ffff00), Grün (#00ff00), Cyan (#00ffff), Blau (#0000ff), Magenta (#ff00ff) und wieder Rot in gleichgroßen Abstufungen (16.6667%) verteilt:

CSS
#hue {
  height: 300px;
  width: 16px;
  position: absolute;
  top: 10px;
  left:320px;
  background-image: linear-gradient(to bottom,
    #ff0000 0%,
    #ffff00 16.6667%,
    #00ff00 33.3334%,
    #00ffff 50%,
    #0000ff 66.6667%,
    #ff00ff 83.3334%,
    #ff0000 100%);
}

Ta-dah: Der Grundfarben-Schieberegler-Hintergrund:

 
 

2.3 Die Ausgabe der Farbwerte

Der Ausgabebereich hat vier Angaben: HSB, HSL, RGB und HEX und zusätzlich eine Vorschau der eingestellten Farbe. Die Angaben für HSB und HSL haben eine Gradangabe zwischen 0° und 360° (für die Position des Farbwertes auf dem Farbkreis) und zwei Prozentangaben zwischen 0% und 100%; RGB hat ebenfalls drei Werte für die drei Kanäle, jeweils zwischen 0 und 255, der HEX-Wert ist eigentlich eine Codierung dieser RGB-Werte, besteht also aus drei Zeichengruppen (die Werte von 0 bis 255 werden hier von 00 bis FF angegeben), die hintereinander in einem sechsstelligen Wert mit vorangestellter # angegeben werden. Alle Ausgaben erhalten innerhalb eines <div id="values"> einen eigenen Abschnitt, die Werte stehen in entsprechenden <span>s, in welche die Werte nach einer Benutzendeninteraktion dynamisch neu geschrieben werden können:

HTML
<div id="colorpicker">
  <!-- #basecolor, #saturation, #brightness, s.o. -->
  <!-- #hue, s.o. -->
  <div id="values">
    <div id="hsb"><strong>HSB:</strong><br>hue: <span class="huevalue">0</span>°,<br>saturation: <span class="saturationvalue">0</span>%,<br>brightness: <span class="brightnessvalue">100</span>%</div>
    <div id="hsl"><strong>HSL:</strong><br>hue: <span class="huevalue">0</span>°,<br>saturation: <span class="saturationvalue">100</span>%,<br>brightness: <span class="lightnessvalue">100</span>%</div>
    <div id="rgb"><strong>RGB:</strong><br>red: <span class="redvalue">0</span>,<br>green: <span class="greenvalue">0</span>,<br>blue: <span class="bluevalue">0</span></div>
    <div id="hex"><strong>HEX:</strong> <span class="hexvalue"> #ffffff</span></div>
    <div id="result"></div>
  </div>
</div>
CSS
#values {
  position: absolute;
  height: 300px;
  width: 160px;
  top: 10px;
  left: 350px;
  box-sizing:border-box;
  color: #e1e2e3;
  font-size: .9rem;
}
#values strong {
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
#values hr {
  border-bottom-color: rgba(255,255,255,0.25);
  border-top-color: transparent;
  margin: 5px 0;
}
#result {
  height: 30px;
  width: 100%;
  background-color:#fff;
  position: relative;
}

Das Ganze sieht dann so aus:

 
 
HSB:
hue: 0°,
saturation: 0%,
brightness: 100%
HSL:
hue: 0°,
saturation: 100%,
brightness: 100%
RGB:
red: 0,
green: 0,
blue: 0
HEX: #ffffff
 

2.4 Farbwähler und Schieberegler

Um nun den Farbwähler-Kringel und das Schieberegler-Schiebedings zu generieren, kommen sowohl zu #basecolor als auch zu #hue jeweils ein div für die "Aktionsfläche" (#chooserarea und #sliderarea) und jeweils ein div für die beweglichen Elemente (#chooser und #slider) hinzu. #chooserarea und #sliderarea sind die Bereiche, in denen #chooser und #slider bewegt werden können. Diese Bereiche sind jeweils ein paar Pixel größer, damit die Mitte der beweglichen Elemente auch bis ganz in die Randbereiche der Areas bewegt werden können:

HTML
<div id="colorpicker">
  <div id="basecolor">
    <div id="saturation">
      <div id="brightness">
        <div id="chooserarea">      <!-- Bereich für den Helligkeits- und Sättigungswähler -->
          <div id="chooser"></div>  <!-- Helligkeits- und Sättigungswähler -->
        </div>
      </div>
    </div>
  </div>
  <div id="hue">
    <div id="sliderarea">           <!-- Bereich für den Schieberegler -->
      <div id="slider"></div>       <!-- Schieberegler -->
    </div>
  </div>
  <!-- #values -->
</div>
CSS
#chooserarea {
  position: absolute;
  width: 310px;
  height: 310px;
  top: -5px;
  left: -5px;
}
#chooser {
  width: 10px;
  height: 10px;
  border: 1px solid #fff;
  box-sizing: border-box;
  border-radius: 50%;
  outline: 1px solid #000;
  cursor: move;
}
#sliderarea {
  height: 308px;
  width: 20px;
  position: absolute;
  top: -4px;
  left: -2px;
}
#slider {
  width: 20px;
  left: 0px;
  height: 8px;
  border: 1px solid #fff;
  outline: 1px solid #000;
  box-sizing: border-box;
  border-radius: 3px;
  cursor: n-resize;
}

Damit #chooser und #slider "beweglich" werden, nutze ich die jQuery-Events .mousedown() (Doku), .mousemove() (Doku), .mouseup() (Doku) & .mouseleave() (Doku).

Zwar wäre die Nutzung der jQuery-Erweiterung jQuery UI, im Speziellen die Interaktion draggable, einfacher. Allerdings besteht ein (für mich) nicht zu lösendes Problem darin, dass bei Klick auf die Interaktionsfläche erst das UI-Element zu der Mausposition bewegt und innerhalb des mousedown-Events die Interaktion draggable greifen müsste. Verschärft wird das dadurch, dass mousedown als event listener auf ein Elternelement des beweglichen (draggable) UI-Elements hört. Allerdings ist das nur über Umwege möglich, was wiederum viele andere (für mich) unlösbare Baustellen aufreißt 🙄 Daher ohne jQuery UI.

Um das mousedown- und das mousemove-Event zu kombinieren (z.B. in die freie Fläche der Helligkeitseinstellung zu klicken und direkt zu ziehen), werden die Variablen dragSlider und dragChooser definiert, die standardmäßig auf false stehen. Diese werden bei mousedown mit true überschrieben und bei mouseup und mouseleave wieder auf false gesetzt. Nur, wenn die Variable = true, kann das Event mousemove eine Funktion triggern. Sowohl mousedown als auch mousemove ermittelt die Position von #chooser (X und Y) bzw. #slider (nur Y) relativ zu der jeweiligen Interaktionsfläche (#chooserarea bzw. #sliderarea). Die Funktionen setChooser() und setSlider() validieren die Koordinaten und setzen diese als Position des jeweiligen UI-Elements via .css(). Die Y-Koordinate des Sliders generiert automatisch die neue Hintergrundfarbe für das Helligkeits- und Sättigungsfeld. Diese Funktion ist hier allerdings erst einmal nur zu Testzwecken implementiert, die wird im weiteren Verlauf noch umgebaut.

JS
$(function() {
  // declare initial variables
  var dragSlider = false,
      sliderY = 0,
      dragChooser = false;
      chooserX = 0,
      chooserY = 0;

  //cklickable and draggable brightness and saturation chooser
  $('#chooserarea').on('mousedown',function(e) {
    dragChooser = true;
    var parentOffset = $('#chooser').parent().offset();
    chooserX = e.pageX - parentOffset.left - 5;
    chooserY = e.pageY - parentOffset.top - 5;
    setChooser();
  }).on('mouseup mouseleave',function() {
    dragChooser = false;
  }).on('mousemove',function(e) {
    if(dragChooser) {
      var parentOffset = $('#chooser').parent().offset();
      chooserX = e.pageX - parentOffset.left - 5;
      chooserY = e.pageY - parentOffset.top - 5;
      setChooser();
    }
  });

  //clickable and draggable hue slider
  $('#sliderarea').on('mousedown',function(e) {
    dragSlider = true;
    var parentOffset = $('#slider').parent().offset();
    sliderY = e.pageY - parentOffset.top;
    setSlider();
  }).on('mouseup mouseleave',function() {
    dragSlider = false;
  }).on('mousemove',function(e) {
    if(dragSlider) {
      var parentOffset = $('#slider').parent().offset();
      sliderY = e.pageY - parentOffset.top;
      setSlider();
    }
  });
  
  //validate negative and too large values, set chooser position on click or drag
  function setChooser() {
    if (chooserX < 0) { chooserX = 0; }
    if (chooserX >300) { chooserX = 300; }
    if (chooserY < 0) { chooserY = 0; }
    if (chooserY >300) { chooserY = 300; }
    $('#chooser').css({'position':'absolute','top':chooserY + 'px','left':chooserX + 'px'});
  }
  //validate negative and too large values, set slider position on click or drag
  function setSlider() {
    if (sliderY < 0) { sliderY = 0; }
    if (sliderY >300) { sliderY = 300; }
    $('#slider').css({'position':'absolute','top':sliderY + 'px'});
    //FOR DEMONSTRATION PURPOSES ONLY
    //convert the slider height (0 - 300px) to hue degrees (0 - 360°) ...
    var baseColor = sliderY * 1.2;
    // ... and set as background-color for #basecolor
    $('#basecolor').css('background-color','hsl(' + baseColor + ',100%,50%)')
  }
});

Das Ergebnis: Die Anzeige für Sättigung und Helligkeit bzw. für Farbwert ist jeweils klickbar, die Position des jeweiligen Wählers wird bei Klick an die Mausposition gesetzt. Innerhalb des mousedown-Events kann der Wähler gezogen werden (Genau dieses 'dragging' innerhalb von mousedown habe ich in jQuery UI nicht hinbekommen. Hinweise willkommen 🙂 ). Das Loslassen des Maus-Buttons (mouseup) und das Herausziehen aus der Interaktionsfläche (mouseleave) führt zum Beenden der entspr. Bewegung des Wählers. Statt der Farbwerte hier zu Demo-Zwecken die Maus-Events und die (gerundeten) Koordinaten, die man im weiteren Verlauf noch benötigt:

 
 
Chooser
mouse event:  
touch event:  
position x:  
position y:  

Slider
mouse event:  
touch event:  
position y:  

2.5 Farbwerte be- und umrechnen

Der Rest ist JavaScript und Mathematik: Speichern der Koordinaten von #chooser und #slider, Berechnen der HSB- und HSL-Werte

  1. Farbwert (hue, HHSB, HSL): Dafür wird die y-Koordinate von #slider benötigt, die oben bereits in der Variable sliderY gespeichert wurde.
    Hier wird sie nun mittels * 1.2 von 0-300px (Position in Px) in 0-360° (Farbwert in Grad) umgerechnet, in die Variable hue geschrieben und abschließend im #values-Abschnitt angezeigt. Da HHSB = HHSL ist, muss nicht umgerechnet werden 🙂
    Via .css('background-color','hsl(' + hue + ',100%,50%)'); kann hier bereits die #basecolor gesetzt werden, da man dafür nur den HHSL-Wert benötigt. Die saturation SHSL liegt bei "Vollsättigung" bei 100%, die lightness LHSL liegt "unabgedunkelt" bei 50%.
  2. Sättigung (saturation, SHSB): Die x-Koordinate von #chooser wurde oben in der Variable chooserX gespeichert, nun wird sie mittels / 3 von 0-300px (Position in Px) in 0-100% (Sättigung in %) umgerechnet, in die Variable saturationHSB geschrieben und im #values-Abschnitt von #hsb angezeigt.
  3. Helligkeit (brightness, BHSB): Die y-Koordinate von #chooser wurde oben in der Variable chooserY gespeichert, jetzt mittels 100 - (chooserY / 3) von 300-0px (Position) in 0-100% (Helligkeit) umgerechnet, als brightnessHSB gespeichert und ebenfalls im #values-Abschnitt von #hsb angezeigt.
  4. Helligkeit (lightness, LHSL): Wenn man BHSB in LHSL gem. dieser Formel umrechnen möchte, ist
    LHSL = BHSB * (1 – SHSB/2)
    Unter Berücksichtigung der zusätzlich notwendigen Umrechnung der 0-300px in 0-100% ergibt sich untenstehende Formel für lightnessHSL.
  5. Sättigung (saturation, SHSL): Hier wird es noch ein wenig wilder, da unterschiedliche Fälle unterschieden werden:
    • Wenn LHSL 0 oder 100%, dann ist SHSL immer 0,
    • ansonsten ist
      SHSL = (BHSB – LHSL) / min (LHSL, 100 – LHSL)
      Das resultieren bei mir in wilden if/else-Verschachtelungen für die Berechnung von saturationHSL.
JS
function setColor() {
  //generate hue from sliderY, 0-300px => 0-360 deg
  hue = Math.round(sliderY * 1.2);
  //display values
  $('#hsb .huevalue').text( hue );
  $('#hsl .huevalue').text( hue );
  // set basecolor via hsl css with saturation 100% and lightness 50%
  $('#basecolor').css('background-color','hsl(' + hue + ',100%,50%)');

  //generate hsb saturation from chooserX, 0-300px = 0-100%
  saturationHSB = Math.round(chooserX / 3);
  //generate hsb brightness from chooserY, 300-0px = 0-100%
  brightnessHSB = Math.round(100 - (chooserY / 3));
  //display values
  $('#hsb .saturationvalue').text( saturationHSB );
  $('#hsb .brightnessvalue').text( brightnessHSB );

  //convert hsb brightness in hsl lightness
  //convert hsb saturation in hsl saturation
  //formulas from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL
  lightnessHSL = Math.round(((brightnessHSB/100) * (1 - ((saturationHSB/100) / 2))) * 100);
  if (lightnessHSL == 0 || lightnessHSL == 100) {
    saturationHSL = 0;
  } else {
    if((1 - (lightnessHSL/100)) < (lightnessHSL/100)) {
      var smallerValue = (1- (lightnessHSL/100));
    } else {
      var smallerValue = (lightnessHSL/100);
    }
    saturationHSL = Math.round(((brightnessHSB/100) - (lightnessHSL/100)) / smallerValue * 100);
  };
  //display values
  $('#hsl .lightnessvalue').text( lightnessHSL );
  $('#hsl .saturationvalue').text( saturationHSL );
}

Vorschau färben, HSL nach RGB und RGB nach HEX konvertieren Das ist tatsächlich etwas abenteuerlich. Da ich die Umrechnung in JS nicht hinbekomme :-?, gehe ich einen CSS-Umweg: HSL ist wie erwähnt im Gegensatz zu HSB prinzipiell CSS-fähig: background-color:hsl(h°,s%,l%); funktioniert in allen gängigen Browsern. Das Element #result wird – wie oben bereits #basecolor – entsprechend mit den berechneten HSL-Werten eingefärbt. Mittels $(element).css('background-color); gibt jQuery dann aber immer (hoffentlich?) die RGB-Werte als rgb([rotwert],[grünwert],[blauwert]) und nicht die HSL-Werte zurück! Diesen Rückgabe-String kann man von rgb( und ) bereinigen und an den Kommata splitten, um an die drei Werte zu gelangen1. Diese RGB-Werte können nun in die drei HEX-Blöcke konvertiert werden2. Die errechneten Werte für R, G und B werden als #RRGGBB im entsprechenden #values-Abschnitt #hex angezeigt.

JS
//generating result preview background color with hsl css
function refreshPreview() {
  $('#result').css('background-color','hsl(' + hue + ',' + saturationHSL + '%,' + lightnessHSL + '%)');
}

//convert hsl to rgb
function getRgbHex() {
  var result = $('#result').css('background-color');
  var rgb = result.replace(/^rgba?\(|\s+|\)$/g,'').split(',');
  for(var i in rgb) {
    var redValue = rgb[0];
    var greenValue = rgb[1];
    var blueValue = rgb[2];
  }
  $('#rgb .redvalue').text( redValue );
  $('#rgb .greenvalue').text( greenValue );
  $('#rgb .bluevalue').text( blueValue );

  //convert rgb to hex format
  var rgb = result;
  function rgb2hex(rgb){
    rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
    return (rgb && rgb.length === 4) ? '#' +
      ('0' + parseInt(rgb[1],10).toString(16)).slice(-2) +
      ('0' + parseInt(rgb[2],10).toString(16)).slice(-2) +
      ('0' + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
  }
  //alert(rgb2hex(rgb));
  $('#hex .hexvalue').text(rgb2hex(rgb));
}

3. Ergebnis und Download

Das Endergebnis kann man hier noch einmal begutachten:

Farbwähler öffnen

Wie immer, hier die Downloads für die eigenen Bastelversuche und Anpassungen:

Das Ganze gibt es als einzelne HTML-Datei mit dem gesamten HTML-, CSS- und JS-Code hier.
Als zip mit externer style.css und script.js sowie aktueller jquery-3.6.0.min.js gibt es das Ganze hier.

Viel Spaß damit 😉

4. Lizenzen und Fußnoten

"Eine Farbwähler-Bastelanleitung" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

1 Den Code habe ich zum Teil von 'user372551', https://stackoverflow.com/a/3752026, CC-by-sa 2.5, Lizenzhinweise.

2 Der Code ist zum Teil von laurent, https://codepen.io/camponogara/pen/EmXmgy, MIT-Lizenz, Lizenzhinweise.

]]>
https://medienmarmela.de/colorpicker/feed/ 0
360° Bilder https://medienmarmela.de/360-bilder/ https://medienmarmela.de/360-bilder/#respond Wed, 15 Sep 2021 15:33:07 +0000 https://medienmarmela.de/?p=2965 Nein, es geht hier NICHT um die Panoramabilder, bei denen man sich um die Position der/des Fotografierenden herum drehen kann. Vielmehr geht es um diejenigen Bilder, bei denen man ein Objekt von alle Seiten betrachten kann, wie sie in vielen Online-Shops gängig sind.

Diese sind – je nach Machart – entweder Videos oder animierte Gifs, bei denen mehrere Einzelbilder zu einer Datei zusammengefügt wurden. Oder die Aufnahmen verbleiben als Einzelaufnahmen – und werden mittels Player oder JavaScript so angezeigt, dass man das Objekt scheinbar im Browser drehen kann, wobei eigentlich nur die Bilder entsprechend schnell getauscht werden.

Umsetzung als animiertes GIF:

Beispiel für eine 360°-Aufnahme (hier: Animiertes Gif)

Umsetzung als Einzelbilder in einem Web-Viewer:

Beispiel für eine Umsetzung in einem Player incl. Vollbildmodus, Drehung in beide Richtungen und Zoom-Funktion.

Sinnvoll für solche Aufnahmen ist eine halbwegs gleichmäßige Drehung des Objektes um eine Achse bei möglichst gleichbleibender Belichtung, Entfernung, usw. Dafür hat sich in diversen Tests der Einsatz eines Drehtellers und eines Stativs bewährt. Um die Einzelaufnahmen dann zusammenzufügen oder darzustellen, ist Software notwendig. Dazu unten mehr.

1. Der Drehteller

Für Objekte bis zu einer Kantenlänge von 20cm kann man sich recht einfach mit einer Eigenkonstruktion aus einer Handvoll Klemmbausteine behelfen. Zusätzlich habe ich mir einen "Winkelanzeiger" ausgedruckt. Also eigentlich ist es lediglich ein Kreis mit 7,5°-Abschnitten. Der dient dazu, dass die Aufnahmen in gleichmäßigen Abständen erstellt werden. Die "Antenne" meines Drehtellers nutze ich sowohl zum Drehen des Tellers als auch zur Anzeige der aktuellen Position. Die 48 Aufnahmen (360° / 48) bei voller Umdrehung sind für meine Belange eine ausreichende Anzahl. Wer gleichmäßigere Drehbewegungen in seinem Endprodukt haben möchte, muss die Schritte entspr. kleiner wählen. Dadurch vergrößert sich allerdings die Dateigröße der Gifs bzw. die Ladezeit des Player-Inhalts.

Bei größeren Objekten tut es auch die drehbare Tortenplatte, die man in Glas oder Kunststoff für ca. 15-20 € bekommt.
Die Vorlage für die Winkelanzeige gibt es hier.

Wir bauen uns einen Drehteller 🙂
Musik: 'Ukulele' von Benjamin Tissot, bensound.com

2. Die Aufnahmen

Um die Objekte freizustellen, bietet sich das sog. Chroma Keying – umgangssprachlich "Green Screen" Technik – an. Das erklärt auch die interessante Farbauswahl bei dem Pappdeckel des Drehtellers und der Winkelanzeige 😉

Wenn man über eine Lightbox oder einen gut ausgeleuchteten, einfarbigen, hellen und mit wenig Schatten beworfenen Hintergrund verfügt, kann man den Schritt sicherlich auch anders lösen (dazu unten mehr).

Ich bin übrigens bereits in früheren Projekten damit in Berührung gekommen, allerdings weniger in der Produktfotografie für Onlineshops, sondern bzgl. Steinen für geologische Bestimmungsübungen und Exponaten aus dem Bergbau:

Quelle: https://www.youtube.com/watch?v=zMUEigKfKdU

Aufnahme und Nachbearbeitung habe ich seitdem ein wenig optimiert: Die Smartphonekamera sollte für die meisten Zwecke ausreichen, ein kleines Stativ und ein Bluetooth-Auslöser machen das dauernde Hin und Her zwischen Drehen des Objektes und der Kamera ein wenig angenehmer. Zu diesem Zweck gibt es sogar bereits automatisierte Lösungen, bei denen die Drehbühne automatisiert in einem voreingestellten Winkel gedreht und die Kamera ausgelöst wird. Aus Budgetgründen mache ich das aber noch in Handarbeit 😉

Handarbeit: Drehen, fotografieren, drehen, fotografieren …
Musik: 'Ukulele' von Benjamin Tissot, bensound.com

3. Die Nachbearbeitung

Während ich damalsTM noch mit Aktionsautomationen in Photoshop gearbeitet habe, mache ich das zwecks Zeitersparnis nun mittels Bildsequenz in After Effects. Dork kamm man mittels Keylight, Spill Suppressor und Farbkorrektur recht schnelle gute Ergebnisse erzielen:

Green Screen entfernen mittels Chroma Keying
Musik: 'Ukulele' von Benjamin Tissot, bensound.com

Je nach Beleuchtung hat man mit verschiedenen mehr oder weniger ärgerlichen Nebeneffekten zu kämpfen: Grünstichigkeit, Schatten, zu viel Transparenz (bei grüner Spiegelung in glatten Flächen, usw.). Außerdem muss man die Reste des Drehtellers (z.B, die "Winkelanzeige-Antenne") maskieren. Trotzdem kann man – wenn man etwas Übung hat – die Aufnahmen und die Nachbearbeitung in ca. 1 Std. je Objekt schaffen.

Wer – wie erwähnt – nicht über eine solche Software verfügt, sollte sowohl den Drehteller als auch den Hintergrund in einer dezenteren Farbe als Knallgrün wählen.

4. Gif, Video oder Web-Player?

Nun hat man also

  • entweder 48 einzelne Fotos oder
  • eine (freigestellte) Bildersequenz in der Zeitleiste eines Videoprogramms oder
  • eine exportierte Bildersequenz in einem Grafikformat.

Diese müssen nun zusammengesetzt werden. Bei der Wahl der Darstellung sollte man den eigentlichen Einsatzzweck, die Zielgruppe, ihre Endgeräte (außerdem Ladezeiten und Datenvolumen) nicht aus den Augen verlieren: In einem Online-Shop sollte es wohl eher so ein Player sein, den man in eine Webseite einbetten kann, wahlweise ein Produktvideo. In einem Erklär-/Lehrvideo tut es eine Videosequenz. Auf dieser Webseite tun es auch schlichte animierte Gifs.

Als Player nutze ich die Software von https://www.y360.at/, ehemals yofla.com. Aber wer nach 'free 360° product viewer' sucht, wird sicher kostenfreie Alternativen finden.

5. Ergebnisse

Hier noch ein paar Ergebnisse von Dingen, die ich gerde griffbereit hatte. Eure Einsatzszenarien müsst Ihr schon selber finden:

6. Update: Low/No Budget-Variante mittels Überbelichtung

Nach ein paar Rückmeldungen mit dem Grundtenor "After Effects ist teuer, Software-Lizenzen notwendig, u.ä." hier noch ein Nachtrag für eine Umsetzung ohne Green Screen und ohne Keying:

Voraussetzungen:

  • weiße Tischdecke
  • kl. Teller
  • weiße Stoffserviette, optional weißes Papier (als Abdeckung für den Teller)
  • viel Licht (am besten indirekt: helle Outdoor-Taschenlampen oder Baustrahler an die Decke richten)
  • Smartphone auf Stativ, optional: (Bluetooth-) Auslöser oder
  • Drehvorrichtung aus Klemmbausteinen (s.o.)

Damit liegt man – Smartphone vorausgesetzt – mit den Kosten unter 50 € (Smartphone-Stativ ca. 15€, LED-Baustrahler ca. 15-20€, evtl. Bluetooth-Auslöser, ca. 5€ und Omas Tischdecke). Der Aufbau sieht in etwa so aus:

Fokus und Belichtung sperren (AE/AF-Sperre), Motiv überbelichten, Bildausschnitt wählen 🙂
Musik: 'Ukulele' von Benjamin Tissot, bensound.com

Um ein Nachbearbeiten und freistellen zu vermeiden, kann man die einzelnen Fotos so weit überbelichten, dass die Schatten, Falten und hellen Abstufungen in Hintergrund (Tischdecke) und Vordergrund (Serviette oder Papier auf Teller) "unsichtbar" werden. Das ist nicht für alle Motive und Objekte (besonders ungeeignet: Helle Gegenstände) möglich, aber die Belichtung ann bis zu dem Punkt erhöht werden, bis die Details des eigentlichen Motivs beeinträchtigt werden. Falten in Servietten und Tischdecken sind doof. Bügeln könnte helfen 😉

Die Bilderserie kann man man (hier auch ohne lizenzpflichtige / kostenintensive Software) z.B. mittels Irfan View zuschneiden (Datei > Batch-Konvertierung > Spezial-Optionen) und über einen beliebigen Gif-Dienst (auch online, z.B. EzGif) zu einem animierten Gif zusammenfügen, die Abmessungen ändern, die Datei komprimieren und mehr. Das Ergebnis sieht dann so aus:

Motiv mittels Überbelichtung "freigestellt"

Vielleicht hilft es der einen oder dem anderen. Viel Spaß beim Basteln!

"360° Bilder – Eine Bastellösung für Produkt- oder Detailaufnahmen" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/360-bilder/feed/ 0
Kreise animieren (2) https://medienmarmela.de/kreise-animieren-2/ https://medienmarmela.de/kreise-animieren-2/#respond Wed, 07 Jul 2021 20:42:29 +0000 https://medienmarmela.de/?p=2562 Neben der hier beschriebenen Art, HTML-divs via CSS (transform:rotate) zu animieren und zu maskieren, ist auch eine weitere Variante verbreitet: SVG circles.

Diese vektorisierten Grafiken sind recht einfach direkt im HTML-Code generierbar und ihre Eigenschaften via CSS oder JS manipulierbar. Im vorliegenden Fall kann man die Attribute stroke („Pfadkontur“), stroke-dasharray (in etwa: „gestrichelte Kontur“) und stroke-dashoffset (meint so etwas wie: „Verschiebung der gestrichelten Kontur“) nutzen, um drei Kreiskonturen zu animieren:

1. Worum es geht…

… zeigt folgendes Video im Schnelldurchlauf:

2. Das Grundgerüst

Auch hier bastele ich zuerst eine Bühne (#stage), auf der ich dann mit weiteren Elementen hantiere. Im vorliegenden Fall ist das ein <svg>-Element mit dem Inhalt <circle>:

HTML

<div id="stage">
  <svg viewBox="0 0 100 100" width="20rem" height="20rem">
    <circle cx="50" cy="50" r="40" class="circle1"></circle>
  </svg>
</div>

CSS

#stage {
  background: #111213;
  width: 20rem;
  height: 20rem;
  margin: 2rem auto;
  border-radius: 2rem;
  position: relative;
}
.circle1 {
  fill:#bf304a;
}

Die viewBox (Doku) öffnet eine Art Zeichenfläche, die über Koordinaten und Größenangaben definiert wird ("x-Ankerpunkt y-Ankerpunkt Breite Höhe"). Die Größenangaben der viewBox sind relativ zur width– und height-Angabe des svg-Elements zu verstehen, funktionieren daher ohne Einheiten (px, %, em, …) und regeln so vor allem a) das Seitenverhältnis der Zeichenfläche und b) die Positionierung von gezeichneten Objekten.

Die Angaben cx (= center x-axis), cy (= center y-axis), r (= Radius), fill (= Füllung), stroke (= Kontur) und weitere SVG Attribute können sowohl als CSS (z.B. cx:10rem;) oder als Element-Attribut in HTML (dann: <svg><circle cx="160"></circle></svg>) angegeben werden. Bei der Angabe direkt im HTML-Code ist eine Angabe verlässlicher und cross-brower-geeigneter, dann ebenfalls ohne Einheiten vorzunehmen. ein cx & cy von je 50 legt den Mittelpunkt des Kreises also genau in die Mitte der viewBox mit den Abmessungen 100 * 100:

Für mehre Ringe benötigen wir nun innerhalb des einen <svg> drei <circle>, und statt einer Füllung (fill) wird nun eine Konturfarbe (stroke) eingesetzt, der man eine Breite (stroke-width) geben kann. Bei einer Konturbreite von 2rem verringert sich der Radius r für jeden folgenden Kreis um ebenfalls 2rem (weil wegen Kontur rundherum, sind also eigentlich 4 rem und Radius ist nur der halbe Durchmesser … ach, Ihr wisst schon):

HTML

<div id="stage">
  <svg viewBox="0 0 100 100" width="20rem" height="20rem">
    <circle cx="50" cy="50" r="40" class="circle1"></circle>
    <circle cx="50" cy="50" r="30" class="circle2"></circle>
    <circle cx="50" cy="50" r="20" class="circle3"></circle>
  </svg>
</div>

CSS

.circle1 {
  fill:none;
  stroke:#ff284f;
  stroke-width:10;
}
.circle2 {
  fill:none;
  stroke:#b3ff41;
  stroke-width:10;
}
.circle3 {
  fill:none;
  stroke:#11b4f4;
  stroke-width:10;
}

So siehts aus:

3. Konturgestaltung

Jetzt wirds ein wenig frickelig, da die Konturlinie mittels stroke-dasharray in Segmente "zerlegt" werden muss und diese Segmente mittels stroke-dashoffset verschoben werden. Dafür benötigt man die Länge der Kontur (man könnte sie auch "Umfang" nennen 🙈).

3.1 stroke-dasharray

Bei Kreisen ist der Umfang bekanntlich bei U = π * 2 * r, also für den äußeren Kreis bei 3,1415… * 2 * 40 = ~ 251,3. Wenn man beispielhaft das dasharray mal auf 1/10 des Umfangs (= 25.13 … Punkt statt Komma 🙄) setzt, erhält man fünf Abschnitte und fünf "Lücken":

CSS

.circle1 {
  /* siehe oben */
  stroke-dasharray: 25.13;
}

3.2 stroke-dashoffset

Die Offset-Einstellung kann nun diese Kontur-Segmente verschieben. Eine Animation von stroke-dashoffset um 50.26; (1 Strich + 1 Lücke, je 25.13;) führt zu einer lustigen Kreisbewegung:

CSS

.circle1 {
  /* siehe oben */
  stroke-dasharray: 25.13;
  animation: strokes 1s linear infinite;
}
@keyframes strokes {
  100%  {stroke-dashoffset: 50.26;}
}

3.3 Einsatz von Array & Offset für eine Animation

Wenn man das Grundprinzip einmal (halbwegs! gilt für mich) begriffen hat, wird klar: Wenn stroke-dasharray = Umfang ist, ist der Kreis "unsichtbar", da die erste Lücke der Segmentierung genau so groß wie die gesamte Kreislinie lang ist. Und wenn man dann stroke-dashoffset von x nach 0 animiert (bei xmax = Umfang), läuft die Kreiskontur "von 0 auf voll":

CSS

.circle1 {
  /* siehe oben */
  stroke-dasharray: 251.3;   /* entspricht dem Umfang π * 2 * r */
  stroke-dashoffset: 251.3;  /* entspricht dasharray */
  animation: strokes 3s ease-in-out infinite;
}
@keyframes strokes {
    0% {stroke-dashoffset: 251.3;}
   50% {stroke-dashoffset: 0;}
}

Gleiches Vorgehen für die anderen beiden Kreise – andere Radien machen andere dasharray- und dashoffset-Werte notwendig. Um die Öffnung der Kreise nach oben zu drehen, drehe ich einfach das ganze svg-Objekt. Zur weiteren Veranschaulichung habe ich ein animation-delay von .2s bzw. .4s für .circle2 und .circle3 eingebaut:

CSS

svg {
  transform:rotate(-90deg);
}

.circle1 {
  /* siehe oben */
  stroke-dasharray: 251.3;
  stroke-dashoffset: 251.3;
  animation: strokes1 3s ease-in-out infinite;
}
.circle2 {
  /* siehe oben */
  stroke-dasharray: 188.5;
  stroke-dashoffset: 188.5;
  animation: strokes2 3s ease-in-out .2s infinite;
}
.circle3 {
  /* siehe oben */
  stroke-dasharray: 125.7;
  stroke-dashoffset: 125.7;
  animation: strokes3 3s ease-in-out .4s infinite;
}

@keyframes strokes1 {
   0% {stroke-dashoffset: 251.3;}
  50% {stroke-dashoffset: 0;}
}
@keyframes strokes2 {
   0% {stroke-dashoffset: 188.5;}
  50% {stroke-dashoffset: 0;}
}
@keyframes strokes3{
   0% {stroke-dashoffset: 125.7;}
  50% {stroke-dashoffset: 0;}
}

So weit, so cool 🙂

4. Feinschliff

"Scalable Vector Graphics Stroke-linecap" von Eric Baas, Quelle, CC-by-sa.

Um das Ganze im Wortsinne abzurunden, kann man mit stroke-linecap (Doku) runde "Endkappen" aufsetzen, muss dann aber dashoffset evtl. um die "Kappengröße" ändern 🙄

Diese ist im Falle von stroke-linecap:round; gleich der Hälfte von stroke-width, hier also 10 / 2 = 5. Je nach gewünschter Überlappung der beiden Kappen (Anfang und Ende) muss das Offset z.B. bei Endpunkten einer Animation entsprechend vergrößert werden. Wenn sie gar nicht überlappen sollen, muss man Offset auf "zwei Kappengrößen", also eine ganze stroke-width setzen.

Die Abbildung zeigt schematisch die Funktionsweise von linecaps und die dadurch provozierte Verlängerung einer Konturlinie, das Beispiel unten einen Animations-End-Offset von 7.5, also "fast" zwei Kappengrößen. Dass nun die Öffnung der kreise nicht mehr exakt oben ist, kann man halbwegs mittels Justierung der Rotation des svg-Elements ausgleichen.

Eine weitere Option ist das Arbeiten mit Farbverläufen innerhalb von stroke: Dafür muss man innerhalb des svgs einen <defs> ... </defs>-Bereich (Doku) anlegen, dort linearGradients definieren und stroke statt einer Füllfarbe einen Verlauf via ID zuordnen. Standardmäßig verläuft ein Verlauf von oben nach unten, gradientTransform dreht die Verlaufsrichtung. Die <stop>-Definitionen legen in % die Punkte über den Verlauf fest, für die Farben hinterlegt sind. Die Ausrichtung von Rotation und Stop-Punkten verlangt je nach Anwendungsfall viel Ausprobieren.

HTML

<svg viewBox="0 0 100 100" width="20rem" height="20rem">
  <defs>
    <linearGradient id="gradient1" gradientTransform="rotate(78)">
      <stop offset="32%" stop-color="#fa114f" />
      <stop offset="100%" stop-color="#f93885" />
    </linearGradient>
    <linearGradient id="gradient2" gradientTransform="rotate(78)">
      <stop offset="32%" stop-color="#99ff01" />
      <stop offset="100%" stop-color="#d8ff00" />
    </linearGradient>
    <linearGradient id="gradient3" gradientTransform="rotate(78)">
      <stop offset="32%" stop-color="#00d8fe" />
      <stop offset="100%" stop-color="#02ffa9" />
    </linearGradient>
  </defs>
  <circle cx="50" cy="50" r="40" class="circle1"></circle>
  <circle cx="50" cy="50" r="30" class="circle2"></circle>
  <circle cx="50" cy="50" r="20" class="circle3"></circle>
</svg>

CSS

.circle1 {
  /*siehe oben*/
  stroke:url(#gradient1);
}
.circle2 {
/*siehe oben*/
  stroke:url(#gradient2);
}
.circle3 {
/*siehe oben*/
  stroke:url(#gradient3);
}

5. Varianten

Darüber hinaus kann man auch hier (wie bei der rotierende-divs-Variante) Gedanken über einen Auslöser der Animation machen. Das können auch hier wieder z.B. :hover oder JavaScript-Werte sein.

5.1 Trigger durch :hover

Beim :hover-Trigger empfiehlt es sich, statt mit CSS animations lieber mit CSS transitions zu arbeiten, da diese hübscher in den ":not(:hover)"-Status zurückschwingen. Das ist bei Keyframe-Animationen nicht so einfach zu realisieren:

5.2 Trigger durch JavaScript / jquery

Durch das Binden des Offset-Wertes an eine Variable können die Ringe auch als Fortschritts-, Timer- oder Counter-Anzeige genutzt werden. Hier kommt der Wert – zur Veranschaulichung – aus einem Schieberegler: Close your rings!

Bewegung heute (0-100%)

Training heute (0-100%)

Stehen heute (0-100%)

So weit.

Ergänzungen, Hinweise, Verbesserungen gerne in die Kommentare. Und – viel Spaß beim Basteln!

"Kreise animieren (2)" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/kreise-animieren-2/feed/ 0
Kreise animieren (1) https://medienmarmela.de/kreise-animieren-1/ https://medienmarmela.de/kreise-animieren-1/#respond Mon, 05 Jul 2021 19:12:12 +0000 https://medienmarmela.de/?p=2540 Wenn man einen hübschen runden Fortschrittsbalken, Timer oder Counter für eine Webseite benötigt, stolpert man meist über zwei verschiedene Herangehensweisen: Entweder man arbeitet in CSS mit transform:rotate und dreht absolut positionierte HTML-Elemente hinter Masken und 'hidden overflows' oder man nimmt einen svg circle mit stroke-dasharray und stroke-dashoffset zur Hilfe.

Die transform-Variante gibt es in diesem Artikel, die SVG-Variante in einem anderen.

1. Worum es geht…

… zeigt das Video im Schnelldurchlauf:

2. Das Grundgerüst

Zuerst bastelt man sich einen Wrapper oder eine "Bühne", in dem man mit einem relativ positionierten Eltern-<div> mit absolut positionierten Kind-Elementen hantieren kann. Insgesamt benötigt man vier Kind-<div>s:

HTML

<div id="stage">
   <div class="circle">
      <div class="rightcircle"></div>
      <div class="leftcircle"></div>
      <div class="mask"></div>
      <div class="innercircle"></div>
   </div>
</div>

CSS

#stage {
  width:20rem;
  height:20rem;   /*width = height*/
  background:#f2efef;
}
.circle {
  position:relative;
  width:15rem;   /*muss kleiner als #stage sein*/
  height:15rem;  /*width = height*/
  top:50%;
  left:50%;
  transform:translate(-50%,-50%);
  border-radius:50%;
  background:#bf304a;
}
 
 
 
 

3. Die Kreisbewegung (rechts)

Jetzt platziert man ein Rechteck über der linken Hälfte des Kreises und setzt den Ankerpunt (CSS: transform-origin) rechts mittig, um es via transform:rotate zu drehen.

CSS

.rightcircle {
  position:absolute;
  left:0;
  top:0;
  width:50%;
  height:100%;
  transform-origin:right center;
  animation:rightcircle 5s linear infinite;
}
@keyframes rightcircle {
  100% {transform:rotate(360deg);}
}

Die Animation dient hier erst einmal nur zur Verdeutlichung – sie wird im weiteren Verlauf noch weiter modifiziert.

 
 
 
 

Wenn man nun für .circle via CSS overflow:hidden; setzt und einen .innercircle gestaltet, der die gleiche Farbe wie der Hintergrund hat, wird schnell deutlich, wie das Ganze am Ende funktioniert. Allerdings gibt es einen Haken – die Bewegung des Rechtecks reicht nur für die rechte Kreishälfte. Daher stoppt die Animation bei 50% der Zeit bei 180 Grad und bleibt dort bis zum Ende stehen):

CSS

.circle {
  /* siehe oben */
  overflow:hidden;
}
.innercircle {
  position:absolute;
  background:#f2efef;  /* entspricht background von #stage */
  top:50%;
  left:50%;
  transform:translate(-50%,-50%);
  width:10rem;
  height:10rem;
  border-radius:50%;
}
@keyframes rightcircle {
   50% {transform:rotate(180deg);}
  100% {transform:rotate(180deg);}
}
 
 
 
 

4. Die Kreisbewegung (links)

Für die linke Hälfte nehmen wir nun das zweite Kind-Element .leftcircle, welches zuerst mit .rightcircle "mitläuft", dann aber zusätzlich die zweite Hälfte der Kreisbewegung macht – erst wieder mit overflow:visible; für .circle und ohne .innercircle:

CSS

.rightcircle, .leftcircle {
  position:absolute;
  left:0;
  top:0;
  width:50%;
  height:100%;
  transform-origin:right center;
}
.rightcircle {
  animation:rightcircle 5s linear infinite;
}
.leftcircle {
  animation:leftcircle 5s linear infinite;
}
@keyframes rightcircle {
   50% {transform:rotate(180deg);}
  100% {transform:rotate(180deg);}
}
@keyframes leftcircle {
  100% {transform:rotate(360deg);}
}
 
 
 
 

Mit den entsprechenden Masken (.circle bekommt overflow:hidden;, .innercircle eingeblendet) sieht das dann so aus:

 
 
 
 

5. Maskierung

Das ist schon nah dran. Lediglich der Anfang der Animation ist noch nicht sooo super 🙈 daher kommt hier das dritte Kind-Element .mask ins Spiel. Damit wird für die Hälfte der Animation die linke Seite des Kreises "wegmaskiert", indem man die Deckkraft (opacity) animiert. Grün ist natürlich auch "suboptimal", hier nur zur Verdeutlichung:

CSS

.mask {
  position:absolute;
  left:0;
  top:0;
  width:50%;
  height:100%;
  background:#86bf30;
  animation:mask 5s linear infinite;
}
@keyframes mask {
  50%    {opacity:1;}
  50.01% {opacity:0;}
}
 
 
 
 

Wenn man nun background von .circle und .mask an #stage anpasst, ist es schon fertig:

 
 
 
 

In manchen Browsern kommt es zu schwer zu unterdrückenden Artefakten in der ersten Hälfte der Animation (ein "Schweif" von .rightcircle, der entlang der Kontur von .circle gezogen wird). Die kann man z.B. mit einer border auf .circle verschleiern.

6. Fertig 🙂

Hier noch einmal der gesamte und bereinigte Code …

HTML

<div id="stage">
   <div class="circle">
      <div class="rightcircle"></div>
      <div class="leftcircle"></div>
      <div class="mask"></div>
      <div class="innercircle"></div>
   </div>
</div>

CSS

#stage {
   width:20rem;
   height:20rem; /*width = height*/
   background:#f2efef;
}
.circle {
   position:relative;
   width:15rem; /*muss kleiner als #stage sein*/
   height:15rem; /*width = height*/
   top:50%; left:50%;
   transform:translate(-50%,-50%);
   border-radius:50%;
   background:#f2efef; /* muss background von #stage entsprechen */
   overflow:hidden;
   border:1px solid #aaa;
}
.innercircle {
   position:absolute;
   background:#f2efef; /* muss background von #stage entsprechen */
   top:50%;
   left:50%;
   transform:translate(-50%,-50%);
   width:10rem; /*muss kleiner als .circle sein*/
   height:10rem; /*width = height*/
   border-radius:50%;
}
.rightcircle, .leftcircle, .mask {
   position:absolute;
   left:0;
   top:0;
   width:50%;
   height:100%;
   transform-origin:right center;
}
.rightcircle, .leftcircle {
   background:#30a3bf; /* Farbe des Kreises */
}
.mask {
   background:#f2efef; /* muss background von #stage entsprechen */
}
.rightcircle {
   animation: rightcircle 5s linear infinite;
}
.leftcircle {
   animation: leftcircle 5s linear infinite;
}
.mask {
   animation: mask 5s linear infinite;
}
@keyframes rightcircle {
    50% {transform:rotate(180deg);}
   100% {transform:rotate(180deg);}
}
@keyframes leftcircle {
   100% {transform:rotate(360deg);}
}
@keyframes mask {
     0%    {opacity:1;}
    50%    {opacity:1;}
    50.01% {opacity:0;}
   100%    {opacity:0;}
}

7. Varianten

Die Animation kann natürlich getriggert werden – z.B. durch :hover oder via JavaScript – und wenn sie nicht in Dauerschleife laufen soll, muss bei allen animation-Angaben der animation-iteration-count von infinite gelöscht oder auf 1 gesetzt werden. Darüber hinaus empfiehlt sich ein animation-fill-mode von forwards. Damit wird die Animation auf dem letzten Frame (der 100%-Einstellung) angehalten und springt nicht am Ende auf die 0%-Einstellung zurück. Die shorthand-Angabe geht dann z.B. so: animation: rightcircle 5s linear 1 forwards;

7.1 Trigger via :hover

 
 
 
 

7.2 Trigger via JavaScript

 
 
 
 

7.3 Dynamische Manipulation via jQuery

 
 
 
 

 

So weit. Viel Spaß beim Basteln 🙂

Verbesserungen, Anregungen, Anmerkungen gerne in die Kommentare.

"Kreise animieren (1)" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/kreise-animieren-1/feed/ 0
'Dark Mode' für Webseiten? https://medienmarmela.de/dark-mode-fuer-webseiten/ https://medienmarmela.de/dark-mode-fuer-webseiten/#respond Mon, 28 Sep 2020 13:36:32 +0000 https://medienmarmela.de/?p=2414 Ja, auch das Thema ist schon "älter", spätestens mit iOS 13 (Mitte 2019) war das Thema irgendwie in aller Munde. Seitdem meint 'Dark Mode' eine Einstellung, die meist hellen Hintergründe und Flächen von Webseiten und Apps zu "invertieren" (also statt dunkler Schrift auf hellem Grund lieber helle Schrift auf dunklem Grund anzuzeigen).

In verschiedenen Kontexten nutze ich das schon länger, z.B. beim Lesen von eBooks. Ich empfinde die hellen Buchstaben als deutlich augenfreundlicher und muss immer an einen damaligen Prof. denken, seines Zeichens ABO-Psychologe und Psychophysiker, der (sinngemäß zititert) gesagt hat:

"Das Auge ist ein 'Lichtsucher' und 'Reizsucher'. Vor allem für elektronische (Lese-) Medien gilt: Bei dunkler Schrift auf hellem Grund wird zuvorderst die weiße Fläche (Lichtreiz) wahrgenommen und erst in einem zweiten Schritt der Buchstabe als 'Unterbrechung dieser leuchtenden Fläche'. Die kognitive Leistung der notwendigen Invertierung von 'Lücke in der Leuchtfläche' zu 'Buchstabe' kostet Energie und Zeit, die bei heller Schrift auf dunklem Grund recht einfach eingespart werden kann."

Ob das einer experimentellen Überprüfung standhält, weiß ich nicht, es erschien mir aber bereits vor Jahren irgendwie ein’leuchtend' 🙂 Wie auch immer, der Dark Mode ist da und zu einem dieser Webdesign- und Entwicklertrends geworden, die wohl nie wieder ganz verschwinden werden, daher:

Yay. Wir basteln uns einen Dark Mode (für Webseiten)!

Technisch sind diverse Dinge zu beachten:

  1. Es gibt einerseits Gerätevoreinstellungen auf Betriebssystem-Ebene.
  2. Benutzende von Webseite sollten die Möglichkeit haben, diese Voreinstellung mit individuellen EInstellungen für eine Webseite zu überschreiben.
  3. Bei wiederkehrenden Besuchenden wäre eine Speicherung der Präferenzen sinnvoll.

1. Dark Mode auf Systemebene: Gerätevoreinstellungen

Die Gerätevoreinstellungen gibt es z.B. seit iOS 13, Android 10 (bei Version 9 tlw. als "Entwickleroption" verfügbar), macOS Mojave (v.10.14) und Windows 10 (1809) auf diversen Geräten.

Dafür benätigt man nur wenige Zeilen Code, nämlich einen <meta> tag im <head> des html-Dokuments und das entsprechende CSS, welches mittels media queries (MDN Docs) @media (prefers-color-scheme: dark) bzw. @media (prefers-color-scheme: light) den entsprechenden Geräte-Einstellungen zugewiesen wird:

HTML

<!DOCTYPE html>
<html>
  <head>
    ....
    <meta name="color-scheme" content="dark light">
  </head>
  <body>
    ....
 </body>
</html>

CSS

/*light mode*/
@media (prefers-color-scheme: light) {
  /* CSS definitions*/
}

/*dark mode*/
@media (prefers-color-scheme: dark) {
  /* CSS definitions*/
}

Das Ganze funktioniert danach (entspr. Geräte, Betriebssysteme und Browser vorausgesetzt) automatisch und die Änderungen werden ohne Rrefresh der Seite beim Umschalten des Modus sofort wirksam (zumindest unter Win10, iOS 14):

In der Datei darkmode_device.html habe ich das mal zusammengeklickert. Einfach mal die Geräte-Enstellungen umschalten und testen.

2. Individuelle Benutzendeneinstellung

Geräteeinstellungen sind schön und gut, aber evtl. soll Seitenbesuchenden die Möglichkeit gegeben werden, individuelle Einstellungen unabhängig von denen auf Betriebssystem-Ebene vorzunehmen.

Das geht z.B. mit einem Schalter, den man in einem Menü oder den Einstellungen platziert. Dafür basteln wir uns z.B. einen Schalter mit dem Checkbox-Hack, mit dem man z.B. eine class von <body> manipuliert: Wenn (if) der Schalter :checked ist, der Dark Mode also momentan aktiviert ist, wird dieser .on('click') ausgeschaltet: Die Klasse dark wird entfernt. Wenn der Schalter zum Zeitpunkt des Anklickens dagegen nicht aktiviert ist (else-Variante), wird die Klasse hinzugefügt. Um den Schalter anzusprechen, nutze ich hier übrigens das <label for="darkmodeswitch">, da die eigentliche Checkbox ausgeblendet ist. Num Nachvollziehen bitte in die Beispieldatei unten schauen. Nicht übermäßig komplex, aber wirksam:

jQuery

$(document).ready(function() {
   $('label[for="darkModeSwitch"]').on('click',function() {
      if ($('#darkModeSwitch').is(':checked')) {
         $('body').removeClass('dark');
      }else {
         $('body').addClass('dark');
      }
   });
});

Über diese Klasse kann mittels entspr. CSS dann ein Dark Mode umgesetzt werden:

CSS

/*light mode*/
body
   {background:#fff;color: #444;}
h1,h2,h3,h4,h5,h6
   {color:#bf304a;}

/*dark mode*/
body.dark
   {background: #222;color:#eee;}
.dark h1,.dark h2,.dark h3,.dark h4,.dark h5,.dark h6
   {color: #d1d2d3;}

Auch hier der Videobeweis, wie es aussieht (bzw. aussehen sollte…):

Das Skript, CSS und alles weitere (jQuery wird vom CDN geladen) gibts in darkmode_switch.html zum Herumspielen 🙂

3. Speicherung der Einstellung

Besonders cool wäre es ja, wenn man diese Einstellung irgendwie speichern könnte, damit bei wiederholtem Besuch die Einstellung nicht erneut vorgenommen werden muss. Neben "Datenbankfeld eines Benutzendenprofils" (aufwändig, für Webseiten ohne Login evtl. nicht realisierbar) oder der Speicherung in einem Cookie bietet der sog. 'Local Storage' (im Folgenden: localStorage, Infos hier: Wikipedia, MDN Docs) eine recht komfortable Möglichkeit, solche Informationen auf dem Benutzendengerät zu speichern. Darin lässt sich (hier mittels jQuery) recht komfortabel via localStorage.setItem('key','value'); ein "Schlüssel-Wert-Paar" speichern und der Wert mittels localStorage.getItem('key') wieder auslesen.

Das gesamte Vorgehen hat also zwei Bestandteile:

3.1 Wiederherstellung der Einstellung des letzten Seitenbesuchs

Beim Laden der Seite muss überprüft werden, ob es zum Schlüssel (hier: 'darkmode') einen (beliebigen) Wert im localStorage gibt. Wenn ja, wird in Abhängigkeit des Werts ('on' oder 'off') der Schalter eingestellt und die Klasse gesetzt, also der Zustand des vergangenen Seitenbesuchs wieder hergestellt. Wenn kein Wert für 'darkmode' existiert, wird der 'light' Mode verwedet:

jQuery

   // check if localStorage has a value
   if (localStorage.getItem('darkmode') != null) {
      // ... check if this value is 'on'
      if (localStorage.getItem('darkmode') == 'on') {
         // then set body class to 'dark' and button state to 'checked'
         $( '#darkModeSwitch' ).prop( 'checked', true );
         $('body').addClass('dark');
      // else (= storage value exists but must be 'off') ...
      } else {
         // remove body class 'dark' and set button state to 'unckecked'
         $( '#darkModeSwitch' ).prop( 'checked', false );
         $('body').removeClass('dark');
      }
   // in case localStorage is empty
   } else {
      // set button state tu 'unchecked', remove body class
      // and save setting in localStorage
      $( '#darkModeSwitch' ).prop( 'checked', false );
      $('body').removeClass('dark');
      localStorage.darkmode = "off";
   }

3.2 SchaLteroptionen

Neben dem Auslesen eventuell gespeicherter Werte aus dem localStorage muss natürlich trotzdem die Schalterfunktion umgesetzt werden: Diese muss nun nicht nur die Klasse ändern (wie bereits oben), sondern auch den localStorage (über-) schreiben. Das ist aber mit je einer zusätzlichen Zeile für die beiden Buttonstati einfach möglich:

jQuery

$('label[for="darkModeSwitch"]').on('click',function() {
   if (localStorage.getItem('darkmode') == 'on') {
      $('body').removeClass('dark');
      localStorage.darkmode = 'off';
   } else {
      $('body').addClass('dark');
      localStorage.darkmode = 'on';
   }
});

Eine Umsetzung mit dem localStorage findet man in der Datei darkmode_localStorage.html. Modus umschalten, Seite schließen, erneut öffnen: Ta-dah! 🙂

Verschiedene Browser bzw. Browsereinstellungen verhindern das Speichern von localStorage-Einträgen bzw. löschen diese beim Schließen des Browsers. Der "private Modus" von Safari gehört ebenso dazu wie "Firefox Klar" oder Einstellungen, alle Website-Daten beim Schließen eines Browsers zu löschen.

4. Kombination und Hierarchisierung

Amateurhafte Skizze zwecks verschiedener Darkmode-Optionen, je nach Geräteeinstellung und vorherigen Seitenbesuchen

Wir haben jetzt also zwei Ansätze, nämlich (a) Gerätevoreinstellung und (b) Benutzendenpräferenz. Zweitere können wir mittels localStorage (teil-) persistent gestalten.

Um beide Ansätze miteinander zu kombinieren, muss man Überlegungen anstellen, welcher Einstellung mehr Gewicht beigemessen wird: Wenn z.B. ein/e wiederkehrende Benutzende/r mit einem localStorage-Eintrag (z.B. Dark Mode = on) mit einem Endgerät mit gegensätzlicher Voreinsteillung (Dark Mode = off) die Seite besucht – was soll dann gelten? Die Benutzendeneinstellung vom letzten Seitenbesuch oder die Geräteeinstellung?

Für mich ist die Betriebssystem-Voreinstellung nicht unernehblich, schließlich trifft der/die Benutzende damit eine generelle Voreinstellung, die für alle Apps und Webseiten (soweit unterstützt) gelten soll. Daher sollte die Überprüfung auf OS-Voreinstellung vor der Überprüfung des localStorage passieren. Meine Überlegungen hier (ja, ich weiß, dass man das anders machen kann 🙄 ):


Das sieht in meinem Code nun so aus:

jQuery

$(document).ready(function() {
   // check device preference for dark mode:
   // if dark mode is active, set body class to 'dark',
   if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
      $('body').addClass('dark');
      // set button state to 'checked' and set localStorage to 'on'
      $( '#darkModeSwitch' ).prop( 'checked', true );
      localStorage.darkmode = "on";
   //if no device-side dark mode is active ...
   } else {
      // ...check localStorage for a value from earlier visit. If storage has a value ...
      if (localStorage.getItem('darkmode') != null) {
         // ... check if this value is 'on'
         if (localStorage.getItem('darkmode') == 'on') {
         // then set body class to 'dark' and button state to 'checked'
            $( '#darkModeSwitch' ).prop( 'checked', true );
            $('body').addClass('dark');
         // else (means: storage value exists but must be 'off') ...
         } else {
            // remove body class 'dark' and set button state to 'unckecked'
            $( '#darkModeSwitch' ).prop( 'checked', false );
            $('body').removeClass('dark');
         }
      // if no device-side dark mode is active and localStorage is empty ...
      } else {
         // remove body class 'dark', set button state to 'unchecked' and set localStorage to 'off'
         $( '#darkModeSwitch' ).prop( 'checked', false );
         $('body').removeClass('dark');
         localStorage.darkmode = "off";
      }
   }

   // modify by switch: on click
   $('#darkModeSwitch').click(function() {
      //check if dark mode is inactive
      if (localStorage.getItem('darkmode') != 'on') {
         //set body class, button state, localStorage
         $('body').addClass('dark');
         $( '#darkModeSwitch' ).prop( 'checked', true );
         localStorage.darkmode = "on";
      // else (means: no dak mode is enabled)
      } else {
         //set body class, button state, localStorage
         $('body').removeClass('dark');
         $( '#darkModeSwitch' ).prop( 'checked', false);
         localStorage.darkmode = "off";
      }
   });
});

Das Ganze gibt es in der Datei darkmode.html als Gesamtkunstwerk zum Download.

Wie immer: Der Code geht sicherlich "schöner" und deckt nicht alle Eventualitäten ab. Auch der notwendige Refresh beim Umschalten des Gerätemodus ist nicht ganz so schön. Verbesserungsvorschläge gerne in die Kommentare.

Ansonsten viel Spaß beim Basteln 🙂

"Dark Mode für Webseiten" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/dark-mode-fuer-webseiten/feed/ 0
iframe-Blocker https://medienmarmela.de/iframe-blocker/ https://medienmarmela.de/iframe-blocker/#comments Thu, 19 Sep 2019 21:02:20 +0000 https://medienmarmela.de/?p=2255 1. Hintergrund

Mmmh. Die DSGVO ist nun schon recht lange in Kraft, trotzdem haben noch nicht alle CMS entspr. Plugins zum Blocken von Drittanbieter-Content.

Für WordPress gibt es diverse Möglichkeiten (ich habe zeitweise – momentan wieder – BorlabsCookie eingesetzt, andere Vorschläge u.a. hier), aber für andere CMS oder nicht-CMS-Seiten gibt es noch keine weit verbreiteten Lösungen. Auch für LMS wie Moodle stehen – trotz aktiver Community und umfangreicher Plugin-Datenbank – keine Lösungen bereit…

Das ist ziemlich "unschön", da hier diverse (personenbezogene) Informationen von Seitenbesuchenden abgefischt und Cookies gesetzt werden können. Eine Folge ist, dass es zwingend zu unvollständigen Datenschutzhinweisen kommen muss.

Exkurs: Meine rechtliche Laienmeinung

Die Seitenbetreibenden haben gem. Art. 13 DSGVO die Pflicht, die Betroffenen "zum Zeitpunkt der Erhebung dieser Daten" darüber zu informieren, dass und wie personenbezogene Daten verarbeitet werden. So weit, so einfach: Beim ersten Besuch der Webseite wird ein Overlay (nein, nicht so ein klitzekleines Cookie-Banner!) eingeblendet, welches die Nutzung der Seite stark beeinträchtigt. Hierin müssen ein Link zur Datenschutzerklärung und mind. ein Button angezeigt werden, mit dem die Betroffenen der Verarbeitung der Daten in der beschriebenen Weise zustimmen.

Allerdings sollen die Betroffenen nach Art. 13 (1) lit. c.-e. DSGVO auch über Verarbeitungszweck, Rechtsgrundlage, berechtigte Interessen der Verantwortlichen und über Empfänger bei Datenweitergabe informieren, außerdem nach Art. 13 (2) lit. a. DSGVO über die Speicherdauer informiert werden. Die oft (z.B. bei eingebetteten YouTube Videos) in den Datenschutzerklärungen auffindbaren Formulierungen wie

"Eingebettete Videos werden von YouTube, LLC, 901 Cherry Ave., San Bruno, CA 94066, USA bereitgestellt. Beim Abspielen wird eine Verbindung zu den Servern von YouTube hergestellt. Dabei wird YouTube mitgeteilt, welche Seiten Sie besuchen. YouTube kann Ihr Surfverhalten evtl. Ihnen persönlich oder Ihrem YouTube-Benutzendenkonto zuordnen. Außerdem werden von YouTube Cookies und weitere Methoden eingesetzt, die evtl. Hinweise über Ihr Nutzungsverhalten sammeln. Weitere Informationen zur Datenverarbeitung bei YouTube finden Sie in der Datenschutzerklärung des Anbieters unter https://www.google.de/intl/de/policies/privacy/."

beinhalten zwar Verantwortliche und Verarbeitungsbeschreibung, allerdings keine detaillierten Angaben zu z.B. Rechtsgrundlage, Zweck und Speicherdauer. Das ist mindestens unvollständig, kann aber auch nicht viel besser sein, da viele relevanten Informationen von YouTube wenig transparent (als Geschäftsgeheimnis?) behandelt werden. Zwar beinhalten die verlinkten Datenschutzhinweise von (in diesem Fall) Google "Hinweise" (≠ detaillierte Informationen) zu Datenkategorien, Verarbeitungszweck und Speicherdauer. Ich bin mir daher nicht so sicher, ob damit der-/diejenige Seitenbetreibende, welche/r das YouTube-Video einbettet, seiner Informationspflicht gegenüber den Besuchenden seiner Seite ausreichend nachkommt…

Daher habe ich immer mal wieder nach Lösungen für die Blockade von iframes gesucht – und am Ende selber eine gebaut:

Die iframes werden nur teilweise geladen und senden keine Anfrage an die Server Dritter, in einem Hinweistext wird auf die (immer noch unvollständige (s.o.)) Datenschutzerklärung verwiesen. Dadurch hat der Besuchende nun die theoretische Möglichkeit, sich zuerst über die Datenverarbeitung bei dem Anbieter des eingebetteten Inhalts zu informieren und trifft mit dem Anklicken eine willentliche Entscheidung (= Einwilligung gem. Art. 6 (1) lit. a) DSGVO, wobei die Ansprüche an eine Einwilligung (vgl. Art. 7 DSGVO) wohl nich alle erfüllt werden, scheint mir?). Aber dieser erzwungene zweite Klick ist sicherlich besser, als Seitenbesuchende der Informationsweitergabe an Dritte einfach unkommentiert auszusetzen. Denn wenn ein iframe erst einmal geladen wird, ist ganz schön was los: Da werden Netzwerkverbindungen zu diversen "Dritten" aufgebaut, lustig Cookies gesetzt und sicherlich noch weitere informationen (User Agents, gerätespezifische "Fingerabdrücke") übertragen.

Das Video unten zeigt, was im Hintergrund passiert, wenn ein iframe geladen wird 😮

2. Vorgehen (für YouTube und Vimeo)

2.1 iframes nummerieren, mit Klassen versehen und Attribute ändern

Um den iframe anzusprechen und später "entsperren" zu können, benötigt er eine eindeutige Klasse oder ID. Für den Fall, dass eine Seite mehrere iframes enthält, geht das am einfachsten durch das Anlegen einer Variable i = 1, das Anwenden der Funktion auf alle iframes (via .each(), Doku) und das Hochzählen am Ende der Funktion durch i++.

Die iframes von YouTube und Vimeo findet man mit dem Selektor $('iframe[src*="youtu"],iframe[src*="vimeo"]'), die Platzhalter mit Wildcard (*, Doku) erlauben eine generelle Erfassung aller entsprechenden iframes, ohne die Video-URLs genau zu kennen. Die Kürzung auf "youtu" statt "youtube" ist dem hauseigenen URL-Shortener-Dienst youtu.be geschuldet 🙂 Um den iframe einfacher ansprechen zu können, schiebe ich das jeweils selektierte Element mit = $(this) in die Variable video.

Dann vergebe ich via .addClass() (Doku) die Klasse 'video_iframenr'+i, worurch – wenn mehrere iframes vorhanden – diese durchnummeriert werden.

Jeder iframe hat normalerweise eine src-Angabe, woher sonst sollte bekannt sein, welche Inhalte in den iframes angezeigt werden sollen. Hier kann man nun ansetzen, und den Ladevorgang der Inhalte zu manipulieren, indem man das Attribut (.attr, Doku) data-src anlegt und mit dem momentanen Inhalt des src-Attributes befüllt, welches man danach löschen kann (.removeAttr, Doku).

Das Skript sieht also bis hierhin so aus:

jQuery

$(document).ready(function(){
   var i = 1;
   $('iframe[src*="youtu"],iframe[src*="vimeo"]').each(function(){
      var video = $(this);
      video.addClass('video_iframenr'+i).attr('data-src',video.attr('src')).removeAttr('src');
      i++;
   });
});

Aus z.B.

HTML

<iframe src="https://www.youtube.com/embed/arPqFrpObtg" ></iframe>

wird so

HTML

<iframe class="video_iframenr1" data-src="https://www.youtube.com/embed/arPqFrpObtg" ></iframe>

Dieser iframe hat keinen Inhalt, da er kein gültiges src-Attribut hat. Es kann also auch keine Verbindung mit YouTube- oder Vimeo-Servern aufgebaut werden.

Nicht ganz einschätzen kann ich das Timing. $(document).ready(); sollte eigentlich bereits beim abgeschlossenen Ladevorgangs des DOM abgefeuert werden, also wenn die iframe-Inhalte noch nicht geladen sind. Zu diesem Zeitpunkt sollten also noch keine Anfragen an den Anbieter herausgegangen sein und keine Cookies des Anbieters angekommen sein?

2.2 Overlay basteln

Als nächstes benötige ich ein Overlay, der die entsprechenden Infos und (später) den "Entsperr"-Button enthält. Dafür nutze ich .wrap() (Doku), um den iframe mit einem div mit der Klasse video_iframeoverlay zu umschließen. Dann kann ich dieses umschließende div via .parent() (Doku) selektieren und mit .append() (Doku) einen Textknoten für meinen Text einbauen.

In diesem Hinweistext würde ich gerne den Link zum Video anbieten. Der steht natürlich momentan im data-src-Attribut, daher lege ich diesen in der Variable sourceLink ab und bereinige diesen via .replace() um den URL-Bestandteil -nocookie. Der ist in den Fällen vorhanden, wenn ein YouTube-Embed-Code mit dem "erweiterten Datenschutzmodus" generiert wurde; er ist bei einer Verlinkung aber ungünstig, da das Linkziel nicht gefunden wird. Damit der Link nicht zu lang wird, kann man ihn in einer weiteren Variable (hier: sourceLinkShort) auf 30 Zeichen gekürzt mit einem "…" am Ende ablegen. Diese beiden Variablen werden dann im Hinweistext nach dem Strickmuster <a href="[sourceLink]">[sourceLinkShort]</a> genutzt. Den eigentlichen Hinweistext formuliere ich später noch ausführlich 🙂

jQuery

      ...

      var sourceLink = video.attr('data-src').replace('-nocookie', '');
      var sourceLinkShort = jQuery.trim(sourceLink).substring(0, 30) + "...";
      video.wrap('<div class="video_iframeoverlay"></div>');
      video.parent('.video_iframeoverlay').append('<p>Hinweistext mit Link <a rel="noopener" target="_blank" href="' + sourceLink + '">' + sourceLinkShort + '</a>.</p>');

      ...

Aus z.B.

HTML

<iframe src="https://www.youtube.com/embed/arPqFrpObtg" ></iframe>

wird so

HTML

<div class="video_iframeoverlay">
   <iframe class="video_iframenr1" data-src="https://www.youtube.com/embed/arPqFrpObtg" ></iframe>
   <p>Hinweistext mit Link <a rel="noopener" target="_blank" href="https://www.youtube.com/embed/arPqFrpObtg">https://www.youtube.com/embed/...</a>.</p>
</div>

2.3 Unlock-Button

Um den eigentlichen iframe-Inhalt nun per Knopfdruck nachladen zu können, benötigt man einen Button (bzw. einen später via CSS als Button gestalteten Link), mit dem das data-src-Attribut wieder zum src-Attribut gemacht wird. Etwas kniffelig ist, dass jeder Unlock-Button ja nur "seinen" iframe (und nicht etwa alle auf der Seite) entsperren soll. Dafür machen wir uns wieder die durchnummerierte iframe-Nummer bzw. die entsprechend nummerierte Klasse zunutze. Also füge ich folgenden Link zum Hinweistext (= innerhalb von video.parent('.video_iframeoverlay').append('<p>...</p>'); hinzu:

<a id="unlockiframe" class="video_iframenr' + i + '">Inhalt laden</a></p>');

So bekommt auch jeder dieser Links/Buttons die gleiche Klasse wir der iframe, zu dem er gehört. Nun muss das Ensperren gebastelt werden:

Jedes Element mit der ID unlockiframe und einer passenden Klasse soll .on() .click() (Doku, Doku) den dazugehörigen iframe entsperren. Dafür schiebe ich die Klasse des Buttons in die Variable relatediframe, und schreibe bei dem iframe mit der selben Klasse ($('iframe.' + relatediframe)) das momentane data-src-Attribut zurück in das src-Attribut. Das data-src-Attribut kann danach gelöscht werden.

Nun muss noch das Overlay entfernt werden: Dafür wird der $('iframe.' + relatediframe) mit .unwrap() (Doku) von dem umschließenden div befreit und der Hinweistext (der sich in einem einzigen p-Tag befinden sollte!) via $('iframe.' + relatediframe + ' + p') (ist ja ein Folge-Element des iframes) selektiert und entfernt (.remove(), Doku):

jQuery

$('#unlockiframe[class*=video_iframenr]').on('click', function () {
   var relatediframe = $(this).attr('class');
   $('iframe.'+relatediframe).attr('src',$('iframe.'+relatediframe).attr('data-src'));
   $('iframe.'+relatediframe).removeAttr('data-src');
   $('iframe.'+relatediframe).unwrap();
   $('iframe.'+relatediframe+' + p').remove();
});

3. Fertig 🙂

Eine kommentierte Gesamtversion des Skriptes incl. m.E. sinnvollem Hinweistext sieht dann so aus:

jQuery

// iframe Block

$(document).ready(function(){

   // Fuer den Fall, dass mehrere iframes vorhanden sind i zum hochzaehlen anlegen,
   var i = 1;

   // bei jedem YT- und Vimeo-iframe (Filter auch fuer youtu.be)
   $('iframe[src*="youtu"],iframe[src*="vimeo"]').each(function(){

      // iframe als Variable ablegen,
      var video = $(this);

      // dem iframe eine nummerierte Klasse geben, das data-src mit src-Attribut befuellen und src loeschen.
      video.addClass('video_iframenr'+i).attr('data-src',video.attr('src')).removeAttr('src');

      // Links fuer das Overlay generieren, nocookie aus der URL entfernen, auf 30 Zeichen kuerzen
      var sourceLink = video.attr('data-src').replace('-nocookie', '');
      var sourceLinkShort = jQuery.trim(sourceLink).substring(0, 30) + "...";

      // Ein Overlay mit Text und Unlock-Button (mit gleicher Klasse wie der iframe) einblenden
      video.wrap('<div class="video_iframeoverlay"></div>');
      video.parent('.video_iframeoverlay').append('<p>Hier wurde ein einegebetteter Medieninhalt (<a rel="noopener" target="_blank" href="' + sourceLink + '">' + sourceLinkShort + '</a>) blockiert. Beim Laden oder Abspielen wird eine Verbindung zu den Servern des Anbieters hergestellt. Dabei k&ouml;nnen dem Anbieter personenbezogene Daten mitgeteilt werden. Weitere Informationen finden Sie in unseren <a href="/datenschutzerklaerung" rel="noopener" target="_blank" title="Datenschutzerkl&auml;rung">Datenschutzhinweisen</a> im Abschnitt "2.5 Eingebettete Medieninhalte und Dienste von anderen Webseiten".<a id="unlockiframe" class="video_iframenr' + i + '">Inhalt laden</a></p>');

      // Hochzaehlen, falls mehrere iframes vorhanden sind.
      i++;
   });

   // Bei Klick auf einen Unlock-Button ...
   $('#unlockiframe[class*=video_iframenr]').on('click', function () {

      // ...den Klassennamen des Buttons (= Klassenname des blockierten iframes) auslesen ...
      var relatediframe = $(this).attr('class');

      // ...und beim entspr. iframe data-src in src zurueckwandeln und data-src loeschen.
      $('iframe.'+relatediframe).attr('src',$('iframe.'+relatediframe).attr('data-src'));
      $('iframe.'+relatediframe).removeAttr('data-src');

      // Am Ende das Overlay (unwrap) und Text (remove) loeschen.
      $('iframe.'+relatediframe).unwrap().siblings('p').remove();
   });
});

Das kann man sicherlich optimieren und besser strukturieren?
Tipps gerne in die Kommentare! Danke!

Das ganze benötigt nun noch ein entsprechendes

4. Design

Alle iframes, egal ob src oder data-src auf Youtube oder Vimeo verweisen, haben eine Breite von 100%, eine maximale (natürlich von Seitendesign bzw. Theme abhängig zu wählende) Breite (hier: 1000px) und eine zu der max. Breite passende Höhe von 562px (das gewünschte Seitenverhältnis von 16:9 ergibt 1000/16=562 😉 ). Die !important-Angaben überschreiben die evtl. im iframe-Code inline vorhandenen Größenangaben.

CSS

iframe[src*="youtu"], iframe[src*="vimeo"],
iframe[data-src*="youtu"], iframe[data-src*="vimeo"] {
   width: 100% !important;
   max-width: 1000px;
   height: 562px !important;
}

Das Overlay und die Zentrierung des Hinweistextes mache ich via Spalten-Flexboxing, der Farbverlauf ist natürlich Geschmackssache 🙂

CSS

.video_iframeoverlay {
   width: 100% !important;
   max-width: 1000px;
   height: 562px !important;
   position:relative;
   background-image: linear-gradient(170deg,rgba(100, 0, 0,0.25),rgba(0,0,0,0.05) 70%);
   position:relative;
    -webkit-display:flex;
       -moz-display:flex;
        -ms-display:flex;
            display:flex;
    -webkit-flex-direction:column;
       -moz-flex-direction:column;
        -ms-flex-direction:column;
            flex-direction:column;
    -webkit-justify-content:center;
       -moz-justify-content:center;
        -ms-justify-content:center;
            justify-content:center;
}

Text und Button werden wie folgt positioniert und gestaltet:

CSS

.video_iframeoverlay p {
   padding: 0 1.5rem;
   text-align: center;
   margin: 0;
   z-index: 2;
   position:absolute;
}
.video_iframeoverlay #unlockiframe {
   background: #bf304a;
   color: #fff;
   padding: 0.5rem;
   border-radius: 0.5rem;
   cursor: pointer;
   display: block;
   margin: 0.5rem auto 0;
   width: 12rem;
   max-width:90%;
   z-index: 2;
}
.video_iframeoverlay #unlockiframe:hover {
   background:#30a3bf;
}

Wer will, kann z.B. via .video_iframeoverlay::before noch eine Grafik oder FontAwesome-Icons o.ä. hinein basteln.

  1. Selber testen? Hier klicken!
  2. Skript / Design herunterladen? Die Dateien videoblock.js und videoblock.css müssen im <head> einer HTML-Datei/-Seite eingebunden werden, ebenso wie eine Kopie oder CDN-Version von jQuery.
  3. Komplettpaket? Das Ganze gibt es hier mit relativen Links, so dass es auch lokal funktionieren sollte: Download

5. Funktionierts?

Diverse Tests haben weder einen Verbindungsaufbau mit YouTube- bzw. Vimeo-Servern noch deren Cookies nachweisen können. Das ganze habe ich mal versucht, in einem Video aufzuzeichnen:

Test des iframe-Blockers im Video

Getestet in Firefox, Chrome, Edge, Internet Explorer (v.11 🙂 ), jew. nur unter Windows als OS.

6. Disclaimer und Hinweise

  • Ob das wirklich wirklich funktioniert, auch auf Mobilgeräten, unter MacOS und Linux,
  • ob das die Ansprüche der DSGVO (mehr) erfüllt,
  • ob das überhaupt notwendig ist,

weiß ich alles nicht so genau. Es scheint für meine Ansprüche aber ausreichend zu funktionieren. Trotzdem erlaube ich mir folgenden Disclaimer:

Ich bin kein Web-Entwickler, sondern Bastler aus dem #TeamHalbwissen. Die Nutzung und Weiterentwicklung ist daher "auf eigene Gefahr". Ob dieses Skript tut, was es soll und ob es überhaupt notwendig ist, kann ich nicht abschließend beurteilen.

Viel Spaß beim Basteln! 🙂 Hinweise zu Fehlern oder möglichen Verbesserungen nehme ich sehr gerne entgegen.

"iframe-Blocker" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/iframe-blocker/feed/ 11
CSS Schalter https://medienmarmela.de/css-schalter/ https://medienmarmela.de/css-schalter/#respond Mon, 16 Sep 2019 20:07:26 +0000 https://medienmarmela.de/?p=2232 1. Worum es geht…

Ich nutze den "Hidden Checkbox Hack" und CSS, um iOS-like Kippschalter (Toggles, Switches) zu gestalten. Diese sehen am Ende so aus:


2. Das HTML

Das HTML ist ein umwrappendes div, ein Checkbox-input-Element mit dazugehörigem label und zwei spans, die ich mit CSS gestalten kann:

HTML

<div class="switch">
  <input id="switch1" type="checkbox">
  <label for="switch1">
    <span class="buttonbackground">
      <span class="buttonslider"></span>
    </span>
  </label>
</div>

Zu beachten:

  1. Das input-Element muss vom Typ "Checkbox" sein und über die id (hier: switch + n) mit dem label (via for="") verknüpft sein.
  2. labels dürfen keine Block-Elemente beinhalten (also bitte keine divs, Buttons oder ähnliches verwenden): Daher spans.

3. Das Design

Das span mit der Klasse buttonbackground bildet die Hintergrundfläche des Schalters. Es soll in der "Aus"-Variante

  • hellgrau und
  • doppelt so breit wie hoch sein,
  • einen border-radius (1/2 Höhe für eine gleichmäßige Rundung) und
  • zwei "Innenschatten" (inset) haben.
  • Die Positionierung relative ist notwendig, da die beweglichen Knöpfe (Klasse: buttonslider) gleich eine absolute Positionierung erhalten.
  • Die Zentrierung in einem evtl. vorhandenen umschließenden Element passiert via margins (links und rechts auto).
  • Und da das span ab Werk ein Inline-Element ist, bekommt es noch ein display:block; mitgegeben.

CSS

.switch {
  background: #aaa;
  padding: 2rem;
  width: 9rem;
  margin: 0 auto;
  border-radius: 0.75rem;
}

.buttonbackground {
  background-color:#ccc;
  width: 5rem;
  height: 2.5rem;          /* = width/2 */
  border-radius: 1.25rem;  /* = height/2 */
  box-shadow:
    0 2px 2px rgba(0,0,0,0.5) inset,
    0 -2px 0px rgba(255,255,255,0.5) inset;
  position: relative;
  margin: 0.5rem auto;
  display:block;
}

Ach ja, das input muss noch unsichtbar gemacht werden …

CSS

#switch1 {
  display:none;
}

… und dann kann der eigentliche Schalter gestaltet werden:

Das span mit der Klasse buttonslider soll

  • ebenfalls hellgrau (aber heller als der Hintergrund),
  • und etwas größer sein, als der buttonbackground hoch ist.
  • Darüber hinaus bekommt es eine Kontur (border) und
  • einen border-radius von 50% (für "rund").
  • Es wird absolute, left:0; und etwas nach oben (top:-.15rem;) positioniert,
  • bekommt einen Schlagschatten und
  • ebenfalls ein display:block;

und sieht dann so aus:

CSS

.buttonslider {
  background:#ddd;
  width:2.7rem;
  height:2.7rem;
  border-radius:50%;
  border:1px solid #aaa;
  position: absolute;
  left:0;
  top:-0.15rem;
  box-shadow: 0 4px 3px rgba(0,0,0,0.3);
  display:block;
}

Ein aktivierter Schalter unterscheidet sich in der Hintergrundfarbe des buttonbackground und Position sowie Hintergrundfarbe des buttonslider. Die Adressierung des "On"-Status basiert – wie erwähnt – auf dem "Checkbox Hack". Dabei macht man sich zunutze, dass die Checkbox "gechecked" wird, auch wenn man nicht auf das Anhak-Kästchen, sondern nur auf das Label klickt. Das "Kunststück" besteht in diesem Beispiel darin, die Klassen buttonbackground und buttonslider für den Fall zu adressieren, dass die Checkbox gechecked ist. Das geht mittels der Pseudo-Klasse :checked (MDN Doku) und einem "angrenzenden" (+) oder einem "allgemeinen Geschwisterselektor" (~).

CSS

/* (IDs) ersetzen! */

#switch(ID):checked + label .buttonbackground {
  background-color:#64b464;
}
#switch(ID):checked + label .buttonslider {
  left: 2.5rem;         /* = buttonbackground-width / 2 */
  background:#eee;
}

Ein "checked" und klickbarer Schalter sieht dann so aus:

4. Feinschliff

Damit Maus-Benutzern klarer wird, dass hier geklickt werden kann, ändert man am besten den Cursor via

CSS

/* (ID) ersetzen! */

#switch(ID) + label .buttonbackground:hover {
  cursor:pointer;
}

Dann kann man buttonbackground und buttonslider noch mit CSS Transitions anmimieren:

CSS

.buttonbackground {
  /*bisheriges CSS*/
  ...
  transition: background-color 0.2s ease;
}
.buttonslider {
  /*bisheriges CSS*/
  ...
  transition: all 0.2s ease;
}

Fertig:

Viel Spaß damit 🙂

Eine HTML-Datei mit dem kompletten HTML und CSS gibt es hier.

"CSS Schalter" von Martin Smaxwil ist unter einer CC BY 4.0-Lizenz veröffentlicht.
Darüber hinausgehende Hinweise zu Bestandteilen wie Code Snippets u.ä. findet man hier.

]]>
https://medienmarmela.de/css-schalter/feed/ 0