Kategorien
JavaScript & jQuery Programmierung Webdesign

Permissions-API: Berechtigungen gebündelt verwalten

Viele der neuen JavaScript-APIs, wie Geolocation oder Notifications, erfordern besondere Berechtigungen, die der Nutzer einer Website einräumen muss. Jede API bringt dabei ihre eigenen Methoden mit, um Rechte abzufragen und einzuräumen. Aufgrund der Vielzahl dieser neuen Schnittstellen, die aufgrund besonderer Browserfunktionen die Zustimmung des Nutzers einholen müssen, ist es schwierig, einen Überblick über zugelassene und blockierte APIs zu bekommen. Mit der neuen Permissions-API hat man endlich die Berechtigungen aller APIs im Blick und kann den jeweiligen Status abfragen.

permissionsapi-teaser_DE

Zentrale Verwaltung aller Berechtigungen

Gerade wenn für eine JavaScript-Anwendungen erforderlich ist, dass mehrere APIs zugelassen sind, kann die neue Permissions-API hilfreich sein. Über die „query()“-Methode des neuen „permissions“-Objekts kann man einzelne APIs auf ihren Berechtigungsstatus prüfen.

navigator.permissions.query({
  name: "geolocation"
}).then(function(e) {
  console.log(e.status);
});

Über die Methode „query()“ werden als Objektliteral verschiedene Optionen übergeben. In erster Linie wird der Name der API – im Beispiel „geolocation“ zur Standortbestimmung – angegeben. Je nach API stehen einem weitere Optionen zur Verfügung, die in die Prüfung mit einbezogen werden können.

permissions-api_berechtigungen
Übersicht der zugelassenen und blockierten Berechtigungen für eine Domain

Über die Promises-Methode „then()“ wird der Status der Berechtigung zurückgegeben. Der Status kann „prompt“, „granted“ oder „denied“ sein. Hat man eine API bislang weder zugelassen noch abgelehnt, wird „prompt“ wiedergegeben. Wurde vom Browser bereits eine Berechtigungsanfrage gestellt und akzeptiert, wird „granted“ ausgegeben. Wurde eine solche Anfrage verweigert, wird „denied“ zurück gegeben.

permissions-api_geolocation
Berechtigungsanfrage der Geolocation-API

Ein weiterer Vorteil der Permissions-API ist die Möglichkeit, einen Status abzufragen, ohne dass eine Berechtigungsanfrage im Browser eingeblendet wird. Für die Geolocation-API gibt es beispielsweise keine Möglichkeit, einfach nur den Berechtigungsstatus abzufragen.

Per Event auf Statusänderungen reagieren

Mit dem „change“-Event ist es zudem möglich, auf Veränderung des Status einer API zu reagieren. Wird beispielsweise die Berechtigung für eine API vom Nutzer wieder zurückgenommen, lässt sich das innerhalb eines Events unmittelbar feststellen.

navigator.permissions.query({
  name: "geolocation"
}).then(function(e) {
  e.addEventListener("change", function() { 
    console.log("Status der Geolocation-API: ", this.status); 
  }, false);
});

Im Beispiel wird dem Rückgabewert „e“ per „addEventListener()“ das „change“-Event zugewiesen. Dieses löst die dort definierte Funktion immer dann aus, wenn sich dieser Rückgabewert ändert – zum Beispiel dann, wenn eine Berechtigung wieder zurückgenommen wird.

Unterstützte APIs und Browser

Derzeit werden die Geolocation-, Notifications-, Push- und Midi-APIs unterstützt. Der Chrome ab Version 43 ist derzeit der einzige Browser, der die Permissions-API unterstützt.

(dpe)

Kategorien
Design JavaScript & jQuery Webdesign

Plumin.js verändert Schriften mit JavaScript

Dank CSS gibt es zahlreiche Möglichkeiten, Texte zu gestalten. Mit Webfonts kann man zudem individuelle Schriften verwenden. Die JavaScript-Bibliothek Plumin.js geht noch einen Schritt weiter. Sie erlaubt es, eine Schrift innerhalb einer Website zu manipulieren. Schriftzeichen können dabei durch individuelle Formen ausgetauscht werden.

pluminjs-teaser_DE

Schrift auswählen und Buchstaben manipulieren

Während man bei den meisten JavaScript-Bibliotheken jene HTML-Elemente ansprechen muss, auf welche die Bibliothek angewendet werden soll, ist das bei Plumin.js nicht nötig. Hier wird eine Schriftart angesprochen, die mit der Bibliothek manipuliert werden soll. An allen Verwendungsstellen dieser Schriftart im Dokument wirken sich die gemachten Veränderungen aus.

pluminjs

Als erstes wird eine nicht sichtbare HTML5-Zeichenfläche erstellt, die intern von Plumin.js genutzt wird.

<canvas id="plumin-canvas" width="1024" height="1024" hidden />

Im nächsten Schritt wird die Zeichenfläche der Bibliothek zur Verfügung gestellt.

plumin.paper.setup("plumin-canvas");

Anschließend wird eine Funktion erstellt, in der die zu manipulierende Schrift, die zu ersetzenden Zeichen und die Formen, welche die Zeichen ersetzen sollen, definiert werden.

(function(p) {
  …
})(plumin)

Alles, was nun folgt, muss sich innerhalb dieser Funktion befinden. Als erstes wird dort der Name der Schrift angegeben, die verändert werden soll.

var schrift = p.Font({
  familyName: "NameDerSchrift",
  ascender: 800,
  descender: -200
});

„familyName“ definiert also den Namen der Schrift. Optional kann man noch Werte für die Ober- und Unterlänge („ascender“ und „descender“) der Schrift angeben. Das hilft bei der späteren Positionierung der Formen.

Als nächstes legen wir die Zeichen, die durch eigene Formen ausgetauscht werden sollen, fest. Dazu wird für jedes Zeichen ein Name („name“) vergeben. Über die Eigenschaft „unicode“ gibt man das Zeichen und über „advanceWidth“ die Breite des Zeichens an.

var A = p.Glyph({
  name: "A",
  unicode: "A",
  advanceWidth: 500
});

Im Beispiel wird der Buchstabe A der Variablen „A“ hinzugefügt. Im dritten Schritt legen wir eine Form an, welche ein Zeichen ersetzen soll.

var formA = p.Path.RegularPolygon({
  center: [250, 350],
  sides: 3,
  radius: 350
});

formA.rotate(180);

Hier erzeugen wir per „RegularPolgyon()“ ein gleichseitiges Dreieck. Der Mittelpunkt der Form wird per „center“ definiert, die Anzahl der Seiten per „sides“ und der Radius per „radius“. Dieses Dreieck drehen wir dann per „rotate()“, so dass die Spitze nach oben zeigt.

Anschließend weisen wir die Form dem Buchstaben A zu.

A.addContour(formA);

Im letzten Schritt fügen wir alle manipulierten Buchstaben der Schrift hinzu.

font.addGlyphs([A]).updateOTCommands().addToFonts();

Überall im Dokument, wo die Schrift zum Einsatz kommt, ersetzen wir so nun alle großen As durch das Dreieck, welches mittels Plumin.js erzeugt wurde.

pluminjs_beispiel
Beispiel für einen per Plumin.js manipulierten Text

Schöne Spielerei, aber nützlich in passenden Projekten

Man kann Plumin.js natürlich sehr schön als Spielerei einsetzen, wie es auf der Website der Bibliothek gemacht wurde. Man kann es aber auch einsetzen, um bestimmte Zeichen mit eigens erstellten Icons zu ersetzen, um auf einen Webfont verzichten zu können. Plumin.js steht unter der MIT-Lizenz und kommt ohne zusätzliche JavaScript-Bibliotheken wie jQuery aus. Die MIT-Lizenz erlaubt den kostenfreien Einsatz für persönliche und kommerzielle Zwecke, also auch in Kundenprojekten.

Die Dokumentation auf der Website ist recht dürftig. Aber sowohl auf der Website als auch im Downloadpaket sind einige Beispiele integriert, die einen guten Einblick in die Bibliothek geben.

(dpe)

Kategorien
HTML/CSS JavaScript & jQuery Webdesign

So gehts: Kreismarkierungen mit Traceit.js und jQuery

Bei klassischen Präsentationen auf Whiteboards oder Flipcharts werden wichtige Schlagworte gerne mal eingekreist, um sie hervorzuheben. Mit dem jQuery-Plugin „traceit.js“ lässt sich diese Möglichkeit der Hervorhebung und Markierung auf Webprojekte übertragen. Ohne großen Aufwand werden beliebige HTML-Elemente mit einem Kreis umzingelt, der an von Hand gezeichnete Einkreisungen erinnert. Vor allem bei Tutorials lassen sich mit „traceit.js“ sehr einfach und effektiv Bereiche auszeichnen, auf die man aufmerksam machen möchte.

traceitjs-teaser_DE

 Traceit.js einbinden und einsetzen

Um „traceit.js“ einzusetzen, muss neben jQuery auch die JavaScript-Bibliothek „Raphaël“ eingebunden sein, da das Plug-in für die Kreise die SVG-Zeichenmethoden von „Raphaël“ verwendet. Alternativ steht auch eine kombinierte JavaScript-Datei zur Verfügung, die sowohl das Plug-in als auch die „Raphaël“-Bibliothek beinhaltet.

traceit

Sind die JavaScript-Dateien eingebunden, kann ein beliebiger Inhalt wie folgt eingekreist werden.

<p><span id="schlagwort">Schlagworte</span> sollten immer auffallen.</p>

Im Beispiel soll der Inhalt eines „<span>“-Elementes mit einer Einkreisung versehen werden. Dazu wird eine ID vergeben, die „traceit.js“ übergeben wird, um das Plug-in darauf anzuwenden.

$("#schlagwort").trace();

traceitjs-standard
Standard-Einkreisung

Das Plug-in sorgt dafür, dass der Inhalt entsprechend seiner Größe eingekreist wird. Dabei sind die Einkreisungen nicht auf Textelemente beschränkt. Auch auf Bilder lässt sich „traceit.js“ anwenden. Problematisch wird es lediglich, wenn mehrere Wörter eingekreist werden sollen und diese sich über mehrere Zeilen erstrecken. Ansonsten berechnet das Plug-in sehr zuverlässig die Größe des einzukreisenden Elementes.

Aussehen der Umkreisung anpassen

Standardmäßig werden Elemente mit einer grünen, dünnen Linie versehen. Per „traceOpt“ lässt sich das Aussehen der Linie jedoch individuell anpassen. Neben Linienfarbe und -stärke kann auch eine Füllfarbe angegeben werden.

$("#schlagwort").trace({
  traceOpt {
    "stroke": "red";
    "stroke-width": 5,
    "stroke-opacity": 0.5,
    "fill": "yellow",
    "fill-opacity": 0.25,
    "z-index": -1,
    "stroke-linecap": "round"
  }
});

Im Beispiel wird eine fünf Pixel starke rote Linie gezeichnet. Diese hat eine 50-prozentige Opazität. Gefüllt wird der Kreis mit einer gelben Fläche mit einer 25-prozentigen Opazität. Auch die Position der Linie kann per „z-index“ definiert werden. Für die Darstellung der Linie können alle CSS-Eigenschaften verwendet werden, die das SVG-Format kennt. So kann man zum Beispiel per „stroke-linecap“ die Linien mit abgerundeten Anfangs- und Endpunkten versehen. Das macht die Umkreisung noch etwas realistischer.

traceitjs-individuell
Individuelle Einkreisung

Standardmäßig wird die Linie über das jeweilige Element gelegt. Im Beispiel wird sie dahinter platziert. Von Hand gezeichnete Kreise liegen logischerweise immer über den Inhalten. Hier und da kann es aufgrund der Lesbarkeit – vor allem bei Texten – sinnvoll sein, den Kreis hinter den Inhalt zu zeichnen. Vor allem bleibt der Inhalt dann auch auswählbar.

Da „traceit.js“ handgezeichnete Kreise nachahmt, stimmen Anfangs- und Endpunkt des Kreises beziehungsweise der Ellipse nicht überein. Mit der Option „traceCanvasPadding“ kann man die Differenz zwischen den beiden Punkten angeben. Bei einem Wert von 0 wird quasi eine perfekte Ellipse gezeichnet. Bei Werten darüber wird die Ellipse nicht geschlossen. Per „redrawSpeed“ wird die Geschwindigkeit der Kreiszeichnung angegeben. Für eine realistische Darstellung sollte man „traceCanvasPadding“ und „redrawSpeed“ immer gemeinsam ändern. Denn je schneller man mit der Hand etwas einkreist, desto höher ist meist der Abstand zwischen Anfangs- und Endpunkt des Kreises beziehungsweise der Ellipse.

Umkreisung als SVG-Element gezeichnet

Die Umkreisung selbst wird – wie bereits erwähnt –  als SVG-Element per „Raphaël“ realisiert. Dazu wird ein SVG-Element innerhalb eines „<div>“-Containers platziert. Dieser Container erhält eine ID bestehend aus der ID des zu umkreisenden Elementes – im Beispiel „schlagworte“ – gefolgt von der Zeichenkette „_wrap“. Per „traceDivPref“ kann man auch die Benennung dieses Containers ändern. Das kann sinnvoll sein, wenn die generierten IDs bereits anderweitig vergeben sind.

Die Positionierung der Umkreisung erfolgt per Stylesheets über den „<div>“-Container. Da dieser über die ID erreichbar ist, können Position und Aussehen der Umkreisung auch individuell über CSS noch geändert werden. So ist das SVG-Element standardmäßig so per Inline-CSS ausgezeichnet, dass überstehende Inhalte ausgeblendet werden („overflow: hidden“). Das führt teilweise dazu, dass das Ende der Linie nicht immer dargestellt wird, da es aus der Zeichenfläche ragt. Per Stylesheets lässt sich dies überschreiben.

#schlagwort_wrap svg {
  overflow: visible !important;
}

So erreicht man, dass die gezeichnete Ellipse immer komplett sichtbar ist.

Verhalten für die Umkreisung definieren

Um eine Einkreisung wieder verschwinden zu lassen, genügt ein Klick darauf. Dann wird diese dezent ausgeblendet. Alternativ kann man die Linienzeichnung auch per „trigger()“-Methode ausblenden.

$("#schlagwort").trigger("hide.trace");

„traceit.js“ stellt zudem drei Callbacks zur Verfügung. Diese ermöglichen es, eine Funktion auszuführen, wenn die Animation abgeschlossen ist („onEndTrace“), wenn ein Klick auf die Einkreisung erfolgt („onClick“) oder wenn diese ausgeblendet wurde („onHide“).

$("#schlagwort").trace({
  onEndTrace: function() {
    alert("Umkreisung abgeschlossen.");
  }
});

Im Beispiel wird ein Alert ausgegeben, sobald die Einkreisung gezeichnet wurde.

Fazit

„traceit.js“ ist schnell eingebunden und angewendet. Gerade wenn es darum geht, die Aufmerksamkeit auf ein bestimmtes Element zu lenken, hat man mit dem Plug-in eine einfache Möglichkeit, dies dezent und ansprechend zu tun.

(dpe)

Kategorien
Design JavaScript & jQuery Webdesign

RulersGuides.js: Hilfslinien wie in Photoshop

Das Lineal und die Möglichkeit, Hilfslinien zu ziehen, sind unverzichtbare Funktionen in Photoshop und anderen Bildbearbeitung- und Zeichenanwendungen. Auch auf einer Website kann es hilfreich sein, Abstände zu messen sowie Ausrichtungen mittels Hilfslinien zu prüfen. RulersGuides.js fügt per Bookmarklet ein solches Lineal mit entsprechenden Funktionen auf jede Website ein.

rulersguidejs-teaser_DE

Bekannte Funktionen aus Photoshop

Um RulersGuides.js nutzen zu können, muss das JavaScript als Bookmarklet im Browser abgelegt werden. Anschließend fügt es auf jeder beliebigen Website per Aufruf des Bookmarklets ein Lineal ein, wie man es aus Photoshop kennt. Die Einheit des Lineals ist logischerweise in Pixel angegeben.

rulersguides2
X- und Y-Koordinaten sowie Breite und Höhe der gezogenen Rechtecke

