KonvaJS - 2D für JavaScript

Eigentlich bauen wir ja 3D-Konfiguratoren. Manche Dinge sind aber intrinsisch zweidimensional. Ein Fenster vielleicht oder die Dielen auf einer Terrasse. Dann brauchen wir auch mal eine 2D-Darstellung.

Bislang haben wir das oft mit fabricjs gemacht. Jetzt haben wir uns einmal KonvaJS angesehen, eine 2D-Bibliothek für JavaScript, die den Canvas abstrahiert.

KonvaJS

Erste Schritte

Das erste, was man auf der Homepage wahrnimmt, ist eine wirklich erstklassige Dokumentation. Damit sind die ersten Schritte schnell erledigt:

npm install konva

Im JavaScript macht man zunächst eine Stage auf und legt dabei die Dimensionen des Bildes fest und natürlich, wo im HTML es eingefügt werden soll:

var stage = new Konva.Stage({
  container: 'container',   // id of container <div>
  width: 500,
  height: 500
});

In die Stage können nun mehrere Layer eingefügt werden. Jeder Layer hat sein eigenes Canvas, damit Aktualisierungen möglichst schnell durchgeführt werden können:

var layer = new Konva.Layer();
stage.add(layer);

Dann kann man eine Group anlegen um darin die eigentlichen Objekte zu gruppieren. Man muss das nicht tun. Es ist aber praktisch, weil man damit Objekte gemeinsam z.B. verschieben und drehen kann. Und - und das ist besonders hilfreich - über die Skalierung einer Gruppe kann man ein eigenes Koordinatensystem einführen, dass unabhängig von der Größe des Canvas ist. Damit kann man beispielsweise gedanklich immer in cm arbeiten:

var group = new Konva.Group({
  x: 120,
  y: 40,
  rotation: 20,
  scale: {x: 0.4, y: 0.4}
});
layer.add(group);      

Schliesslich können in die Gruppe - oder direkt in einen Layer - die eigentlichen Objekte, Shapes genannt, eingefügt werden:

var circle = new Konva.Circle({
  x: stage.width() / 2,
  y: stage.height() / 2,
  radius: 70,
  fill: 'red',
  stroke: 'black',
  strokeWidth: 4
});
group.add(circle);

Fertig. Jetzt wird das alles gezeichnet:

layer.draw();

Und das Ergebnis ist ein roter Kreis:

KonvaJS

Insgesamt ergibt sich also ein Baum aus Stage, Layern, Groups und Shapes, ein bisschen, wie wir es im 3D-Bereich auch gewohnt sind.

Shapes

Es gibt eine ganze Reihe vorgefertigter Shapes, natürlich Rechteck, Kreis, Linie (auch Splines) usw. Man kann aber auch eigene Shapes erzeugen:

var rect = new Konva.Shape({
  x: 10,
  y: 20,
  fill: '#00D2FF',
  width: 100,
  height: 50,
  sceneFunc: function (context, shape) {
    context.beginPath();
    // don't need to set position of rect, Konva will handle it
    context.rect(0, 0, shape.getAttr('width'), shape.getAttr('height'));
    // (!) Konva specific method, it is very important
    // it will apply are required styles
    context.fillStrokeShape(shape);
  }
});

Hier ist context ein Wrapper um den 2D-Kontext, der dessen Methoden und noch ein paar mehr zur Verfügung stellt.

Events und drag/drop

Für die Interaktion gibt es Events, für die Listener genau so auf den Shapes registriert werden können, wie man sich das wünschen würde:

circle.on('mouseover', function () {
    writeMessage('Mouseover circle');
});

Drag/drop braucht man aber nicht selbst zu implementieren, da gibt es eine vorgefertiges Lösung:

var draggableCircle = new Konva.Circle({
  x: stage.width() / 2,
  y: stage.height() / 2,
  radius: 70,
  fill: 'red',
  stroke: 'black',
  strokeWidth: 4,
  draggable: true,
  dragBoundFunc: function (pos) {
    return {
    x: pos.x,
    y: this.absolutePosition().y,
  };
});
group.add(draggableCircle);

Dabei ist die dragBoundFunc optional. Sie schränkt die Bewegungsfreiheit des Shapes ein. Im Beispiel kann der Kreis nur in x-Richtung verschoben werden. Einfach, oder?

Sonstiges

Konva kann noch viiiiel mehr. Zum Beispiel gibts eine React-Anbindung, viele Transformationen, Clipping, Filter und Animationen. Den Zustand - das ist für unsere Konfiguratoren wichtig - kann man in JSON im- und exportieren.

Wir haben mit KonvaJS einen Terrassenplaner umgesetzt. Das ging einfach, hat Spaß gemacht und wir mussten und - trotz ziemlich vieler Shapes - nicht mal allzusehr um Performance kümmern.

Thanks, @lavtron!