Aus dem Lineal lassen sich horizontale und vertikale Hilfslinien ziehen und auf der Website platzieren. Über ein Menü stehen einem verschiedene Funktionen zur Verfügung. Wer Hilfslinien zum Beispiel einsetzt, um damit Abstände, Größen und Positionen zu messen, wird die Funktion „Show detailed info“ ganz hilfreich finden. Sie zeigt zu jedem Rechteck, das per Hilfslinien gezogen wurde, die X- und Y-Koordinaten sowie die Breite und Höhe an.

Verschiedene Darstellungsmöglichkeiten

Die Lineale sind in der Normaleinstellung an den linken und oberen Rand des HTML-Dokumentes gebunden. Das heißt, beim Scrollen nach unten verschwindet das horizontale Lineal. Per „Unlock rulers“ können die Lineale so dargestellt werden, dass sie immer sichtbar bleiben.

Außerdem ist es möglich, die Lineale und die Hilfslinien unabhängig voneinander ein- und auszublenden. Natürlich ist es auch möglich, alle Hilfslinien auf einmal zu löschen.

Hilfslinien an DOM-Elemente ausrichten

Für eine präzise Platzierung von Hilfslinien gibt es die Funktion „Snap to DOM“. Hierbei werden die Hilfslinien immer an den Außenkanten der DOM-Elemente ausgerichtet. So können Hilfslinien beispielsweise exakt so platziert werden, dass sie ein bestimmtes Element – zum Beispiel einen „<p>“-Absatz oder ein „<section>“-Bereich – umschließen.

Alternativ gibt es die Möglichkeit, Hilfslinien in festen Abständen zu platzieren – zum Beispiel alle 100 Pixel von oben und alle 50 Pixel von links. So kann man sich schnell ein Raster bauen.

Hilfslinien speichern

Sobald eine Seite neu geladen wird, verschwinden die Hilfslinien wieder. Es gibt aber die Möglichkeit, alle gesetzten Hilfslinien zu speichern. Sie lassen sich dann aus einem Menü heraus wieder abrufen und darstellen.

Die Hilfslinien werden immer pro Domain gespeichert. Man kann gespeicherte Hilfslinien also nicht für verschiedene Websites nutzen, sondern immer nur innerhalb eines Projektes.

Bookmarklet für alle Browser

Das Bookmarklet RulersGuides.js gibt es in zwei Ausführungen. Eine ist für den Internet Explorer ab Verison 7 (allerdings ohne Möglichkeit, Hilfslinien zu speichern), die andere Variante für alle anderen gängigen Browser.

Link zum Beitrag

RulersGuides.js steht unter der bekannten MIT-Lizenz, die den kostenfreien Einsatz zu privaten und kommerziellen Zwecken erlaubt. Eine kurze Anleitung zur Bedienung, in der auch die Tastenkombinationen zum Aufruf der Funktionen vorgestellt werden, ist ebenfalls verfügbar.

Kategorien
Design HTML/CSS JavaScript & jQuery Webdesign

JavaScript: Ramjet transformiert HTML-Elemente untereinander

Transformationen und Animationen kommen dank CSS3 und HTML5 auf immer mehr Websites zum Einsatz. Die JavaScript-Bibliothek ramjet nutzt die CSS3-Möglichkeiten und ermöglicht es, ein beliebiges HTML-Element in ein anderes transformieren zu lassen.

javascript-ramjet-teaser_DE

Einfacher Effekt mit großer Wirkung

Im Grunde ist der Transformationseffekt, den ramjet verwendet, ein einfacher. Man nehme zwei beliebige HTML-Elemente –Bilder, Texte und SVG-Grafiken können dabei kombiniert werden; auch die Position und Größe der Elemente spielt keine Rolle. Über einen einfachen JavaScript-Aufruf erreicht man, dass das erste Element per Animation in das zweite transformiert wird.

ramjet_beispiel
Beispiel für eine Transformationsanimation, wie man sie von iOS kennt

Dabei wird die Größe und Position des ersten Elementes an das zweite angeglichen. Gleichzeitig wird das erste Element aus- und das zweite, dahinter angeordnete eingeblendet. In einer zügigen Animation erreicht man einen eindrucksvollen Transformationseffekt. Je länger die Animationsdauer ist, desto deutlicher wird die Art und Weise der Tranformation und verliert ein wenig am Wow-Effekt.

Elemente per ID ansprechen und tranformieren

ramjet ist sehr einfach angewendet. Ist die JavaScript-Bibliothek eingebunden, lassen sich die beiden zu transformierenden HTML-Elemente aufgrund ihrer ID ansprechen.

ramjet.transform(elementA, elementB);

Im Beispiel wird ein Element mit der ID „elementA“ in das Element mit der ID „elementB“ transformiert. In dieser einfachen Variante bleiben die beiden Ausgangselemente als solche sichtbar.

Will man erreichen, dass das zweite Element erst durch die Transformation erscheint und das erste Element dadurch ausgeblendet wird, muss man etwas mehr JavaScript in den Effekt investieren.

elementA.style.opacity = 0;

ramjet.transform(elementA, elementB, {
  done: function () {
    elementB.style.opacity = 1;
  }
});

Für das Beispiel muss zunächst das zweite Element („elementB“) versteckt werden. Das erreicht man zum Beispiel, indem man seine Sichtbarkeit (per CSS-Eigenschaft „opacity“) auf Null setzt. Anschließend wird per JavaScript zu Beginn der Transformationsanimation auch das erste Element („elementA“) versteckt. Dann leiten wir per „ramjet.transform()“ die Transformation ein, indem wir die beiden IDs („elementA“ und „elementB“) übergeben. Während der Animation sind die beiden Ursprungselemente somit nicht sichtbar.

ramjet_animation
Ablauf der Animation von Element A zu Element B

Über den Parameter „done“ wird eine Funktion ausgeführt, sobald die Transformationsanimation abgeschlossen ist. Diese sorgt dafür, dass das zweite Element sichtbar wird, sobald die Animation beendet wurde.

Zusätzliche Parameter

Neben dem Parameter „done“ gibt es weitere Möglichkeiten, den Übergang zu bestimmen. Per „duration“ kann beispielsweise die Länge der Animation angegeben werden. Außerdem besteht mit „easing“ die Möglichkeit, eine Easing-Methode für das Beschleunigen und Abbremsen der Animation festzulegen.

ramjet.transform(elementA, elementB, {
  duration: 5000,
  easing: ramjet.easeIn
});

Während „ramjet.linear“ die Standard-Easing-Methode ist, gibt es „ramjet.easeIn“, „ramjet.easeOut“ und „ramjet.easeInOut“ für unterschiedliche Animationseffekte.

Statt die Elemente direkt per „style.opacity“ unsichtbar zu machen, kann man auch die Methoden „ramjet.hide()“ und „ramjet.show()“ verwenden. Diese setzen die „opacity“-Eigenschaft ebenfalls auf 0 beziehungsweise 1.

Browsersupport und Link zum Beitrag

ramjet läuft auf allen modernen Browser wie Chrome und Firefox. Außerdem funktioniert die Bibliothek im Internet Explorer ab Version 9. ramjet wurde unter die MIT-Lizenz gestellt, ist also kostenfrei auch für kommerzielle Projekte einsetzbar.

(dpe)

Kategorien
HTML/CSS JavaScript & jQuery

Mit Clusterize.js Datentabellen schnell darstellen

Wer sehr umfangreiche Datenmengen hat und diese tabellarisch in einem HTML-Dokument darstellen möchte, sollte die Performanz seiner Seite stets im Blick haben. Grundsätzlich ist es zwar möglich, auch eine Tabelle mit mehreren hundert oder tausend Zeilen auszuzeichnen. Allerdings ist es nicht immer ein Vergnügen, sich durch so eine lange Tabelle durchzuarbeiten. Vor allem mit dem Mausrad stellt man schnell fest, dass das Scrollen nicht so flüssig läuft, wie man es eigentlich gewohnt ist. Die Datenmenge der Tabelle macht dem Browser zu schaffen. Clusterize.js ist eine JavaScript-Bibliothek, die Abhilfe schafft, indem es immer nur Teilbereiche einer Tabelle lädt und darstellt.

clusterizejs

Einfaches Konzept, große Wirkung

Clusterize.js setzt zunächst voraus, dass nur ein Ausschnitt einer Tabelle innerhalb eines scrollbaren Containers dargestellt wird. Statt aller Zeilen sind somit nur einige wenige Zeilen zu sehen. Dafür besitzt der Container eine Scrollbar, mit der man sich durch die gesamte Tabelle bewegen kann. Allerdings sorgt Clustize.js dafür, dass nicht die komplette Tabelle geladen wird. Stattdessen ist nur der sichtbare Bereich der Tabelle auch tatsächlich in der DOM-Struktur angelegt. Sobald innerhalb des Tabellencontainers gescrollt wird, werden neue Tabellendaten geladen. Damit die Scrollbar funktioniert, werden nicht sichtbare Bereiche mit einer entsprechenden Höhe versehen. Man kann also wie gewohnt durch eine Tabelle scrollen.

Gerade bei sehr großen Datenmengen ist der Unterschied sehr deutlich bemerkbar. Das automatische Nachladen per JavaScript sorgt dafür, dass immer nur relativ wenige Tabellenzeilen vorhanden sind. Dies sorgt für einen schlanken Quelltext und verhindert Performance-Probleme. Abgesehen vom Geschwindigkeitszuwachs merkt der Benutzer nichts vom Laden der Daten. Das geschieht im Hintergrund.

Tabellen per Clusterize.js laden

Um Clusterize.js verwenden zu können, müssen die JavaScript- und Stylesheet-Datei der Bibliothek am Ende des HTML-Bodys beziehungsweise im HTML-Head eingebunden werden. Anschließend hat man die Wahl zwischen zwei Lademöglichkeiten. Besitzt man bereits eine Tabelle mit allen Zeilen, sorgt Clusterize.js dafür, dass jene Tabellenzeilen zunächst entfernt und in einem Array gespeichert werden, die jenseits des sichtbaren Bereichs liegen. Alternativ kann man auch eine leere Tabelle auszeichnen und die Zeilen direkt als JavaScript-Array übergeben. Dazu müssen die einzelnen Zeilen allerdings per HTML ausgezeichnet sein.

<div class="clusterize">
  <table>
    <thead></thead>
  </table>
  <div id="scrollArea" class="clusterize-scroll">
    <table>
      <tbody id="contentArea" class="clusterize-content">
        <tr class="clusterize-no-data">
          <td>Lade Daten …</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Das Beispiel zeigt exemplarisch, wie eine Tabelle ausgezeichnet sein muss, damit diese per Clusterize.js genutzt werden kann. Damit die Körperzeilen der Tabelle unabhängig von den Kopfzeilen gescrollt werden können, werden zwei „<table>“-Elemente erstellt. Die erste Tabelle enthält somit die feststehenden Kopfzeilen. Die zweite Tabelle, die innerhalb eines „<div>“-Elementes platziert ist, ist scrollbar. Das umschließende Container-Element sorgt dafür, dass nur ein Ausschnitt der Tabelle sichtbar ist. Wichtig ist, dass die entsprechenden Klassen mit angeben werden („clusterize“, „clusterize-scroll“ und „clusterize-content“). Über die IDs des „<div>“-Elementes und des darin enthaltenen „<table>“-Elementes wird die Tabelle per Clusterize.js angesprochen.

var daten = ["<tr>…</tr>", "<tr>…</tr>", …];
var clusterize = new Clusterize({
  rows: daten,
  scrollId: "scrollArea",
  contentId: "contentArea"
});

Da die Tabelle im Beispiel keine Inhalte besitzt, müssen diese über einen Array übergeben werden. Hier ist es der Array „daten“, der komplett ausgezeichnete Zeilen enthält, die jeweils per „<tr>“-Element eingeleitet werden. Dieser Array wird der Option „rows“ des „Clusterize()“-Objektes übergeben. Sind die Zeilen bereits alle innerhalb des „<table>“-Elementes ausgezeichnet, wird die „rows“-Option einfach weggelassen.

Über die Option „scrollId“ wird der Bereich angegeben, der den scrollbaren Bereich definiert, in dem die eigentliche Tabelle liegt. Diese wird per „contentId“ angesprochen.

Mehr ist nicht zu machen. Clusterize.js sorgt eigenständig dafür, dass immer die richtigen Zeilen geladen und angezeigt werden. Das Ganze funktioniert nicht nur mit Tabellen, sondern auch mit Listen. So lassen sich auch sortierte („<ol>“) sowie unsortierte Listen („<ul>“) auf gleiche Weise laden. Sinnvoll ist das natürlich nur, wenn man entsprechend umfangreiche Listen hat, bei denen man mit einer Verbesserung der Performance rechnen kann.

Anzahl der Zeilen pro Block einstellbar

Standardmäßig werden immer 50 Zeilen in einem Block geladen, der dann in der Tabelle platziert wird. Die Anzahl der Zeilen kann man jedoch über die Optionen individuell einstellen. Ist die Tabelle beispielsweise so gestaltet, dass immer mehr als 50 Zeilen zu sehen sind, sollten die zu ladenden Zeilen pro Block erhöht werden.

Ebenfalls einstellbar ist die Anzahl der Blocks pro Cluster. Standardmäßig werden maximal vier Cluster in der Tabelle dargestellt.

var clusterize = new Clusterize({
  …
  rows_in_block: 50,
  blocks_in_cluster: 4
});

Es gibt weitere optionale Einstellungsmöglichkeiten. So gibt es mit „tag“ die Möglichkeit, ein Element zu definieren, welches verwendet werden soll, um die Tabelle aufzufüllen. Wird per „tag“ kein Element angegeben, wählt Clusterize.js“ automatisch ein Passendes aus – zum Beispiel „<tr>“ bei Tabellen und „<li>“ bei Listen. Diesen Platzhalter-Elementen wird dann per CSS-Eigenschaft „height“ eine Höhe zugewiesen, die der Höhe entspricht, welche die nicht geladenen Tabellenzeilen beziehungsweise Listenelementen entsprechen.

clusterizejs_quelltext
Quelltext einer per Clusterize.js veränderten Tabelle: Nicht geladene Zeilen werden als eine Zeile mit entsprechender Höhe dargestellt

Auch wenn das Framework das Laden von Inhalten selbständig regelt, gibt es Methoden, um Daten zu aktualisieren und einer Tabelle neue Zeilen anzuhängen.

clusterize.append();

Das Beispiel mit der Methode „append()“ sorgt dafür, dass neue Daten ans Ende der Tabelle angehängt werden. Dies geschieht normalerweise beim Herunterscrollen. Analog dazu gibt es die Methode „prepend()“, die Daten am Anfang anhängt. Das geschieht normalerweise beim Heraufscrollen.

Browsersupport und Link zum Download

Clusterize.js läuft in allen modernen und auch mobilen Browsern, einschließlich des Internet Explorers ab Version 8. Eine Sache gilt es jedoch zu beachten. Per CSS wird ja eine Höhe für die nicht dargestellten Inhalte definiert. Bei mehreren hundert oder tausend Tabellenzeilen kann diese Höhe schon mal sehr ordentlich ausfallen. Alle Browser haben jedoch unterschiedliche Maximalwerte für Höhenangeben. So können Webkit-Browser zum Beispiel um die 134 Millionen Pixel und Gecko-Browser „nur“ zehn Millionen Pixel Höhe interpretieren. Diese Begrenzungen sollte man zumindest im Hinterkopf haben.

Die Bibliothek steht unter der verbreiteten MIT-Lizenz und kann somit auch kommerziell eingesetzt werden. Wer sich von dem Performance-Unterschied zunächst einmal überzeugen möchte, findet auf der Projekt-Website ein sehr aussagekräftiges Beispiel. Dort kann man eine „normale“ Tabelle mit einer per Clusterize.js optimierten Tabelle vergleichen.

Kategorien
JavaScript & jQuery Webdesign

Service Worker: JavaScript unabhängig von einer Website ausführen

Neue JavaScript-APIs haben in den letzten Jahren viele neue und nützliche Funktionen hervorgebracht, die vor allem für das mobile Web entwickelt wurden – zum Beispiel die Geolocation-der Application-Cache-API. Von den technischen Möglichkeiten nähern sich Webanwendungen immer mehr nativen Apps für Mobilgeräte an. Eine Sache war bislang jedoch den Apps vorbehalten: Anders als Webanwendungen können Apps auch dann laufen, wenn sie gar nicht geöffnet sind. Mit der neuen Service-Worker-API klappt das nun auch mit JavaScript und somit in Webanwendungen.

serviceworker-teaser_DE

Die Möglichkeiten der Service-Worker-API

Damit JavaScript ausgeführt werden kann, war es bislang immer Voraussetzung, dass eine Website in einem Browser geöffnet war. Mit dem Schließen einer Website – durch Schließen des Browser-Tabs oder -Fensters – wurden auch sämtliche ausgeführten JavaScripts beendet. Die neue Service-Worker-API hingegen erlaubt es, eine JavaScript-Datei auch dann auszuführen, wenn die dazugehörige Website gar nicht geöffnet ist.

Mit Service Workern ist es somit also möglich, Funktionen im Hintergrund auszuführen – auch nach dem Verlassen einer Website. Eine der Einsatzmöglichkeiten der Servive Worker ist das Cachen von Dateien im Hintergrund. Gerade bei der mobilen Nutzung der Internets sind Übertragsungsraten nicht immer gleich gut. Ein Service-Worker-Script hat die Möglichkeit, im Hintergrund neue Inhalte zu laden – bei einer Nachrichtenseite zum Beispiel neue Artikel samt Bilder. Wird die Nachrichtenseite zu einem späteren Zeitpunkt aufgerufen, stehen die Inhalte bereits zur Verfügung.

Die Voraussetzungen

Da die API noch recht neu ist, ist Chrome derzeit der einzige Browser, der die API gut unterstützt und auch Debugging-Möglichkeiten zur Verfügung stellt. Im Firefox lassen sich Servive Worker für Nightly-Builds über „about:config“ aktivieren. Andere Browser unterstützen die API derzeit noch nicht.

Außerdem funktionieren Service Worker ausschließlich über sichere HTTPS-Verbindungen. Das hat in erster Linie Sicherheitsgründe. Denn mit einem im Hintergrund laufenden Service-Worker-Script lässt sich eine Website kontrollieren. Aber es ist ohnehin damit zu rechnen, dass neue APIs, die Zugriff auf sensible Funktionen oder Hardware haben, ausschließlich über HTTPS-Verbindungen laufen werden.

Servive Worker registrieren

Wie bereits erwähnt, handelt es sich bei einem Service Worker um eine von der eigentlichen Website losgelöste JavaScript-Datei, die im Hintergrund – unsichtbar für den Besucher – ausgeführt wird. Diese JavaScript-Datei muss zunächst im Browser registriert werden. Dies geschieht über die „register()“-Methode der Service-Worker-API.

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("service-worker.js").then(
    function(erfolg) {
      console.log("Ja, hat geklappt.", erfolg);
    }
  ).catch(
    function(fehler) {
      console.log("Nein, hat leider nicht geklappt.", fehler);
    }
  );
}

Im Beispiel wird zunächst über eine „if“-Abfrage geprüft, ob der Browser die Service-Worker-API überhaupt unterstützt. Anschließend wird die Datei „service-worker.js“ registriert. Über einen Promise – „then()“ beziehungsweise „catch()“ – wird in der Konsole ausgegeben, ob die Registrierung geklappt hat oder nicht. Versucht man beispielsweise einen Service Worker über eine nicht sichere HTTP-Verbindung zu registrieren, wird der Browser die Registrierung ablehnen und die Fehlermeldung der „catch()“-Methode ausgeben.

Die Service-Worker-Datei hat ab dem Zeitpunkt der Registrierung die Möglichkeit, den Inhalt der Website zu verändern. Allerdings kann man den Zugriff auf bestimmte Verzeichnisse der Website begrenzen. Hierzu wird per „scope“ ein Verzeichnis übergeben.

navigator.serviceWorker.register("service-worker.js", {
  scope: "/verzeichnis/"
})

Im Beispiel hätte das Service-Worker-Script nur Zugriff auf die Inhalte des angegebenen Verzeichnisses – einschließlich seiner Unterverzeichnisse.

Mit Service Workern arbeiten

Nachdem der Service Worker registriert wurde, nimmt das Script – im Beispiel die Datei „service-worker.js“ seine Arbeit auf. Es wird nun vom Browser im Hintergrund ausgeführt. Sobald die Website, über welche der Service Worker registriert wurde, neu geladen wird, hat der Service Worker Zugriff auf die Website und kann die Ausgabe der Website beeinflussen.

Innerhalb der Service-Worker-Datei steht einem nun das „fetch“-Event zur Verfügung. Das „fetch“-Event wird immer dann gestartet, wenn eine Ressource innerhalb des per „scope“ definierten Bereiches abgerufen wurde. Es erlaubt einem, mit der Methode „respondWith()“ mit der Seite zu kommunizieren und einzelne Requests zu überschreiben. Dazu wird ein neuer Response erstellt und der alte dadurch ersetzt.

self.addEventListener("fetch", function(e) {
  e.respondWith(new Response("Das ist ein neuer Inhalt"));
});

Das einfache Beispiel sorgt dafür, dass jeder Request innerhalb des „scope“-Verzeichnisses – sei es ein HTML-Dokument oder eine Grafik – mit dem neuen Response überschrieben wird. Auch komplexe Responses sind möglich. Mit der zusätzlichen Option „headers“ kann man weitere Angaben zur Art des Responses machen.

e.respondWith(new Response("<svg>…</svg>", {
   headers: {
     "Content-Type": "image/svg+xml"
   }
 }));

Im Beispiel eine SVG-Grafik mit entsprechendem Content-Type ausgegeben.

Inhalte laden und an bestimmte Requests ausgeben

Statt mit „Response()“ eigene Inhalte zu bauen, mit denen man bestehende Inhalte überschreibt, hat man auch die Möglichkeit, mit der „fetch()“-Methode Inhalte zu laden.

e.respondWith(
  fetch("https://www.example.com/bild.jpg", {
    mode: "no-cors"
  })
);

Im Beispiel wird von einer externen Domain eine Bilddatei geladen. Die Angabe von „no-cors“ über die Option „mode“ ist notwendig, um das Laden von Inhalten aus externen Quellen zu erlauben.

In den bisherigen Beispielen sind mit „respondWidth()“ immer alle Requests – HTML-Dokumente, Bilder und alles andere, was über eine URL ausgegeben wird – überschrieben worden. Das will man in der Regel natürlich nicht erreichen. Über einfache „if“-Abfragen kann man jedoch bestimmte URLs mit einem eigenen Response überschreiben.

if (e.request.url.indexOf(".jpg") >= 0) {
  e.respondWith(
    fetch("https://www.example.com/bild.jpg", {
      mode: "no-cors"
    })
  );
}

Im Beispiel werden alle Requests, welche die Zeichenkette „.jpg“ beinhalten, mit der extern geladenen Datei „bild.jpg“ überschrieben. Alle anderen Requests bleiben unberührt und geben ihre Inhalte in gewohnter Weise aus.

Debugging im Chrome

Eine Übersicht aller im Browser registrierten Service Worker erhält man über die Eingabe von „chrome://inspect/#service-workers“ in der Adressleiste des Chrome-Browsers. Für jeden Service Worker stehen die Funktionen „inspect“ und „terminate“ zur Verfügung. Chrome vergibt für jeden Service Worker eine Prozess-ID („pid“), die vor der URL des Service-Worker-Scripts angegeben wird. Per „inspect“ wird ein Fenster mit den „Developer Tools“ von Chrome geöffnet. Dort stehen einem die bekannten Debugging-Funktionen zur Verfügung.

serviceworker_chrome
Registrierte Service Worker im Chrime

Über den Punkt „terminate“ wird der jeweilige Service Worker wieder gelöscht.

Cashing mit Service Workern

Die Service Worker sind mit einer eigenen Caching-Funktion ausgestattet, die unabhängig vom „Application Cache“ der HTML5-Caching-API läuft. Über den Service-Worker-Cache lassen sich Dateien, die beispielsweise als Teil einer Webanwendung verfügbar gemacht werden sollen, im Hintergrund herunterladen – auch wenn die Seite zwischenzeitlich verlassen wird.

serviceworker_chrome-dev-tools
„Developer Tools“ für einen Service Worker – mit dem Service-Worker-Cache

Das Cachen von Dateien lässt sich über das „install“-Event der Servive-Worker-API realisieren. Dieses wird nach der Registrierung aufgerufen.

self.addEventListener("install", function(e) {
  e.waitUntil(
    caches.open("mein-cache").then(function(cache) {
      return cache.addAll([
        "webapp.js",
        "webapp.css",
        "wabapp.png"
      ]);
    })
  );
});

Die Methode „waitUntil()“ sorgt dafür, dass andere Events erst gestartet werden, wenn das aktuell laufende beendet wurde. So wird sichergestellt, dass zum Beispiel das „fetch“-Event erst gestartet wird, wenn alle Dateien innerhalb des „install“-Events gecached wurden.

Per „cache.open()“ wird ein Servive-Worker-Cache angelegt. Diesem wird ein individueller Name – im Beispiel „mein-cache“ – gegeben. Anschließend werden per „addAll()“ die Dateien übergeben, die dem Cache hinzugefügt werden sollen.

Da Chrome die Methode „addCache()“ derzeit noch nicht unterstützt, ist ein Polyfill notwendig, welches diese Funktionalität nachrüstet. Anschließend stehen die gecachten Dateien zur Verfügung und werden anstelle der Dateien auf dem Server verwendet. Anders als normaler Cache wird nicht geprüft, ob aktuellere Dateien zur Verfügung stehen.

Fazit

Auch wenn sich die Service-Worker-API derzeit aufgrund der beschränkten Browserunterstützung noch nicht produktiv einsetzen lässt, hat sie viel Potenzial, um gerade Webapps für das mobile Internet schneller und komfortabler zu machen.

Kategorien
JavaScript & jQuery Programmierung

Neun JavaScript-Bibliotheken zum Gestalten interaktiver Charts

Eine Menge Daten mit sehr vielen Variablen möchte gut strukturiert und möglichst interaktiv dargestellt werden. Rohe Datensätze sind schwer zu verstehen, deshalb müssen Daten erst aufbereitet werden, um sie verständlich zu machen. Diagramme können hier hilfreich sein. Im Webdesign können wir für diese Aufgabenstellung Charts nutzen, denn auch hier sind Diagramme eine der besten Möglichkeiten zur Visualisierung von komplexen Datensätzen. Das Hinzufügen von interaktiven Möglichkeiten gibt dem Leser die Möglichkeit, mit den Datensätzen zu interagieren. Interaktive Charts sind recht leicht in eine Website zu integrieren, vor allem mit der Hilfe einiger JavaScript-Bibliotheken. Im heutigen Beitrag stellen wir Ihnen neun Bibliotheken vor, mit denen sich interaktive Diagramme leicht realisieren lassen.

interaktivecharts-teaser_DE

JavaScript-Bibliotheken zur Erstellung von Diagrammen

Wir haben Ihnen neun der besten Chart-Bibliotheken herausgesucht und bieten so viele Möglichkeiten, große Datenmengen gut verständlich zu visualisieren. Manche Bibliotheken sind recht einfach aufgebaut und nicht ganz so umfangreich. Einige andere hingegen lassen jede Art der Daten-Visualisierung zu, die man sich denken kann. Eine modular aufgebaute Bibliothek haben wir ebenfalls verlinkt. Ihr Vorteil dabei ist, dass Sie nur das in Ihre Website integrieren müssen, was wirklich notwendig ist. Das bläht die Website nicht auf und ist der Performance förderlich. Alle vorgestellten Chart-Bibliotheken sind kostenlos.

Chart.js – HTML5 Charts

Chart.js

Chart.js nutzt das HTML5 Canvas Objekt zur Darstellung der Daten. Die Bibliothek vermag Ihre Daten in sechs verschiedenen Diagramm-Stilen darzustellen und ist responsiv und modular. Modular bedeutet, dass nicht genutzte Chart-Typen entfernt werden können, um die Website nicht unnötig zu belasten. Farben und Animationen können recht leicht angepasst werden.

Chartist.js – Simple responsive Charts

Chartist.js

Die Chartist.js Bibliothek nutzt die SVG-Technik zur Darstellung ansprechender Diagramme. Der Vorteil von Chartist ist die strikte Trennung von Design (CSS) und Funktion (JS). Für Entwickler sind die genutzten SASS-Dateien im Download-Paket enthalten. Mit Hilfe der Chartist Animation API können Sie jede Art von Diagrammen realisieren. Chartist hat sich der Kollege Denis Potschien schon einmal näher angeschaut.

C3.js – D3-based reusable chart library

C3.js

Der Funktionsumfang von C3.js ist recht groß. Die Bibliothek ist mit interessanten Effekten und Animationen versehen, wie die Demo auf der Startseite zeigt. Über die API kann jederzeit Einfluss auf die Darstellung der Charts genommen werden. Eine Design-Anpassung ist ebenfalls leicht mit CSS möglich. Auf der Seite mit den Beispielen lässt sich schnell erkennen, wie wandelbar und vielseitig die Diagramm-Bibliothek ist.

Flot – Attractive JavaScript plotting for jQuery

Flot

Flot ist ein jQuery Plugin für die Erstellung von Interaktiven Charts. Unterstützt werden hierbei das Drehen der Diagramme, das Schwenken, Hinein- oder Hinaus-Zoomen und Datenpunkt-Wechselwirkungen. Einzelne Datenbereiche innerhalb der Diagramme können ab- oder hinzugeschaltet werden. Einige Demo-Beispiele sind online, man kann das Plugin vor dem Download testen. Flot bietet Ihnen eine Vielzahl von Diagramm-Typen und basiert auf dem HTML5 Element Canvas.

ECharts – Bring your Data to Life

ECharts

Die ECharts-Bibliothek kommt aus China und scheint dort sehr populär zu sein. EChart ist erstaunlich umfangreich, wie die Liste der Features und die Demo-Beispiele zeigen. Die Bibliothek schafft es, große Datenmengen zu verarbeiten (Plotten von bis zu 200.000 Datenpunkten auf einem kartesischen Diagramm) und hat die Fähigkeit, mühelos Daten zu extrahieren, zu integrieren und den Austausch von Daten zwischen mehreren Diagrammen vorzunehmen. Man kann recht einfach von einen Diagrammtyp zu einem anderen wechseln und eine Menge mehr.

Peity – Progressive SVG Charts

Peity

Peity ist ein weiteres, auf SVG basierendes Chart-Plugin für jQuery. Es vermag jedes Element in ein Diagramm zu konvertieren, und bietet für die visualisierte Aufbereitung der Daten folgende Chart-Typen an: Torten und Donut Charts, Balkendiagramme und Liniencharts. Ein einfaches Element wie zum Beispiel ein <span> kann mittels eines Wertes (<span class=“donut“>1/5</span>) und dem Aufruf  $(‚.donut‘).peity(‚donut‘) in ein Donut-Diagramm gewandelt werden.

dc.js – Dimensional Charting Javascript Library

dc.js

DC.js nutzt ebenso wie C3.js auch die D3-Bibliothek, um ansprechende Diagramme mittels SVG zu rendern. DC.js wurde entwickelt, um Daten und Analysen für den Desktop und mobile Geräte aufzubereiten. Benutzer-Interaktionen lassen sich zu Ihren Diagrammen hinzufügen. Die Software erlaubt Ihnen, alle Arten von Diagrammen – von einfach bis hochkomplex – darzustellen.

Google Charts

Google-Charts

Die Google Charts API erlaubt es, interaktive Diagramme und Datentools mit der Google Visualization API zu erstellen. In der Chart-Galerie können Sie die einzelnen Chart-Typen ansehen und die von Ihnen benötigten auswählen. Um beginnen zu können, muss etwas JavaScript der Website hinzugefügt werden, damit die Google Charts Bibliothek geladen werden kann. Nach dem Auflisten der Daten in Ihrer Website, können Sie mit einigen benutzerdefinierten Änderungen der Chart-Optionen eine Darstellungsvariante bestimmen. Nun muss nur noch ein Chart-Objekt mit einer ID und innerhalb der Website ein <div> Element erstellt werden, innerhalb dessen sich die Diagramme befinden sollen.

NVD3 Re-usable charts for d3.js

NVD3

NVD3 ist keine eigenständige Bibliothek, sondern eine Reihe von nutzbaren Vorlagen für die d3.js Bibliothek. Die Tabellen und Diagramme sind mit d3.js aufgebaut und dienen als Vorlage zum leichteren Erlernen von d3.js. Sie können die Vorlage beliebig verändern und so leichter verstehen, wie Sie gute Diagramme aufbauen sollten. Die Vorlagen finden Sie in den Beispielen.

Links zum Beitrag

(dpe)

Kategorien
Design HTML/CSS JavaScript & jQuery Webdesign

Slideout.js: Off-Canvas-Navigation mit Gestensteuerung

Die Gestaltung einer für Mobilgeräte optimierten Website ist mitunter eine große Herausforderung. Der wenige Platz, der auf mobilen Geräte zur Verfügung steht, macht es nicht immer einfach, alle relevanten Inhalte unterzubringen. Gerade Navigationen stellen häufig ein Problem dar, da sie oft viel Platz einnehmen. Daher verschwinden Navigationen oft außerhalb des sichtbaren Bereichs einer Website und werden über eine Schaltfläche eingeblendet. Solche Off-Canvas-Navigationen sind beliebt, praktisch und werden vor allem in mobilen Apps gerne eingesetzt. Mit Slideout.js lässt sich eine Off-Canvas-Navigation einfach in ein eigenes Webprojekt integrieren – inklusive Gestensteuerung, was vor allem auf Mobilgeräten der Usability entgegenkommt.

slideoutjs

Menü und Panel definieren

Das Prinzip einer Off-Canvas-Navigation ist einfach: Per HTML wird ein Bereich mit dem Menü ausgezeichnet, der per CSS jenseits des sichtbaren Bereiches platziert wird – meist außerhalb des linken Randes. Der eigentliche Inhalt – hier Panel genannt – stellt den sichtbaren Bereich mit den Inhalten dar. Slideout.js liefert Stylesheet-Angaben, welche die beiden Elemente entsprechend platzieren.

<nav id="menue">
  …
</nav>

<main id="panel">
  <button class="js-slideout-toggle"></button>
  …
</main>

Im Beispiel werden ein „<nav>“-Element für die Navigation sowie ein „<main>“-Element für den Inhaltebereich ausgezeichnet. Innerhalb des „<main>“-Elementes befindet sich zudem noch ein „<button>“-Element, welches die Navigation per Fingertap oder Mausklick öffnet.

Am Ende des HTML-Bodys wird nun die JavaScript-Datei für Slideout.js eingebunden. Außerdem werden den HTML-Elementen – „<nav>“, „<main>“ und „<button>“ – anhand ihrer ID beziehungsweise ihres Klassennamens die Funktionen von Slideout.js zugewiesen:

<script src=".slideout.min.js"></script>

<script>

var slideout = new Slideout({
  "panel": document.getElementById("panel"),
  "menu": document.getElementById("menu"),
  "padding": 250,
  "duration": 500
 });
 
document.querySelector(".js-slideout-toggle").addEventListener("click", function() {
  slideout.toggle();
});
 
</script>

Per „Slideout()“ werden Optionen zugewiesen, welche die HTML-Elemente als Menü beziehungsweise als Panel auszeichnen. Außerdem wird über den Wert „padding“ eingestellt, die breit die Navigation ist und wie weit der Inhaltebereich nach rechts verschoben werden muss, damit die Navigation sichtbar ist. Der Wert „duration“ gibt die Länge der Animation in Millisekunden an. Standardmäßig dauert sie 300 Millisekunden.

Außerdem muss dem „<button>“-Element die Funktion „slideout.toggle()“ zugewiesen werden, damit per Fingertap oder Mausklick die Navigation ausfahren beziehungsweise wieder verschwinden kann.

slideoutjs_beispiel
Beispiel für eine Off-Canvas-Navigation mit Slideout.js

Zwar stellt Slideout.js ein exemplarisches Stylesheet für das Aussehen der Navigation zur Verfügung. Diese kann allerdings auch gänzlich frei definiert werden. Für das Ein- und Ausblenden werden Inline-Styles verwendet. Per „transition“-Eigenschaft erfolgt die Animation.

Integrierte Gestensteuerung

Neben der einfachen Einbindung von Slideout.js ist es vor allem die integrierte Gestensteuerung, die hervorsticht. Ohne weiteres Zutun lässt sich die Navigation auf Mobilgeräten mit der typischen Wischgeste ein- beziehungsweise ausblenden. Statt einer Wischgeste kann die Navigation per Geste gezogen werden. Beim Loslassen rastet die Navigation dann jeweils im ein- oder ausgeklappten Zustand ein.

Wer Slideout.js ohne Gestensteuerung verwenden möchte, kann diese ausschalten.

var slideout = new Slideout({
  "touch": false
 });

Dazu wird die Option „touch“ auf „false“ gesetzt.

Zusätzliche Methoden und Events

Mit den Methoden „open()“, „close()“ und „toggle()“ stellt Slideout.js Methoden zur Verfügung, mit denen die Navigation aus- und eingeklappt werden kann. Diese lassen sich auf beliebige Elemente anwenden – wie auf das „<button>“-Element im Beispiel oben.

Mit der Methode „isOpen()“ kann man zudem den aktuellen Status der Navigation auslesen. Ist das Menü ausgeklappt, gibt die Methode „true“ zurück, andernfalls „false“.

if (slideout.osOpen() == true) {
  console.log("Menü ist ausgeklappt.");
}

Neben den Methoden gibt es auch Events, mit denen man auf das Verhalten der Navigation reagieren kann. So gibt es unter anderem die Ereignisse „open“, „close“ und „transition“, die auf das Öffnen, Schließen sowie auf die Animation der Navigation reagieren. Während „open“ und „close“ ausgelöst werden, nachdem die Navigation komplett auf- beziehungsweise zugeklappt ist, werden „beforeopen“ und „beforeclose“ vor der Ein- oder Ausklapp-Animation ausgelöst.

slideout.on("open", function() {
  console.log("Menü ist geöffnet.");
});

Im Beispiel wird eine Funktion ausgeführt, sobald das Menü geöffnet beziehungsweise ausgeklappt wurde.

slideout.once("once", function() {
  console.log("Menü ist geöffnet.");
});

Während „on()“ bei jedem Öffnen der Navigation reagiert, führt „once()“ nur beim erstmaligen Öffnen der Navigation die Funktion aus.

Browsersupport und Link zum Beitrag

Slideout.js funktioniert in allen modernen Browsern. Der Internet Explorer wird ab Version 10 unterstützt. Slideout.js steht unter der MIT-Lizenz und kann somit für jedes Projekt, auch kommerziell und in Kundenprojekten, kostenlos eingesetzt werden.

(dpe)

Kategorien
JavaScript & jQuery Programmierung

Blip. und die Web Audio API: Einfach mit Audiodateien arbeiten

Mit Einführung der Web-Audio-API ist das Erstellen und Manipulieren von Sounds per JavaScript möglich geworden. Die Audio-Library Blip. stellt Methoden zur Verfügung, mit denen schnell und relativ unkompliziert Klänge eingebunden, verändert und abgespielt werden können. So können Sie etwa die Abspielgeschwindigkeit beeinflussen, Sounds übereinander legen oder sie in einer Schleife abspielen. Das funktioniert verhältnismäßig einfach. Im folgenden Beitrag stelle ich ein paar funktionierende Beispiele für den Soforteinsatz vor…

blip-webaudioapi-teaser_DE

Clips anlegen und abspielen

Ist die JavaScript-Datei blip.js im HTML-Head eingebunden, können wir damit begonnen, sogenannte Clips zu erstellen. Das sind Audiodateien, die beispielsweise im Wave- oder MP3-Format vorliegen müssen, und per sampleLoader()  geladen und eingebunden werden.

blip

Diese Samples lassen sich anschließend auf unterschiedliche Art manipulieren und abspielen.

blip.sampleLoader()
.samples({
  "melodie": "melodie.mp3",
  "gesang": "gesang.mp3"
})
.done(abspielen)
.load();

Im Beispiel werden zwei Audiodateien geladen und entsprechenden Variablen zugeordnet. Über die Methode done() rufen wir eine Funktion auf, die auf die Samples Zugriff hat – im Beispiel ist es die Funktion abspielen. Die Methode load() sorgt dafür, dass die angegebenen Samples geladen werden. Die über done() angegebene Funktion wird aufgerufen, wenn die Samples im Browser bereitstehen.

Innerhalb der Funktion abspielen hat man nun Zugriff auf die Samples. Mit den Methoden play() und stop() können wir nun das Abspielen der Samples steuern. Der play()-Methode kann dabei eine Zeit übergeben werden, die für einen zeitlichen Versatz vor dem Abspielen sorgt.

blip.clip().sample("melodie").play();
blip.clip().sample("gesang").play(8);

Im Beispiel ordnen wir beide Samples über die clip()-Methode einem Clip zu. Während melodie ohne Versatz abgespielt wird, wird gesang erst nach acht Sekunden abgespielt. Gerade wenn man mehrere Klänge übereinander legen will, ist es manchmal hilfreich, diese zu verschiedenen Zeiten abspielen zu können.

Lautstärke und Geschwindigkeit verändern

Der play()-Methode lässt sich neben der Versatzzeit ein Objektliteral übergeben, über das die Lautstärke und die Abspielgeschwindigkeit eines Samples beeinflusst werden kann. Mit dem Parameter gain wird die Lautstärke, über rate die Geschwindigkeit angegeben.

blip.clip().sample("gesang").play(8, {
  gain: 2,
  rate: 0.75
});

Im Beispiel wird der Sample doppelt so laut und mit Dreiviertel der normalen Geschwindigkeit abgespielt. Der Wert 1 stellt für beide Parameter jeweils den Normalwert dar.

Loops erstellen und abspielen

Mit Blip. können wir sehr einfach sogenannte Loops erstellen. Dabei handelt es sich um Samples, die in einem bestimmten Rhythmus in einer Endlosschleife wiederholt werden. Loops bieten sich an, wenn ein einzelner Klang (etwa ein Trommelschlag) stets wiederholt werden soll. Der loop()-Methode muss die Geschwindigkeit übergeben werden, mit welcher der Sample wiederholt werden soll. Dies geschieht über tempo(). Hier geben wir einen Wert an, der die Wiederholungen pro Minute definiert. Über die Methode tick() wird eine Funktion übergeben, welche in der über tempo() angegebenen Zeit immer wieder aufgerufen wird.

blip.loop()
  .tempo(50)
  .tick(function (t) {
    schlagzeug.play(t)
  }
);

Im Beispiel wiederholt sich der Sample schlagzeug 50 Mal in der Minute. Die Wiederholung erfolgt immer im gleichen Abstand. Mit der zusätzlichen Methode data() lassen sich unterschiedliche Wiederholschritte angeben, so dass verschiedene Takte definiert werden können.

blip.loop()
  .tempo(50)
  .data([1, 1, 0])
  .tick(function (t) {
    schlagzeug.play(t)
  }
);

Im Beispiel übergeben wir data() drei Werte als Array. Sie sorgen dafür, dass die über tick() übergebene Funktion bei jedem dritten Aufruf ausgelassen wird. Die Anzahl der Werte für data() ist frei definierbar. Auch komplexe Abfolgen sind auf diese Weise möglich.

Den Zufall einbeziehen

Blip. bietet mit der Methode chance() die Möglichkeit, das Abspielen von einer vorgegebenen Wahrscheinlichkeit abhängig zu machen. Dazu wird der Methode ein Wahrscheinlichkeitswert übergeben, der einen zufälligen Rhythmus in Kombination mit der loop()-Methode ergibt.

blip.loop()
  .tempo(50)
  .tick(function (t) {
    if (blip.chance(1/3)) {
      schlagzeug.play(t)
    }
  }
);

In diesem Beispiel spielt der Sample schlagzeug bei jedem Durchlauf bei einer Wahrscheinlichkeit von 1 zu 3 ab. Außerdem lassen sich mit der random()-Methode Zufallszahlen ausgeben.

blip.loop()
  .tempo(50)
  .tick(function (t) {
    schlagzeug.play(t, {
      "rate": blip.random(0.25, 2.5)
    })
  }
);

Im letzten Beispiel wird die Abspielgeschwindigkeit bei jedem Durchlauf zufällig festgesetzt. Sie wird dabei immer zwischen den Werten 0,25 und 2.5 liegen. Ohne Angabe von Werten liegt die Zufallszahl immer zwischen 0 und 1.

Fazit und Lizenz

Blip. bietet einige gute Funktionen, um Klänge zu manipulieren. Neben den vorgestellten Funktionen erlaubt Blip. auch den Zugriff auf die verschiedenen Audio-Nodes der Web-Audio-API, mit denen beispielsweise eigene Klänge erzeugt werden können.

Blip. steht jedermann kostenlos zur Verfügung und darf unter Nennung des Entwicklers eingesetzt und weiterentwickelt werden. Voraussetzung ist lediglich ein Browser, der die Web-Audio-API unterstützt. Das ist bei allen modernen Browsern, auch auf mobilen Clients der Fall. Eine Ausnahme bildet Opera Mini und – natürlich – der Internet Exploder. Mit Microsoft Edge soll dann auch aus Redmond Standardunterstützung folgen…

(dpe)

Kategorien
Design Illustrator JavaScript & jQuery Webdesign

Animiertes SVG-Morphing mit Illustrator

Das SVG-Format löst mehr und mehr Flash für vektorbasierte und animierte Grafiken ab. Aufgrund der XML-Syntax des SVG-Formates lassen sich Grafiken im Grunde per Texteditor erstellen, was in den meisten Fällen jedoch wenig sinnvoll ist. Denn Zeichenprogramme wie Illustrator verfügen über die Möglichkeit, Grafiken ins SVG-Format zu exportieren. Animationen lassen sich damit allerdings nicht umsetzen, so dass hier doch der Texteditor zu Hilfe genommen werden muss. Allerdings ist es relativ einfach, mit Illustrator und einem Texteditor eine SVG-Grafik in Bewegung zu bringen – zum Beispiel als Morphing-Animation.

svgmorphing-teaser

Formen in Illustrator zeichnen

Ein bewegtes Morphing entsteht, indem eine Zeichenform in eine andere Zeichenform animiert wird. Dazu müssen beide Formen zunächst in Illustrator angelegt werden. Wichtig ist, dass beide Formen mit demselben SVG-Element ausgezeichnet werden – zum Beispiel „<polygon>“ für einfache mehreckige Formen oder „<path>“ für komplexe Formen mit Kurven und Aussparungen. Auch die Anzahl der Koordinaten beziehungsweise Kurven müssen jeweils übereinstimmen. Ansonsten lassen sich die Transformationen nicht animieren.

Im Beispiel legen wir in Illustrator einen fünfzackigen Stern an und exportieren diesen als SVG-Datei. Anschließend ändern wir die Koordinaten des Sterns so, dass daraus ein Zehneck wird. Dieses Zehneck wird ebenfalls als SVG-Datei gespeichert. In beiden Grafiken sind jeweils zehn Koordinaten angegeben. Statt die Koordinaten zu ändern, kann man natürlich auch eine gänzlich neue Form zeichnen, solange diese dieselbe Anzahl von Koordinaten aufweist.

svg-illustrator-export
SVG-Optionem beim Speichern in Illustrator

Illustrator wird sowohl den Stern als auch das Zehneck jeweils als „<polygon>“-Element in der SVG-Datei auszeichnen. Sobald Bézierkurven oder verknüpfte Pfade zum Einsatz kommen, wird die Form als „<path>“-Element exportiert. Hier muss man einfach darauf achten und sich das exportierte Ergebnis einmal ansehen.

Formen zusammenbringen und animieren

Illustrator hat seine Arbeit für die animierte Form nun getan. Ab sofort wird mit dem Texteditor weitergearbeitet. Dort wird die erste SVG-Datei geöffnet und das entsprechende „<polygon>“-Element gesucht. Es besitzt eine „fill“-Eigenschaft, welche die Füllfarbe definiert, sowie eine „point“-Eigenschaft mit den zehn Koordinaten-Paaren bestehend aus jeweils einer X- und einer Y-Koordinate.

<polygon fill="#FF8200" points="47.6,4.8 62.2,34.5 95.1,39.3 71.3,62.5 76.9,95.2 47.6,79.8 18.2,95.2 23.8,62.5 0,39.3 32.9,34.5" />

Zum Animieren von Formen gibt es das „<animate>“-Element, welches das jeweilige Elternelement in Bewegung versetzt. Daher muss es als Kindelement des Polygons im Quelltext platziert werden.

<polygon fill="#FF8200" points="47.6,4.8 62.2,34.5 95.1,39.3 71.3,62.5 76.9,95.2 47.6,79.8 18.2,95.2 23.8,62.5 0,39.3 32.9,34.5">
  <animate attributeName="points" to="47.6,0 76.9,9.5 95.1,34.5 95.1,65.5 76.9,90.5 47.6,100 18.2,90.5 0,65.5 0,34.5 18.2,9.5" dur="500ms" repeatCount="indefinite" />
</polygon>

Das „<animate>“-Element besitzt mindestens drei Eigenschaften. Mit „attributeName“ wird die Eigenschaft des Elternelements angegeben, die per Animation verändert werden soll. In unserem Beispiel ist es das „point“-Element des Polygons. Die Eigenschaft „to“ enthält den Zielwert für die Eigenschaft, die per „attributeName“ angegeben ist. In diesem Fall sind es also die Koordinaten des Zehnecks. Dazu kopieren wir die Koordinaten – also den Wert des „point“-Attributes der zweiten SVG-Datei – und weisen sie der „to“-Eigenschaft als Wert zu.

svg-morphing-schritte
Animationsablauf vom Stern zum Zehneck

Während das „<polygon>“-Element in der „point“-Eigenschaft also die Koordinaten für den Stern hat, besitzt das „<animate>“-Element in der „to“-Eigenschaft die Koordinaten des Zehnecks. Per „dur“-Eigenschaft wird noch die Dauer der Animation angegeben. Ruft man die SVG-Datei im Browser auf, wird der Stern innerhalb einer halben Sekunde in ein Zehneck verwandelt. Will man die Animation mehrmals oder dauerhaft ausführen, gibt man über die Eigenschaft „repeatCount“ die Anzahl der Wiederholungen oder „indefinite“ für dauerhafte Wiederholungen an.

Hin und zurück animieren

In unserem Beispiel wiederholt sich die Transformation vom Stern zum Zehneck zwar dauerhaft. Allerdings findet keine Animation vom Zehneck zurück zum Stern statt. Will man eine rückläufige Animation ergänzen, um jeweils fließend ins Zehneck und wieder zurück zum Stern zu transformieren, muss die „<animate>“-Eigenschaft etwas verändert werden.

Statt der „to“-Eigenschaft, welche nur ein Ziel für eine Animation beinhaltet, wird die „values“-Eigenschaft verwendet. Der Vorteil von „values“ ist, dass dort beliebig viele Animationsschritte hinterlegt werden können. Per Semikolon voneinander getrennt, lassen sich also beliebige viele Koordinaten für das „<polygon>“-Element angeben. Diese werden nacheinander abgearbeitet und somit animiert.

Für unser Beispiel müsen per „values“-Eigenschaft drei Animationsschritte angegeben werden. Als erster und letzter Wert für „values“ werden die Koordinaten des Sterns verwendet. Als zweiter Wert werden die Koordinaten des Zehneckes angegeben.

<polygon fill="#FF8200" points="47.6,4.8 62.2,34.5 95.1,39.3 71.3,62.5 76.9,95.2 47.6,79.8 18.2,95.2 23.8,62.5 0,39.3 32.9,34.5">
  <animate attributeName="points" values="[Sternkoordinaten]; [Zehneckkoordinaten]; [Sternkoordinaten]" dur="500ms" repeatCount="indefinite" />
</polygon>

Da Start- und Endpunkt für die Animation jeweils die Koordinaten des Sterns sind, endet und beginnt die Animation jeweils mit derselben Form.

Animation per Klick starten

Mit einer weiteren Eigenschaft kann man die Animation erst dann beginnen lassen, wenn die Form angeklickt wird. Dazu muss einfach die Eigenschaft „begin“ mit dem Wert „click“ ergänzt werden.

<polygon fill="#FF8200" points="47.6,4.8 62.2,34.5 95.1,39.3 71.3,62.5 76.9,95.2 47.6,79.8 18.2,95.2 23.8,62.5 0,39.3 32.9,34.5">
  <animate begin="click" attributeName="points" values="[Sternkoordinaten]; [Zehneckkoordinaten]; [Sternkoordinaten]" dur="500ms" repeatCount="indefinite" />
</polygon>

Mehr Möglichkeiten hat man, wenn man JavaScript zur Steuerung der Animation einsetzt. So ist es möglich, per Klick die Animation zum Zehneck und per weiterem Klick die Animation zurück zum Stern zu starten. Dazu müssen zwei verschiedene „<animate>“-Elemente definiert werden. Das erste Element enthält die Koordinaten zum Zehneck, das zweite die Koordinaten zum Stern. Da jeweils nur ein Zielwert für die Animation pro „<animate>“-Element angegeben wird, verwenden wir wieder die „to“-Eigenschaft.

<polygon fill="#FF8200" points="47.6,4.8 62.2,34.5 95.1,39.3 71.3,62.5 76.9,95.2 47.6,79.8 18.2,95.2 23.8,62.5 0,39.3 32.9,34.5">
  <animate attributeName="points" to="[Zehneckkoordinaten]" dur="500ms" begin="indefinite" fill="freeze" />
  <animate attributeName="points" to="[Sternkoordinaten]" dur="500ms" begin="indefinite" fill="freeze" />
</polygon>

Außerdem wird der Eigenschaft „begin“ der Wert „indefinite“ zugewiesen, um zu verhindern, dass die Animation ohne Zutun des Nutzers gestartet wird. Die Eigenschaft „fill“ bekommt den Wert „freeze“ zugewiesen, um zu erreichen, dass am Ende der Animation das animierte Bild beibehalten wird. Andernfalls würde zum Ausgangsbild der Animation zurückgekehrt werden.

Mit ein paar Zeilen JavaScript kann man nun jeweils eine der beiden „<animate>“-Eigenschaften ausführen.

document.getElementsByTagName("polygon")[0].addEventListener("click", function() {
  if (document.getElementsByTagName("polygon")[0].getAttribute("class") != "zehneck") {
    document.getElementsByTagName("polygon")[0].setAttribute("class", "zehneck");
    document.getElementsByTagName("animate")[0].beginElement();
  } else {
    document.getElementsByTagName("polygon")[0].setAttribute("class", "");
    document.getElementsByTagName("animate")[1].beginElement();
  }
}, false);

Im Beispiel wird dem Polygon per „addEventListener()“ eine Funktion zugewiesen, die per Klick auf das Element jeweils aufgerufen wird. Je nach zugewiesener Klasse des „<polygon>“-Elementes wird die erste oder zweite Animation ausgeführt – per Methode „beginElement()“.

Verwendet man JavaScript für die Steuerung von SVG-Grafiken, dürfen diese nicht per „<img>“-Element in einem HTML-Dokument eingebunden sein. Man sollte die SVG-Grafik entweder per „<embed>“-Element einbinden oder direkt ins HTML-Dokument implementieren.

Browser, die zwar das SVG-Format beherrschen, aber mit SVG-Animationen noch nichts anfangen können, werden nur die Ausgangsform der Animation darstellen.

(dpe)

Kategorien
HTML/CSS JavaScript & jQuery Responsive Design

matchMedia() – Media Queries mit JavaScript

Dass eine Website auch auf mobilen Geräten funktionieren und vernünftig aussehen muss, ist mittlerweile zu einer Selbstverständlichkeit und jüngst sogar zu einem Ranking-Faktor geworden. Dank Media Queries ist es zum Glück relativ einfach, per CSS das Aussehen für unterschiedliche Displaygrößen und -orientierungen zu definieren. Die JavaScript-Methode „matchMedia()“ ermöglicht es zudem, Media Queries auch in JavaScript einzusetzen. Somit stehen uns alle Möglichkeiten moderner Frontend-Programmierung offen.

matchMedia()

Der Vorteil von „matchMedia()“

Bisher gelang es unter JavaScript nur bedingt, auf Browser- beziehungsweise Geräteeigenschaften zu reagieren. Zwar gibt es die Möglichkeit, die Browser- sowie die Bildschirmauflösung abzufragen – so einfach und bequem wie mit Media Queries bei Stylesheets ist es allerdings nicht. Vor allem muss man mit unterschiedlichen Begrifflichkeiten arbeiten, was die Entwicklung responsiver Websites mit CSS in Kombination mit JavaScript erschwert.

if (window.innerWidth >= 320 && window.innerWidth > window.innerHeight) {
  …
}

Im obigen Beispiel wird über eine Bedingung geprüft, ob die Fensterbreite größer gleich 320 Pixel ist und das Fenster in Landscape-Orientierung dargestellt wird. Für die letzte Bedingung wird einfach geprüft, ob das Fenster breiter als hoch ist. Mit der Methode „matchMedia()“ lässt sich diese Abfrage mit einer Media-Queries-Angabe darstellen, die so auch in einem Stylesheet vorkommen kann.

if (window.matchMedia("(min-width: 320px) and (orientation: landscape)").matches) {
  …
}

Hier wird der Methode einfach per Media Queries übergeben, welche Bedingungen erfüllt sein müssen. Wichtig ist, dass die Methode mit dem Attribut „matches“ abgeschlossen wird. Es wird „true“ zurückgeben, wenn die Media-Queries-Angabe vom Browser erfüllt wird.

Vor allem im Zusammenspiel mit Stylesheets ist es eine Erleichterung, Media Queries sowohl per CSS als auch per JavaScript einsetzen zu können. Außerdem stehen alle Bedingungen zur Verfügung, die auch per CSS berücksichtigt werden können. Neben der Auflösung und der Orientierung lässt sich beispielsweise die Pixeldichte des Gerätes („device-pixel-ratio“) berücksichtigen.

Über das Attribut „media“ ist es zudem möglich, die Media-Queries-Angabe der Methode auszulesen.

console.log(window.matchMedia("(min-width: 320px) and (orientation: landscape)").media);

Das Beispiel schreibt also den in „matchMedia()“ hinterlegten Wert in die Browserkonsole.

Per Event und „matchMedia()“ auf Änderungen reagieren

In den seltensten Fällen sind die beim Seitenaufruf berücksichtigten Media Queries statisch. Bei Desktopgeräten kann sich die Auflösung des Browserfensters, bei Mobilgeräten kann sich die Geräteorientierung ändern. Daher sollte man Änderungen an Browser und Gerät per Event überwachen.

Die Änderung der Browsergröße könnte man noch mit dem Event-Handler „resize“ abfangen. Andere Geräte- und Browsereigenschaften können jedoch nicht mit den gängigen Event-Handlern in den Griff bekommen werden.

Zwar gibt es keinen Event-Handler speziell für Media Queries; dennoch gibt es die Möglichkeit, Änderungen der per Media Queries angegebenen Bedingungen zu überwachen.

Zunächst müssen zwei Variablen definiert werden. Die eine Variable (im Beispiel „mq“) enthält die „matchMedia()“-Methode mit den zu überwachenenden Media-Queries-Angaben.

Die zweite Variable (im Beispiel „mq_ereignis“) enthält eine Funktion, welche prüft, ob die Media-Queries-Angaben erfüllt sind oder nicht.

Dazu wird per „matches“ abgefragt, ob die in der Variablen „mq“ hinterlegten Angaben zutreffen.

var mq = window.matchMedia("(min-width: 320px)");
var mq_ereignis = function(mq) {
  if (mq.matches){ 
    // Fensterbreite ist mindestens 320 Pixel breit
  } else {
    // Fensterbreite ist kleiner als 320 Pixel
  }
}

Anschließend wird der Funktion, welche in der Variablen „mq_ereignis“ hinterlegt ist, noch die Variable „mq“ übergeben. Im letzten Schritt wird der Variablen „mq“ noch per „addListener()“ die Funktion in „mq_ereignis“ zugewiesen.

mq_ereignis(mq);
mq.addListener(mq_ereignis);

Das gesamte Script sollte im HTML-Head untergebracht sein. Anschließend reagiert die Seite dynamisch auf Änderungen, welche die Media-Queries-Angaben betreffen, die per „matchMedia()“ definiert wurden.

Browsersupport und Polyfill

Die Methode „matchMedia()“ wird von allen gängigen Browsern unterstützt. Der Internet Explorer unterstützt die Methode ab Version 10. Für ältere Browser gibt es ein Polyfill, welches die Funktionalität nachbildet.

(dpe)

Kategorien
JavaScript & jQuery Programmierung

Licht und Schatten: Shader gestalten mit HTML5 und WebGL

In letzter Zeit war viel von babylon.js die Rede und vor kurzem haben wir babylon.js v2.0 veröffentlicht, das eine 3D Sound-Positionierung (mit WebAudio) sowie volumetrische Lichtstreuungen ermöglicht.

shaders-teaser_DE

Wer die Bekanntgabe von Version 1.0 verpasst hat, kann sich die Keynote dazu hier noch mal ansehen und direkt zum Abschnitt 2:24-2:28 gehen.

In dieser Keynote zeigen Steven Guggenheimer und John Shewchuk in einer Demo, wie die Unterstützung für Oculus Rift in Babylon.js eingebaut wurde. Und eines der wichtigsten Elemente dieser Demo war wiederum unsere Arbeit an einem speziellen Shader, der Objektive simulieren kann, wie in diesem Bild zu sehen ist:

image

Zusammen mit Frank Olivier und Ben Constable sprach ich auf der Build selbst in einer Session über die Grafik im Internet Explorer und in Babylon.js.

Daraus ergibt sich für mich auch gleich die Frage, die bei Diskussionen um babylon.js immer wieder auftaucht: Was ist mit Shadern eigentlich genau gemeint? Ein guter Grund also, heute mal zu zeigen, wie Shader funktionieren.

Die Theorie

Bevor wir mit dem Ausprobieren anfangen, sollten wir erstmal versuchen zu verstehen, was dabei im System genau passiert.

Wenn wir über mittels Hardware beschleunigtes 3D reden, geht es um zwei CPUs: die zentrale CPU und die GPU. Die GPU ist letztlich eine Art extrem spezialisierte CPU.

Die GPU ist eine Zustandsmaschine, die man mit Hilfe der CPU einrichtet. So wird die CPU zum Beispiel die GPU so konfigurieren, dass diese Linien rendert statt Dreiecke. Oder sie wird den Grad der Transparenz festlegen usw.

Wenn alle Zustände definiert sind, wird die CPU bestimmen, was genau wiedergegeben wird (die Geometrie, welche aus einer Liste von Punkten besteht (genauer: Scheitel- oder Eckpunkte, die im sogenannten Vertex Buffer gespeichert werden) und einer Index-Liste (die Flächen (oder Dreiecke), die im sogenannten Index Buffer gespeichert sind)).

Im letzten Schritt wird die CPU dann festlegen, wie die Geometrie schließlich genau gerendert wird. Und für diese spezielle Aufgabe definiert die CPU bestimmte Shaders für die GPU. Shaders sind letztlich ein Stück Code, das die GPU für alle Scheitel- bzw. Eckpunkte und für jeden Pixel ausführt, den es zu rendern gilt.

Zuerst ein paar Begriffserklärungen: Einen Scheitel- oder Eckpunkt (Englisch: Vertex) kann man sich als “Punkt” in einer 3D Umgebung vorstellen im Unterschied zu diesem Punkt in einer 2D Umgebung.

Es gibt zwei Arten von Shadern: den Vertex Shader und den Pixel (oder Fragment) Shader.

Grafik-Pipeline

Bevor wir richtig ins Thema Shaders einsteigen, noch kurz ein paar Grundlagen. Um Pixel darzustellen, nutzt die GPU die von der CPU vorgegebene Geometrie und tut das Folgende:

  • Mithilfe des Index Buffer werden drei Eckpunkte erfasst, um ein Dreieck festzulegen: Der Index Buffer enthält eine Liste von Vertex Indizes. Das heißt, jeder Eintrag im Index Buffer ist die Nummer eines Eckpunkts im Vertex Buffer. Auf diese Weise werden Duplikate von Eckpunkten vermieden. Im folgenden Beispiel besteht der Index Buffer aus einer Liste mit 2 Flächen: [1 2 3 1 3 4]. Die erste Fläche enthält Eckpunkt 1, Eckpunkt 2 und Eckpunkt 3. Die zweite Fläche enthält Eckpunkt 1, Eckpunkt 3 und Eckpunkt 4. In dieser Geometrie gibt es also vier Eckpunkte:

image

  • Der Vertex Shader wird auf jeden Eckpunkt des Dreiecks angewendet. Wichtigste Aufgabe des Vertex Shaders ist die Erstellung eines Pixels für jeden Eckpunkt (die Projektion des 3D Eckpunkts auf einem 2D Bildschirm):

image

  • Die GPU benutzt diese drei Pixel (die ein zweidimensionales Dreieck auf dem Bildschirm definieren), um alle diesem Pixel zugeordneten Werte zu interpolieren (zumindest die Position). Der Pixel Shader wird dann auf jeden Pixel der im 2D-Dreieck enthalten ist angewendet, um für jeden Pixel eine Farbe zu erstellen:

image

  • Dieser Prozess wird für jede Fläche durchgeführt, die der Index Buffer vorgibt.

Aufgrund seiner parallelen Eigenschaften kann die GPU diesen Schritt für viele Flächen gleichzeitig durchführen und dadurch entsprechend gute Ergebnisse hervorbringen.

GLSL

Wie wir gerade gesehen haben, benötigt die GPU zwei Shader, um Dreiecke darstellen zu können: den Vertex Shader und den Pixel Shader. Diese Shader werden mit GLSL (Graphics Library Shader Language) geschrieben. Sie sieht so aus wie C.

Für den Internet Explorer 11 haben wir ein Compiler-Programm entwickelt, um GLSL in HLSL (High Level Shader Language) umzuwandeln, was die Programmiersprache für Shader in DirectX 11 ist. Auf diese Weise gewährleistet der IE11 die Sicherheit des Shader Codes (keiner will schließlich seinen Rechner neu starten, um WebGL zu nutzen):

image

Hier ist ein Beispiel für einen häufig verwendeten Vertex Shader:

precision highp float;

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
    gl_Position = worldViewProjection * vec4(position, 1.0);

    vUV = uv;
}

Struktur eines Vertex Shader

Ein Vertex Shader enthält Folgendes:

  • Attributes: Ein Attribut definiert einen Teil eines Eckpunkts. Standardmäßig sollte ein Eckpunkt zumindest die Position beinhalten (a vector3:x, y, z). Aber als Entwickler kann man auch weitere Daten hinzufügen. So gibt es im vorherigen Beispiel den vector2 namens uv (Koordinaten für eine Textur, mit der eine 2D-Textur auf ein 3D-Objekt angewendet werden kann.)
  • Uniforms: Das sind Variablen, die vom Shader benutzt und von der CPU festgelegt werden. Im Beispiel gibt es davon nur eine und zwar eine Matrix, welche die Position des Eckpunkts (x, y, z) auf den Bildschirm projiziert (x, y)
  • Varying: Varying-Variablen sind Werte, die vom Vertex Shader generiert und an den Pixel Shader übermittelt werden. Im Beispiel überträgt der Vertex Shader einen vUV-Wert (eine einfache Kopie von uv) an den Pixel Shader. Konkret bedeutet das, der Pixel hat eine definierte Position und Koordinaten für die Textur. Diese Werte werden von der GPU interpoliert und vom Pixel Shader verwendet.
  • Main: Diese Funktion ist der Code, den die GPU für jeden Eckpunkt ausführt. Er muss mindestens einen Wert für die gl_position erzeugen (die Position des aktuellen Eckpunkts auf dem Bildschirm).

Der Vertex Shader in unserem Beispiel ist ziemlich einfach aufgebaut. Er erzeugt eine Systemvariable (beginnend mit gl_) namens gl_position, um die Position des damit verbundenen Pixels festzulegen und er legt die Varying-Variable vUV fest.

Die Magie einer Matrix

In unserem Shader-Beispiel gibt es die Matrix worldViewProjection. Wir brauchen sie, um die Position des Eckpunkts auf die gl_position-Variable zu projizieren. Schön und gut, aber woher bekommen wir den Wert für diese Matrix? Nun, es ist eine Uniform-Variable, man muss sie also von der CPU aus definieren (mithilfe von JavaScript).

Das gehört zweifellos zu den eher komplizierten Aufgaben wenn man sich mit 3D beschäftigt. Man muss etwas von komplexer Mathematik verstehen (oder eine 3D Engine wie babylon.js benutzen, mehr dazu später).
Die Matrix worldViewProjection ist eine Kombination von drei verschiedenen Matrizen:

image

Mit der entstandenen Matrix können 3D-Eckpunkte in 2D-Pixel umgewandelt werden, unter Berücksichtigung der Perspektive und allen Werten bezüglich der Position/des Maßstabs/einer Rotation des Objekts.

Diese Matrix zu erstellen und sie aktuell zu halten – das ist deine Verantwortung als-3D Entwickler.

Zurück zu den Shadern

Nachdem der Vertex Shader auf alle Eckpunkte angewendet wurde (also dreimal), haben wir drei Pixel mit der korrekten gl_position und einem vUV-Wert. Die GPU wird daraufhin diese Werte auf alle Pixel, die das Dreieck enthält (und aus denen es besteht), interpolieren.

Danach wird sie bei jedem Pixel den Pixel Shader anwenden:

precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;

void main(void) {
    gl_FragColor = texture2D(textureSampler, vUV);
}

Die Struktur von Pixel (oder Fragment) Shader

Die Struktur eines Pixel Shaders gleicht der eines Vertex Shaders:

  • Varying: Varying Variablen sind Werte, die vom Vertex Shader generiert und an den Pixel Shader übermittelt werden. Im Beispiel erhält der Pixel Shader einen vUV-Wert vom Vertex Shader.
  • Uniforms: Das sind Variablen, die vom Shader benutzt und von der CPU festgelegt werden. Im Beispiel gibt es nur einen Sampler, ein Werkzeug um die Farben von Texturen zu erkennen.
  • Main: Diese Funktion ist der Code, den die GPU für jeden Eckpunkt ausführt. Er muss mindestens einen Wert für die gl_FragColor erzeugen (die Farbe des aktuellen Pixels).

Dieser Pixel Shader ist sehr einfach: Er erkennt die Farbe der Textur mithilfe der Texturkoordinaten des Vertex Shaders (der sie von den Eckpunkten übermittelt bekam).
Und so sieht der Shader dann aus:

(Der vollständige Code ist auf meinem Blog nachzulesen)

Um dieses Ergebnis zu erzielen, muss man sich mit EINER MENGE WebGL Code herumschlagen. WebGL ist auf der einen Seite ein echt leistungsstarkes Tool, andererseits aber ein API auf niedriger Stufe, bei dem alles selbst gemacht werden muss: Von der Erzeugung der Buffer bis zur Definition der Strukturen von Eckpunkten. Dazu kommen die Berechnung, die Festlegung der Zustände, das Laden der Texturen usw…

Zu schwer? BABYLON.ShaderMaterial kommt zu Hilfe

An dieser Stelle denken sicher viele: Klar, Shader sind echt cool, aber WebGL und der ganze mathematische Hintergrund – dazu habe ich keine Lust, das ist mir echt zu komplex.

Auf jeden Fall! Das ist nur zu verständlich und genau deshalb habe ich ja Babylon.js gebaut.

Was nun folgt, ist der Code auf dem die vorherige Demo mit der rollenden Kugel läuft. Als erstes brauchen wir eine einfache Webseite:

<!DOCTYPE html>
  <html>
  <head>
  <title>Babylon.js</title>
  <script src="Babylon.js"></script>

  <script type="application/vertexShader" id="vertexShaderCode">
  precision highp float; 

  // Attributes
  attribute vec3 position;
  attribute vec2 uv;
        // Uniforms
  uniform mat4 worldViewProjection;
        // Normal
  varying vec2 vUV;
        void main(void) {
  gl_Position = worldViewProjection * vec4(position, 1.0);
        vUV = uv;
  }
  </script>

  <script type="application/fragmentShader" id="fragmentShaderCode">
  precision highp float; 
  varying vec2 vUV;
        uniform sampler2D textureSampler;
        void main(void) {
  gl_FragColor = texture2D(textureSampler, vUV);
  }
  </script>
    <script src="index.js"></script>
  <style>
  html, body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  overflow: hidden;
  margin: 0px;
  overflow: hidden;
  }
        #renderCanvas {
  width: 100%;
  height: 100%;
  touch-action: none;
  -ms-touch-action: none;
  }
  </style>
  </head>
  <body>
  <canvas id="renderCanvas"></canvas>
  </body>
  </html>

Hinweis: Die Shader werden hier durch <script> Tags definiert. In Babylon.js können sie zudem in unterschiedlichen Dateien definiert werden (.fx files).

Babylon.js kann hier heruntergeladen werden oder auf unserem GitHub Repository. Um Zugang zum BABYLON.StandardMaterial zubekommen, wird die Version 1.11 oder höher benötigt.

Der primäre JavaScript Code sieht dann wie folgt aus:

"use strict";

document.addEventListener("DOMContentLoaded", startGame, false);

function startGame() {
    if (BABYLON.Engine.isSupported()) {
        var canvas = document.getElementById("renderCanvas");
        var engine = new BABYLON.Engine(canvas, false);
        var scene = new BABYLON.Scene(engine);
        var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene);

        camera.attachControl(canvas);

        // Creating sphere
        var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene);

        var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, {
            vertexElement: "vertexShaderCode",
            fragmentElement: "fragmentShaderCode",
        },
        {
            attributes: ["position", "uv"],
            uniforms: ["worldViewProjection"]
        });
        amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene));

        sphere.material = amigaMaterial;

        engine.runRenderLoop(function () {
            sphere.rotation.y += 0.05;
            scene.render();
        });
    }
};

Ich benutze hier BABYLON.ShaderMaterial, um mich weder mit dem Erstellen, dem Verlinken noch der Bearbeitung der Shader direkt beschäftigen zu müssen.

Wer ein BABYLON.ShaderMaterial erstellt, muss das DOM-Element angeben, wo die Shader abgelegt sind oder den Basisnamen der Dateien, in denen sich die Shader befinden. Bei der Nutzung von Dateien muss für jeden Shader nach folgendem Muster eine eigene Datei erzeugt werden: basename.vertex.fx und basename.fragment,.fx. Dann wird das Material wie folgt erstellt:

var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader",
        {
            attributes: ["position", "uv"],
            uniforms: ["worldViewProjection"]
        });

Außerdem müssen die Namen der benutzten Attributes und Uniforms angegeben werden.
Anschließend können die Werte der Uniforms und Sampler mithilfe der Funktionen setTexture, setFloat, setFloats, setColor3, setColor4, setVector2, setVector3, setVector4, setMatrix  direkt eingestellt werden.

Klingt einfach, oder?

Wer will, kann sich ja noch mal die worldViewProjection-Matrix weiter oben anschauen. Im Vergleich dazu ist die jetzt mit Babylon.js und BABYLON.ShaderMaterial erstellte Version doch das reinste Kinderspiel stimmt´s?! BABYLON.ShaderMaterial berechnet alles automatisch, weil wir es in der Liste der Uniforms so angegeben haben.

BABYLON.ShaderMaterial kann außerdem diese Matrix-Arten verarbeiten:

  • world
  • view
  • projection
  • worldView
  • worldViewProjection

Nein, Mathe braucht man hier nicht mehr. Jedes Mal wenn man zum Beispiel eine sphere.rotation.y += 0.05 veranlasst, wird die world-Matrix der Kugel erstellt und an die GPU übermittelt.

CYOS: Shader selber bauen

Jetzt können wir uns getrost auf den nächsten Level trauen. Wir werden eine Seite bauen, auf der man eigene Shader dynamisch erstellen und das Ergebnis sofort anschauen kann. Dafür nehmen wir den gerade besprochenen Code, die Seite nutzt das BABYLON.ShaderMaterial Objekt um die von uns erstellten Shader zu kompilieren und auszuführen.

Ich habe mit dem ACE Code Editor für CYOS gearbeitet – ein hervorragender Code Editor mit Syntaxmarkierern. Lohnt auf jeden Fall, sich ihn mal hier anzuschauen. Und CYOS gibt es hier.

Mit dem ersten Kombinationsfeld wählt man vorgegebene Shader aus. Welche das sind, sehen wir gleich.

Im zweiten Kombinationsfeld kann man das Gitter (das 3D Objekt) verändern, das uns eine Vorschau der Shader gibt.

Mit dem Compile Button wiederum erstellt man ein neues BABYLON.ShaderMaterial von den Shadern. Dieser Button nutzt folgenden Code:

// Compile
shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, {
    vertexElement: "vertexShaderCode",
    fragmentElement: "fragmentShaderCode",
},
    {
        attributes: ["position", "normal", "uv"],
        uniforms: ["world", "worldView", "worldViewProjection"]
    });

var refTexture = new BABYLON.Texture("ref.jpg", scene);
refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;

var amigaTexture = new BABYLON.Texture("amiga.jpg", scene);

shaderMaterial.setTexture("textureSampler", amigaTexture);
shaderMaterial.setTexture("refSampler", refTexture);
shaderMaterial.setFloat("time", 0);
shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero());
shaderMaterial.backFaceCulling = false;

mesh.material = shaderMaterial;

Unglaublich einfach, oder? Das Material ist nun in der Lage drei vorberechnete Matrizen zu übergeben: (world, worldView und worldViewProjection). Den Eckpunkten werden zugleich Position, Normalkoordinaten und Textur-Koordinaten zugeordnet. Zwei Texturen sind dabei bereits geladen:

amiga.jpg:

amiga

  • ref.jpg:

ref

Und zu guter Letzt kommt dann noch der renderLoop. Dort aktualisiere ich zwei praktische Uniforms:

  • Eine heißt time und erlaubt uns ein paar lustige Animationen.
  • Die andere ist cameraPosition, um die Kameraposition in die Shader zu bekommen (sehr sinnvoll bei Lichtberechnungen).
engine.runRenderLoop(function () {
    mesh.rotation.y += 0.001;

    if (shaderMaterial) {
        shaderMaterial.setFloat("time", time);
        time += 0.02;

        shaderMaterial.setVector3("cameraPosition", camera.position);
    }

    scene.render();
});

Dank unserer fleißigen Arbeit an Windows Phone 8.1, kann man CYOS auch auf einem Windows Phone benutzen (So kann man Shader erstellen, wo und wann es gerade passt.):

wp_ss_20140417_0001

Basic Shader

Beginnen wir also mit dem ersten Shader, der auf CYOS definiert wird: Der Basic Shader.

Den kennen wir schon. Er berechnet die gl_position und benutzt Texturkoordinaten, um für jeden Pixel eine Farbe abzurufen.

Um die Pixelposition zu berechnen brauchen wir die worldViewProjectionMatrix und die Position des Eckpunkts:

precision highp float;

// Attributes
attribute vec3 position;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
    gl_Position = worldViewProjection * vec4(position, 1.0);

    vUV = uv;
}

Texturkoordinaten (uv) werden unverändert an den Pixel Shader übermittelt.

Hinweis: Wir müssen “precision mediump float;” in der ersten Zeile hinzufügen, sowohl für den Vertex Shader als auch für den Pixel Shader; das ist für Chrome erforderlich. Um eine bessere Performance zu erreichen, wird damit festgelegt, dass keine Gleitkommawerte mit vollständiger Präzision benutzt werden.

Der Pixel Shader ist sogar noch einfacher, weil wir nur die Texturkoordinaten brauchen und die Texturfarbe abrufen:

precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
    gl_FragColor = texture2D(textureSampler, vUV);
}

Wir haben bereits gesehen, dass das uniform-Element textureSampler mit der Textur “amiga” gefüllt ist. Daher sieht das Ganze dann so aus:

image

Schwarz/Weiß Shader

Nun zum nächsten Shader: den Schwarz/Weiß Shader.

Dieser Shader basiert auf dem vorhergehenden, soll jedoch ausschließlich im Schwarz/Weiß Rendering Modus arbeiten.
Wir nehmen also den Vertex Shader, den wir schon haben. Nur den Pixel Shader werden wir leicht modifizieren.

Zunächst beschränken wir uns auf nur eine Komponente, in diesem Fall hier die grüne:

precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;
void main(void) {
    gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0);
}

Deutlich zu sehen: Statt .rgb (diese Funktion nennt man Swizzle) haben wir .ggg benutzt.

Um aber einen wirklich korrekten Schwarz/Weiß-Effekt zu erzielen, sollten wir außerdem die Helligkeit berechnen (dabei alle Komponenten in Betracht ziehend):

precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
    float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11));
    gl_FragColor = vec4(luminance, luminance, luminance, 1.0);
}

Die Punktfunktion (oder das Punktprodukt) wird folgendermaßen berechnet:

result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z

In unserem Beispiel also:

luminance = r * 0.3 + g * 0.59 + b * 0.11 (Diese Werte berücksichtigen, dass das menschliche Auge Grüntöne besser wahrnimmt.)

Na wenn das nicht ziemlich cool klingt, oder?

image

Cel Shading Shader

Kommen wir jetzt zu einem etwas komplexeren Shader, dem Cel Shading Shader (fälschlicherweise oft als „cell shading“ geschrieben).

Bei diesem brauchen wir die Normalkoordinaten und die Positionen der Eckpunkte im Pixel Shader. Demzufolge sieht der Vertex Shader so aus:

precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 world;
uniform mat4 worldViewProjection;

// Varying
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUV;

void main(void) {
    vec4 outPosition = worldViewProjection * vec4(position, 1.0);
    gl_Position = outPosition;

    vPositionW = vec3(world * vec4(position, 1.0));
    vNormalW = normalize(vec3(world * vec4(normal, 0.0)));

    vUV = uv;
}

Hinweis: Wir benutzen hier auch die world-Matrix, weil Position und Normalkoordinaten ohne Umwandlung gespeichert sind. Die Anwendung der world-Matrix erlaubt es uns, die Rotation des Objekts mit einzuberechnen.

Der Pixel Shader sieht dann wie folgt aus:

precision highp float;

// Lights
varying vec3 vPositionW;
varying vec3 vNormalW;
varying vec2 vUV;

// Refs
uniform sampler2D textureSampler;

void main(void) {
    float ToonThresholds[4];
    ToonThresholds[0] = 0.95;
    ToonThresholds[1] = 0.5;
    ToonThresholds[2] = 0.2;
    ToonThresholds[3] = 0.03;

    float ToonBrightnessLevels[5];
    ToonBrightnessLevels[0] = 1.0;
    ToonBrightnessLevels[1] = 0.8;
    ToonBrightnessLevels[2] = 0.6;
    ToonBrightnessLevels[3] = 0.35;
    ToonBrightnessLevels[4] = 0.2;

    vec3 vLightPosition = vec3(0, 20, 10);

    // Light
    vec3 lightVectorW = normalize(vLightPosition - vPositionW);

    // diffuse
    float ndl = max(0., dot(vNormalW, lightVectorW));

    vec3 color = texture2D(textureSampler, vUV).rgb;

    if (ndl > ToonThresholds[0])
    {
        color *= ToonBrightnessLevels[0];
    }
    else if (ndl > ToonThresholds[1])
    {
        color *= ToonBrightnessLevels[1];
    }
    else if (ndl > ToonThresholds[2])
    {
        color *= ToonBrightnessLevels[2];
    }
    else if (ndl > ToonThresholds[3])
    {
        color *= ToonBrightnessLevels[3];
    }
    else
    {
        color *= ToonBrightnessLevels[4];
    }

    gl_FragColor = vec4(color, 1.);
}

Aufgabe dieses Shaders ist es, eine Lichtquelle zu simulieren. Dabei wird auf weich verlaufende Schattierungen verzichtet, stattdessen kommen Helligkeitsstufen zum Einsatz. Ist die Lichtintensität zum Beispiel zwischen 1 (Maximum) und 0,95, wird die Farbe des Objekts (abgerufen von der Textur) direkt angewendet. Liegt die Intensität zwischen 0,95 und 0,5, wird die Farbe um den Faktor 0,8 abgeschwächt usw.

Diesen Shader erhalten wir in vier Schritten:

  • Zuerst geben wir Helligkeitsstufen und Levelkonstanten an.
  • Dann berechnen wir mithilfe der Phong-Gleichung die Lichtwerte (Wir gehen dabei davon aus, dass sich die Lichtquelle nicht bewegt.):
vec3 vLightPosition = vec3(0, 20, 10);

// Light
vec3 lightVectorW = normalize(vLightPosition - vPositionW);

// diffuse
float ndl = max(0., dot(vNormalW, lightVectorW));

Die Lichtintensität pro Pixel ist abhängig vom Winkel zwischen den Normalkoordinaten und der Lichtrichtung.

  • Anschließend erhalten wir die Texturfarbe für die Pixel
  • Zum Schluss kontrollieren wir die Helligkeitsstufen und wenden den jeweiligen Level auf die Farbe an.

Das sieht dann ein bisschen aus wie ein Cartoon-Objekt:

image

Phong Shader

Im vorherigen Shader haben wir einen Teil der Phong-Gleichung benutzt. Jetzt nehmen wir einfach mal die ganze Gleichung.

Der Vertex Shader ist leicht, weil die Action ausschließlich im Pixel Shader stattfindet:

precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUV;

void main(void) {
    vec4 outPosition = worldViewProjection * vec4(position, 1.0);
    gl_Position = outPosition;

    vUV = uv;
    vPosition = position;
    vNormal = normal;
}

Gemäß der Gleichung müssen wir die diffuse Reflexion und die Spekularität mithilfe der Lichtrichtung und den Normalkoordinaten der Eckpunkte berechnen:

precision highp float;

// Varying
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUV;

// Uniforms
uniform mat4 world;

// Refs
uniform vec3 cameraPosition;
uniform sampler2D textureSampler;

void main(void) {
    vec3 vLightPosition = vec3(0, 20, 10);

    // World values
    vec3 vPositionW = vec3(world * vec4(vPosition, 1.0));
    vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0)));
    vec3 viewDirectionW = normalize(cameraPosition - vPositionW);

    // Light
    vec3 lightVectorW = normalize(vLightPosition - vPositionW);
    vec3 color = texture2D(textureSampler, vUV).rgb;

    // diffuse
    float ndl = max(0., dot(vNormalW, lightVectorW));

    // Specular
    vec3 angleW = normalize(viewDirectionW + lightVectorW);
    float specComp = max(0., dot(vNormalW, angleW));
    specComp = pow(specComp, max(1., 64.)) * 2.;

    gl_FragColor = vec4(color * ndl + vec3(specComp), 1.);
}

Die diffuse Reflexion hatten wir schon im Shader davor, wir müssen also nur noch den Spiegeleffekt hinzufügen. Diese Illustration eines Wikipedia-Artikels zeigt sehr gut, wie der Shader letztlich funktioniert:

image

Bei unserer Kugel sieht das so aus:

image

Discard Shader

Für den Discard Shader stelle ich an dieser Stelle ein neues Konzept vor: Das Discard Keyword.

Dieser Shader wird jeden nicht-roten Pixel verwerfen und ein Dug-Objekt vortäuschen.

Der Vertex Shader ist der gleiche wie am Anfang beim Basic Shader:

precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;

// Varying
varying vec2 vUV;

void main(void) {
    gl_Position = worldViewProjection * vec4(position, 1.0);

    vUV = uv;
}

Der Pixel Shader prüft die Farbe und nutzt die Discard-Funktion, wenn zum Beispiel die grüne Komponente zu hoch ist:

precision highp float;

varying vec2 vUV;

// Refs
uniform sampler2D textureSampler;

void main(void) {
    vec3 color = texture2D(textureSampler, vUV).rgb;

    if (color.g > 0.5) {
        discard;
    }

    gl_FragColor = vec4(color, 1.);
}

Das sieht dann etwas seltsam aus:

image

Wave Shader

Nachdem wir mit dem Pixel Shader unseren Spaß hatten, will ich nun zeigen, dass auch der Vertex Shader eine Menge kann.

Für den Wave Shader werden wir den Phong Pixel Shader wiederverwenden.

Der Vertex Shader benutzt das uniform-Element time um einige Animationswerte zu erhalten. Dadurch wird der Shader eine Welle mit den Positionswerten der Eckpunkte erzeugen:

precision highp float;

// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

// Uniforms
uniform mat4 worldViewProjection;
uniform float time;

// Varying
varying vec3 vPosition;
varying vec3 vNormal;
varying vec2 vUV;

void main(void) {
    vec3 v = position;
    v.x += sin(2.0 * position.y + (time)) * 0.5;

    gl_Position = worldViewProjection * vec4(v, 1.0);

    vPosition = position;
    vNormal = normal;
    vUV = uv;
}

Nachdem eine Sinuskurve auf die position.y angewendet wird, erhalten wir dieses Ergebnis:

image

Spherical Environment Mapping

Diese Variante wurde größtenteils von diesem Tutorial inspiriert. Ein wirklich exzellenter Artikel, den es sich zu lesen lohnt, genau wie das Experimentieren mit dem dazugehörigen Shader.

image

Fresnel Shader

Zum Abschluss will ich auch noch meinen Favoriten vorstellen: den Fresnel Shader.
Dieser Shader variiert die Intensität abhängig vom Winkel zwischen der Blickrichtung und den Normalkoordinaten der Eckpunkte.

Der Vertex Shader ist der gleiche, den wir für den Cel Shading Shader benutzt haben und es nicht schwer, die Fresnel Reflexion in unseren Pixel Shader hineinzurechnen (Wir haben schließlich die Normalkoordinaten und die Kameraposition, um die Blickrichtung zu beurteilen):

precision highp float;

// Lights
varying vec3 vPositionW;
varying vec3 vNormalW;

// Refs
uniform vec3 cameraPosition;
uniform sampler2D textureSampler;

void main(void) {
    vec3 color = vec3(1., 1., 1.);
    vec3 viewDirectionW = normalize(cameraPosition - vPositionW);

    // Fresnel
    float fresnelTerm = dot(viewDirectionW, vNormalW);
    fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.);

    gl_FragColor = vec4(color * fresnelTerm, 1.);
}

image

Oder doch einen ganz anderen Shader?

Genug der Vorbereitung, jetzt kann jeder daran gehen, einen eigenen Shader zu bauen. Eigene Erfahrungen können gern hier im Kommentarfeld oder im babylon.js Forum geteilt werden (Link s. unten).

Wer sich noch intensiver mit dem Thema beschäftigen will, hier ein paar nützliche Links:

Und hier noch ein bisschen Lehrmaterial von meiner Seite:

Oder, wer noch einen Schritt zurückgehen will – die Schulungsreihe über JavaScript von unserem Team:

Und jeder ist natürlich eingeladen, unsere kostenlosen Werkzeuge für sein nächstes Web-Projekt zu nutzen: Visual Studio Community, Azure Trial und Cross-Browser Testwerkzeuge für Mac, Linux oder Windows.

Dieser Artikel ist Teil der Web Dev Tech Series von Microsoft. Wir freuen uns, das Microsoft Edge und seine neue EdgeHTML Rendering Engine mit euch zu teilen. Kostenlose Virtual Machines oder Remote Testings für Mac, iOS, Android oder Windows gibt es hier: dev.modern.IE.

Kategorien
JavaScript & jQuery Programmierung

Nicht OOP, aber nah dran: Einfache Vererbung mit JavaScript

Viele meiner Freunde sind C#- oder C++-Entwickler und sind daran gewöhnt, in ihren Projekten mit Vererbung zu arbeiten. Wenn der eine oder die andere darüber nachdenkt, es auch mal mit JavaScript zu versuchen, ist eine der ersten Fragen: “Kann ich denn in JavaScript eigentlich auch die Vererbung nutzen?”

simpleinheritance-teaser_DE

JavaScript benutzt einen anderen Ansatz als C# oder C++, um objektorientierte Programmiermuster zu ermöglichen: Es ist eine prototypenbasierte Sprache. Die Nutzung von Prototypen bedeutet, dass das Verhalten (Behaviors) von Objekten wiederverwendet werden kann, indem man bereits vorhandene Objekte klont (also nachbildet), die als Prototypen dienen. Jedes Objekt in JavaScript ist von einem Prototypen abhängig, der einen Satz von Funktionen und Attributen (Members) vorgibt, den das Objekt nutzen kann. Klassen gibt es nicht. Nur die Objekte. Und jedes Objekt kann dann als Prototyp für ein weiteres Objekt dienen.

Dieses Konzept ist außerordentlich flexibel und wir können damit OOP-Paradigmen, wie eben auch Vererbung, simulieren.

Vererbung umsetzen

Am besten, wir visualisieren erst einmal, was wir genau erreichen wollen – mithilfe einer Hierarchie:

image

Zunächst erzeugen wir ClassA, das geht leicht Weil es wie gesagt keine eindeutigen Klassen gibt, können wir eine Reihe von Behaviors festlegen (A class so…), indem wir einfach folgende Funktion erstellen:

var ClassA = function() {
this.name = „class A“;
}

Diese “Klasse” kann nun mit dem new-Keyword instanziiert werden:

var a = new ClassA();
ClassA.prototype.print = function() {
    console.log(this.name);
}

Und sie wird unser Objekt nutzen:

a.print();

Eigentlich ganz einfach, oder?
Das gesamte Beispiel ist gerade mal acht Zeilen lang:

var ClassA = function() {
    this.name = "class A";
}

ClassA.prototype.print = function() {
    console.log(this.name);
}

var a = new ClassA();

a.print();

Jetzt werden wir ein Tool hinzufügen, mit dem wir eine Art Vererbung zwischen Klassen erzeugen können. Es hat nur eine Aufgabe – den Prototypen zu klonen:

var inheritsFrom = function (child, parent) { child.prototype = Object.create(parent.prototype); };

Wenn das nicht fast nach Zauberei aussieht! Mit dem Kopieren des Prototypen übertragen wir alle Attribute und Funktionen auf die neue Klasse.

Wenn wir also eine zweite Klasse als abgeleitete Klasse der ersten hinzufügen wollen, brauchen wir nichts weiter als diesen Code:

var ClassB = function() {
    this.name = "class B";
    this.surname = "I'm the child";
}

inheritsFrom(ClassB, ClassA);

Weil ClassB nun die print Funktion von ClassA geerbt hat, funktioniert dann auch der folgende Code:

var b = new ClassB();
b.print();

Und führt zu dem folgenden Ergebnis:

class B

Wir können die print-Funktion für ClassB aufheben:

ClassB.prototype.print = function() {
    ClassA.prototype.print.call(this);
    console.log(this.surname);
}

In diesem Fall erhalten wir dieses Resultat:

class B
I’m the child

Der Trick besteht darin, ClassA.prototype aufzurufen um die print-Basisfunktion zu erhalten. Und mithilfe der call-Funktion können wir dann die Basisfunktion des aktuellen Objekts (this) aufrufen. Wie wir ClassC erstellen ist nun offensichtlich:

var ClassC = function () {
    this.name = "class C";
    this.surname = "I'm the grandchild";
}

inheritsFrom(ClassC, ClassB);

ClassC.prototype.foo = function() {
    // Do some funky stuff here...
}

ClassC.prototype.print = function () {
    ClassB.prototype.print.call(this);
    console.log("Sounds like this is working!");
}

var c = new ClassC();
c.print();

Und das kommt dabei heraus:

class C
I’m the grandchild

Das scheint doch zu funktionieren!

Noch mehr Tipps, Tricks und Hilfen rund um JavaScript

Es klingt vielleicht seltsam, aber Microsoft bietet eine ganze Reihe kostenloser Lehrangebote zu zahlreichen Open Source JavaScript-Themen an – und mit der anstehenden Veröffentlichung von Microsoft Edge werden wir in diesem Bereich noch sehr viel mehr tun. Auch von mir gibt es Tutorials:

Außerdem haben wir noch die Schulungsreihe unseres Teams:

Praktisch sind zudem die folgenden kostenlosen Tools: Visual Studio Community, Azure Trial, und Cross-Browser Testing Tools für Mac, Linux, oder Windows.

Und das noch mit auf den Weg…

Um es abschließend noch mal klar zu sagen: JavaScript ist nicht C# oder C++. Diese Skriptsprache hat ihre eigene Philosophie. Für alle C++- oder C#-Entwickler, die sich die ganze Leistung von JavaScript zu Eigen machen wollen, habe ich diesen wichtigen Tipp: Versucht nicht eure Lieblingssprache in JavaScript nachzubilden. Es gibt nicht die beste oder die schlechteste Sprache. Nur unterschiedliche Ansätze und Philosophien!

Dieser Artikel ist Teil der Web Dev Tech Series von Microsoft. Wir freuen uns, Microsoft Edge und seine neue EdgeHTML Rendering Engine mit euch zu teilen. Kostenlose Virtual Machines oder Remote Testings für Mac, iOS, Android oder Windows gibt es hier: dev.modern.IE.

Kategorien
Design JavaScript & jQuery Webdesign

CSS3: Animation mit play() und pause() steuern

Seit Einführung der „animate()“-Methode ist es möglich, CSS3-Animationen direkt per JavaScript zu erstellen, ohne dass diese in irgendeinem Stylesheet definiert sein müssen. Fortan können wir diese Animationen nun auch mit den Methoden „play()“ und „pause()“ steuern – also ganz so, wie man es von der Audio- und Videosteuerung her kennt. Darüber hinaus stehen mit „cancel()“ und „reverse()“ weitere Methoden zur Verfügung, die eine umfangreiche Steuerung von CSS3-Animationen ermöglichen.

css3-playpause-teaser_DE

CSS3-Animation per „animate()“ erstellen

Zunächst einmal erstellen wir eine Animation per JavaScript und der „animate()“-Methode, denn es lassen sich keine Animationen steuern, die per CSS3-Eigenschaft „animation“ und der „@keyframes“-Regel über ein Stylesheet erstellt wurden.

var animation = document.getElementById("animation").animate([
  {transform: "rotate(0)"},
  {transform: "rotate(90deg)"}
 ], 5000);

Im Beispiel wird einem Element mit der ID „animation“ die „animate()“-Methode zugewiesen. Über diese Methode drehen wir das Element innerhalb von fünf Sekunden um 90 Grad. Wie die Animationen per JavaScript genau funktionieren, habe ich bereits in einem anderen Artikel erläutert. Über die Variable „animation“ ist es nun möglich, die Animation zu steuern.

document.getElementById("animation").addEventListener("click", function() {
  animation.pause();
 }, false);

Im Beispiel löst ein Click-Event die Methode „pause()“ aus, sobald das animierte Element angeklickt wird. Die Animation wird somit angehalten. Mit der Methode „play()“ wird die Animation fortgesetzt. Jetzt wird zum Pausieren und Fortsetzen häufig derselbe Button verwendet – im Beispiel ist es das animierte Element selbst, über welches die Wiedergabe gesteuert wird.

Um die Methoden „pause()“ und „play()“ je nach Status in einer Funktion anwenden zu können, gibt es bei Videos die Eigenschaft „played“, die einem verrät, ob das Video derzeit wiedergegeben wird oder nicht. Für die „animate()“-Methode steht leider keine Eigenschaft zur Verfügung, welche den Wiedergabestatus auslesen kann. Allerdings gibt es die Eigenschaft „currentTime“, welche die aktuelle Wiedergabezeit der Animation in Millisekunden einschließlich mehrerer Nachkommastellen misst und ausgibt.

Unter Zuhilfenahme dieser Eigenschaft lässt sich relativ einfach feststellen, ob die Animation gerade ausgeführt wird oder nicht.

var zeit;
document.getElementById("animation").addEventListener("click", function() {
  if (zeit == animation.currentTime) {
    animation.play();
  } else {
    zeit = animation.currentTime;
    animation.pause();
  }
}, false);

Im Beispiel definieren wir zunächst eine Variable „zeit“. Die darauf folgende Funktion prüft per „if“-Anweisung, ob der Wert von „zeit“ identisch ist mit der aktuellen Wiedergabezeit „currentTime“ der Animation. Da beim ersten Klick „zeit“ noch keinen Wert hat, wird der „else“-Abschnitt der Funktion ausgeführt. Das heißt, die Wiedergabezeit wird der Variablen „zeit“ zugewiesen und die Animation pausiert. Da die Wiedergabezeit steht, ist sie solange mit „zeit“ identisch, bis wir per „play()“ die Animation fortsetzen.

Mit dieser kleinen Abfrage wird per Klick auf das Animationselement die Wiedergabe angehalten und beim erneuten Klick fortgeführt.

Richtung und Geschwindigkeit ändern

animate-play-pause

Eine Besonderheit an den neuen Steuerungsmethoden für CSS3-Animationen ist die „reverse()“-Methode. Sie erlaubt es, eine Animation rückwärts abzuspielen. Ersetzt man im obigen Beispiel die Methode „pause()“ mit „reverse()“, kann man die Abspielrichtung der Animation per Mausklick jeweils umkehren.

animation.reverse();

Außerdem ist es möglich, mit der Eigenschaft „playbackRate“ die Abspielgeschwindigkeit zu ändern. Erlaubt ist ein beliebiger positiver oder negativer Dezimalwert. Der Wert 1 steht dabei für die normale Geschwindigkeit, die in der „animate()“-Methode vorgegeben wurde. Im Beispiel benötigt die Animation bei Normalgeschwindigkeit fünf Sekunden.

Ein Wert größer 1 lässt die Animation schneller abspielen. Bei einem Wert von 2 würde die Animation also in doppelter Geschwindigkeit laufen. Werte kleiner als 1 sorgen für eine langsamere Wiedergabe. Bei einer negativen Rate wird die Animation rückwärts abgespielt. Auch hier gilt, dass Werte größer -1 die Animation langsamer und Werte kleiner als -1 die Animation schneller abspielen.

animation.playbackRate = 1.5;

Hat „playbackRate“ den Wert 0, kommt die Animation zum Stillstand. Da die Geschwindigkeit während der Wiedergabe der Animation geändert werden kann, lassen sich zusammen mit „requestAnimationFrame()“ sehr einfach Beschleunigungen oder Abbremsungen in die Animation einbinden.

window.requestAnimationFrame(beschleunigung); 
function beschleunigung() {
  player.playbackRate += 0.05;
  window.requestAnimationFrame(beschleunigung);
}

Im Beispiel erhöhen wir die Geschwindigkeit bei jedem Framewechsel um 0,05, so dass die Animation stets beschleunigt, bis das Ende erreicht ist.

Als letzte Methode kennt die „animate()“-Steuerung noch„cancel()“. Sie sorgt dafür, dass die Animation unmittelbar abgebrochen wird und zum Ausgangspunkt zurückkehrt.

animation.cancel();

Auf das Ende einer Animation reagieren

Oftmals soll am Ende einer Animation eine Funktion ausgeführt werden, um beispielsweise etwas zu laden oder eine Meldung auszugeben. Über den Event-Handler „finish“ können wir am Abspielende einer Animation eine beliebige Funktion auslösen.

animation.addEventListener("finish", function() {
  console.log("Die Animation wurde beendet.");
}, false);

Im Beispiel wird das „finish“-Event per „addEventListener()“ der Variablen „animation“ zugewiesen. Sobald die Animation beendet wurde, wird per „console.log()“ ein Text in die Konsole geschrieben. Das „finish“-Event wird übrigens auch dann ausgelöst, wenn die Animation per „cancel()“ abgebrochen wird. Die Methode „pause()“ hingegen löst das Ereignis nicht aus.

 Browsersupport

Derzeit werden die hier vorgestellten Methoden und Eigenschaften zur Wiedergabesteuerung vom Chrome ab Version 39 unterstützt. Andere Browser kennen die Methoden noch nicht.

(dpe)

Kategorien
JavaScript & jQuery Programmierung

Zahl, E-Mail-Adresse oder Datum: is.js findet es heraus

Die JavaScript-Bibliothek is.js ist ein umfangreiches Werkzeug, um Zahlen, Zeichenketten, Arrays und Objekte auf bestimmte Eigenschaften zu überprüfen. So lässt sich mit is.js zum Beispiel feststellen, ob es sich bei einer Zeichenkette um ein Datum oder eine E-Mail-Adresse handelt. Auch lässt sich herausfinden, ob ein Wert beispielsweise ein Array oder ein boolescher Ausdruck ist. Die Bibliothek umfasst zahlreiche Methoden, die zur Validierung von Formulareingaben oder für Rechenoperationen eingesetzt werden können. Die Verwendung von is.js ist dabei so einfach und intuitiv, dass es keiner umfangreichen Dokumentation bedarf.

isjs-teaser_DE

Einfacher Einstieg

Da is.js ohne jQuery oder andere Bibliotheken auskommt, genügt es also, die JavaScript-Datei is.js am Ende des HTML-Bodys einzubinden. Anschließend stehen einem alle Methoden zur Verfügung. Um beispielsweise festzustellen, ob es sich bei einem Wert um eine Zahl handelt, genügt der Aufruf von „is.number()“. Handelt es sich bei dem übergebenen Wert tatsächlich um eine Zahl, gibt die Methode „true“ zurück, andernfalls „false“.

is.number(32);

Im Beispiel wird „true“ zurückgegeben. Alternativ gibt es bei allen Methoden auch die Möglichkeit, eine umgekehrte Prüfung mit „is.not“ durchzuführen – zum Beispiel: „is.not.number()“.

is.not.number(32);

In diesem Fall würde das Beispiel „false“ zurückgeben.

Eine Besonderheit von is.js besteht darin, mehrere Werte gleichzeitig prüfen zu können. Dazu übergibt man mehrere Zahlen oder Zeichenketten entweder einfach per Komma voneinander getrennt oder als Array. Per „is.all“ oder „is.any“ kann dann geprüft werden, ob alle Werte oder mindestens einer der Werte dem Kriterium der Methode entsprechen.

is.all.number(32, "vier");
is.all.number([32, "vier"]);

Das Beispiel zeigt die beiden unterschiedlichen Möglichkeiten der Schreibweise – nur per Komma und als Array. Im Beispiel wird die Methode „false“ zurückgeben, da der zweite Wert jeweils eine Zeichenkette und keine Zahl ist.

is.any.number(32, "vier");
is.any.number([32, "vier"]);

Nimmt man statt „is.all.number()“ nun „is.any.number()“, wird im Beispiel „true“ wiedergegeben, da mindestens einer der übergebenen Werte eine Zahl ist.

isjs

Vom Prinzip funktionieren die allermeisten Methoden von is.js auf diese Weise. „is.number()“ gehört hierbei zu einer von 16 Methoden, mit denen der Typ eines Wertes geprüft werden kann. Zu den weiteren Methoden zur Erkennung eines Typs gehören zum Beispiel „is.string()“ für Zeichenketten, „is.boolean()“ für boolesche Ausdrücke und „is.array()“ für Arrays.

is.array(["Apfel", "Birne"]);
is.all.array(["Apfel", "Birne"], "Kirsche");

Auch bei „is.array()“ lassen sich mehrere Werte gleichzeitig überprüfen. Während der erste Aufruf im Beispiel „true“ wiedergibt, wird im zweiten „false“ wiedergegeben, da dort neben einem Array auch eine einfache Zeichenkette aufgeführt ist.

Neben der Typ-Methoden gibt es zudem einige Methoden, mit denen beispielsweise Arrays und Ausdrücke darauf hin geprüft werden können, ob sie vorhanden, leer, wahr oder falsch sind.

is.existy({});
is.empty({});

Im Beispiel wird jeweils ein leeres Literalobjekt übergeben. Sowohl „is.existy()“ als auch „is.empty()“ werden „true“ rückmelden, da das Literalobjekt sowohl vorhanden als auch leer ist.

Mit regulären Ausdrücken Zeichenktten prüfen

Zwar bieten die neuen HTML-Eingabefelder die Möglichkeit, E-Mail-Adressen und URLs auf ihre Validität zu überprüfen. Dennoch gibt es auch per is.js diese und andere Methoden, um bestimmte Zeichenketten anhand vordefinierter regulärer Ausdrücke zu validieren.

So stehen einem neben „is.email()“ und „is.url()“ weitere Methoden wie „is.dateString()“ und „is.timeString()“ zur Verfügung, mit denen geprüft wird, ob ein Wert einer Datums- beziehungsweise Zeitangabe entspricht.

is.dateString("03/05/2015");
is.timeString("13:45:30");

Auch Kreditkartennummern sowie US-amerikanische und kanadische Postleitzahlen können mit entsprechenden Methoden verifiziert werden. Mit „is.ip()“, „is.ipv4()“ und „is.ipv6()“ püft man zudem IP-Adressen. Insgesamt 18 Methoden stehen bereit, um Zeichenketten nach einem bestimmten Muster prüfen zu lassen.

Browser und Betriebssysteme feststellen

Gerade für komplexe Webanwendungen ist es häufig wichtig, den verwendeten Browser beziehungsweise das verwendete Betriebssystem ausfindig zu machen. Auch hier stellt is.js einige Möglichkeiten zur Verfügung. Statt sich aus den Angaben des User-Agents die entsprechenden Informationen herauszusuchen, verfügt es mit „is.firefox()“, „is.chrome()“ und „is.ie()“ über einfache Methoden, um den verwendeten Browser festzustellen.

is.ie();
is.ie(7);

In den beiden Beispielen wird zunächst geprüft, ob der Internet Explorer in einer beliebigen Version verwendet wird. Wird der Methode eine Versionsnummer übergeben, wird auch diese bei der Verifizierung berücksichtigt. Das zweite Beispiel prüft also explizit, ob der Internet Explorer in der Version 7 genutzt wird.

Mit „is.android“, „is.ios()“ und „is.windows()“ lässt sich zudem das verwendete Betriebssystem ganz leicht feststellen. Bestimmte Versionen können hierbei jedoch nicht berücksichtigt werden.

Gerade bei der Entwicklung von Webanwendungen für Mobilgeräte kann es wichtig sein, den Gerätetyp festzustellen. Mit „is.tablet()“, „is.mobile()“ und „is.desktop()“ kann zwischen Tablet, Smartphone und klassischem Desktoprechner unterschieden werden. Außerdem lässt sich bei den Mobilgeräten noch herausfinden, ob es sich um Android- oder Windows-Geräte handelt – mit „is.androidTablet()“ und „is.windowsTablet()“.

is.androidTablet();
is.windowsTablet();

Zeiten prüfen

Mit Zeiten zu rechnen, ist nicht immer einfach. Daher stellt is.js hierfür einige ebenso praktische wie einfache Methoden bereit. So kann man mit „is.today()“ zum Beispiel herausfinden, ob eine Zeitangabe dem aktuellen Tag entspricht. Mit „is.yesterday()“ und „is.tomorrow()“ wird hingegen geprüft, ob eine Zeitangabe dem gestrigen beziehungsweise morgigen Tag entspricht.

„is.past()“ und „is.future()“ stellen fest, ob ein Datum in der Vergangenheit oder der Zukunft liegt.

is.today(new Date());

Im Beispiel wird geprüft, ob die aktuelle Zeit dem heutigen Tag entspricht. In diesem Fall wird immer „true“ wiedergegeben.

Mit „is.day()“, „is.month()“ und „is.year()“ kann man darüber hinaus feststellen, ob ein Datum an einem bestimmten Wochentag, in einem bestimmten Monat oder Jahr liegt. Dazu wird den Methoden zum einen das Datum, zum anderen der Wochentag, Monat beziehungsweise das Jahr übergeben, für welches das Datum geprüft werden soll.

is.day(new Date("01/26/2015"), "monday");

Im Beispiel wird geprüft, ob der 26. Januar 2015 ein Montag war.

„is.inDateRange()“ verrät, ob eine Zeitangabe innerhalb eines bestimmten Zeitraums liegt. Dazu wird die zu überprüfende Zeitangabe sowie Anfangs- und Endzeit der Zeitspanne übergeben.

is.inDateRange(new Date("01/25/2015"), new Date("01/24/2015"), new Date('01/26/2015"));

Hier wird geprüft, ob der 25. Januar 2015 zwischen dem 24. und 26. Januar des Jahres liegt.

Insgesamt gibt es 20 verschiedene Methoden zur Überprüfung von Zeitangaben. Unter anderem lassen sich noch Wochenenden („is.weekend()“), Schaltjahre („is.leapYear()“) und Jahresquartale („is.quarterOfYear()“) feststellen.

Lizenz und Link zum Beitrag

is.js steht unter der MIT-Lizenz, kann also für beliebige Projekte verwendet werden. Die Datei ist in der komprimierten Form keine 14 Kilobyte groß.

(dpe)