HTML5 Custom Controls und Sencha Touch
Kommentare

Da bei einem Zustandswechsel nicht immer der gesamte Zeichencode ausgeführt werden muss (es ändert sich ja im Prinzip nur der Farbgradient eines einzelnen Kreises), speichert man die verschiedenen Ebenen

Da bei einem Zustandswechsel nicht immer der gesamte Zeichencode ausgeführt werden muss (es ändert sich ja im Prinzip nur der Farbgradient eines einzelnen Kreises), speichert man die verschiedenen Ebenen der Komponente in Buffern. Diese Buffer sind ebenfalls Canvas-Elemente, die jedoch nicht in den DOM „eingehängt“ werden und somit nur im Speicher zur Verfügung stehen. Vorteil dieser Methode ist, dass man die verschiedenen Bilder im Voraus in den Speicher „zeichnen“ kann und sie später lediglich abzurufen braucht. Einzig bei einer Größenänderung der Komponente muss die gesamte Visualisierung neu durchgeführt werden. In Listing 1 wurde aus Platzgründen auf den Zeichencode verzichtet. Wie man sehen kann, kann die LED-Komponente mit einigen Parametern initialisiert werden. Werden diese nicht angegeben, nimmt man die Default-Parameter, beispielsweise die Breite und Höhe der Komponente.

Listing 1
var Led = function(param) {
  para        = para || {};
  this.id     = (undefined === param.id ? 'control' : param.id);
  this.parId  = (undefined === param.parId ? 'body' : param.parId);
  this.width  = (undefined === param.width ? 400 : param.width);
  this.height = (undefined === param.height ? 400 : param.height);
  var on      = (undefined === param.on ? false : param.on);

  // Create the  element
  var canvas    = document.createElement('canvas');
  canvas.id     = this.id;
  canvas.width  = this.width;
  canvas.height = this.height;
  if (this.parId === 'body') {
    document.body.appendChild(canvas);
  } else {
    document.getElementById(this.parId).appendChild(canvas);
  }

  // Get the canvas context and clear it
  var mainCtx = document.getElementById(this.id).getContext('2d');
  mainCtx.save();
  mainCtx.clearRect(0, 0, this.width, this.height);
  mainCtx.canvas.width  = this.width;
  mainCtx.canvas.height = this.height;

  var imageWidth  = this.width;
  var imageHeight = this.height;
  var initialized = false;

  // Initialize buffers
  var bkgBuffer    = document.createElement('canvas');
  bkgBuffer.width  = imageWidth;
  bkgBuffer.height = imageHeight;
  var bkgCtx       = bkgBuffer.getContext('2d');

  var offBuffer    = document.createElement('canvas');
  offBuffer.width  = imageWidth;
  offBuffer.height = imageHeight;
  var offCtx       = offBuffer.getContext('2d');

  var onBuffer     = document.createElement('canvas');
  onBuffer.width   = imageWidth;
  onBuffer.height  = imageHeight;
  var onCtx        = onBuffer.getContext('2d');

  var fgdBuffer    = document.createElement('canvas');
  fgdBuffer.width  = imageWidth;
  fgdBuffer.height = imageHeight;
  var fgdCtx       = fgdBuffer.getContext('2d');

  // Drawing methods
  var drawBkg = function(ctx) {...}

  var drawLed = function(ctx, isOn) {...}

  var drawFgd = function(ctx) {...}

  var init = function() {
    initialized = true;
    drawBkg(bkgCtx);
    drawLed(offCtx, false);
   drawLed(onCtx, true);
   drawFgd(fgdCtx);
  }

  // Public methods
  this.paintComponent = function() {
    if (!initialized) {
      init();
    }
    mainCtx.save();
    mainCtx.clearRect(0, 0, this.width, this.height);
    mainCtx.drawImage(bkgBuffer, 0, 0);
    if (on) {
      mainCtx.drawImage(onBuffer, 0, 0);
   } else {
      mainCtx.drawImage(offBuffer, 0, 0);
   }
   mainCtx.drawImage(fgdBuffer, 0, 0);
    mainCtx.restore();
  }

  this.setOn = function(newState) {
    on = newState;
   this.paintComponent();
  }

  this.isOn  = function() {
    return on;
  }
  this.paintComponent();
}
Listing 2
...
this.width = (undefined === param.width ? 400 : param.width);
this.height = (undefined === param.height ? 400 : param.height);
...

Wird also keine Breite (width) mit übergeben, so erhält die Breite automatisch den Wert 400, was der Breite des Originalbildes entsprach. Diese Technik entspricht einem Konstruktor, wie man ihn aus anderen Programmiersprachen wie etwa Java kennt. Im folgenden Schritt wird ein Canvas-Element erzeugt und in den HTML DOM eingefügt. Zudem holen wir uns von diesem Canvas-Element den so genannten Canvas Context (im Listing abgekürzt durch ctx), löschen seinen Inhalt und setzen seine Größe. Dieser Canvas Context stellt auch das 2-D-API zur Verfügung, mit dessen Hilfe wir die Komponente letztendlich „zeichnen“ können.

Listing 3
...
  // Create the  element
  var canvas    = document.createElement('canvas');
  canvas.id     = this.id;
  canvas.width  = this.width;
  canvas.height = this.height;
  if (this.parentId === 'body') {
    document.body.appendChild(canvas);
  } else {
    document.getElementById(this.parentId).appendChild(canvas);
  }
  
  // Get the canvas context and clear it
  var mainCtx = document.getElementById(this.id).getContext('2d');
  mainCtx.save();
  mainCtx.clearRect(0, 0, this.width, this.height);

  // Set the canvas size
  mainCtx.canvas.width  = this.width;
  mainCtx.canvas.height = this.height;
...

Da wir unser Canvas-Element angelegt und eingerichtet haben, müssen wir noch die Buffer der verschiedenen Ebenen unserer Komponente anlegen und einrichten. Für die LED-Komponente habe ich vier verschiedene Ebenen/Buffer angelegt: Background, Off, On und Foreground. Jeder dieser Buffer wird wie in Listing 4 am Beispiel des Background Buffers erzeugt und initialisiert.

Listing 4
...
  var bkgBuffer    = document.createElement('canvas');
  bkgBuffer.width  = imageWidth;
  bkgBuffer.height = imageHeight;
  var bkgCtx       = bkgBuffer.getContext('2d');
...

Es wird also ein Canvas-Element erzeugt (jedoch NICHT in den DOM „gehängt“), seine Größe gesetzt, das Canvas-Context-Objekt von diesem Canvas-Element geholt und in eine Variable gespeichert. Die verschiedenen Zeichenmethoden nehmen nun als Parameter jeweils ein Context-Element, auf dem dann die verschiedenen grafischen Elemente gezeichnet werden. Für jede Ebene wurde im Beispiel eine Zeichenmethode angelegt und nur für die LED selbst eine Methode erzeugt, da sich die beiden Zustände der LED nur durch unterschiedliche Füllungen eines Kreises unterscheiden (Listing 5).

Listing 5
...
  var drawLed = function(ctx, isOn) {
    ctx.save();
    ctx.beginPath();
    ctx.arc(0.5 * imageWidth, 0.5 * imageHeight, 
            0.25 * imageWidth, 0, 2 * Math.PI, false);
    var ledFill = 
      ctx.createLinearGradient(0.3275 * imageWidth, 
                               0.3275 * imageHeight, 
                               0.6686 * imageWidth, 
                               0.6686 * imageHeight);
    if(isOn) { // on
      ledFill.addColorStop(0.00, 'rgb(181, 0, 0)');
      ledFill.addColorStop(0.49, 'rgb(112, 0, 0)');
      ledFill.addColorStop(1.00, 'rgb(251, 0, 0)');
     ctx.fillStyle = ledFill;
     ctx.fill();
     // glow
      ctx.shadowOffsetX = 0.0 * imageWidth;
      ctx.shadowOffsetY = 0.0 * imageHeight;
      ctx.shadowColor   = 'rgba(255, 0, 0, 1)';
      ctx.shadowBlur    = 0.25 * imageWidth;
      ctx.fill();
    } else { // off
     ledFill.addColorStop(0.00, 'rgb(52, 15, 20)');
     ledFill.addColorStop(0.49, 'rgb(37, 0, 0)');
     ledFill.addColorStop(1.00, 'rgb(52, 15, 20)');
     ctx.fillStyle = ledFill;
     ctx.fill();    
   }
    ctx.restore();
  }
...

Damit wir die Buffer später verwenden können, müssen wir noch eine Methode zur Initialisierung hinzufügen, in der alle Buffer „befüllt“ werden. Dazu müssen die Buffer zunächst gelöscht werden, was mit der clearRect()-Methode erfolgt.

Listing 6
...
  var init = function() {
    initialized = true;
    bkgCtx.clearRect(0, 0, this.width, this.height);
   offCtx.clearRect(0, 0, this.width, this.height);
   onCtx.clearRect(0, 0, this.width, this.height);
   fgdCtx.clearRect(0, 0, this.width, this.height);
    
   drawBkg(bkgCtx);
    drawLed(offCtx, false);
    drawLed(onCtx, true);
   drawFgd(fgdCtx);
  }
...

Nachdem wir nun Methoden haben, um die Canvas-Context-Elemente anzulegen, zu initialisieren und mit grafischen Elementen zu füllen, benötigen wir noch eine Methode zur Visualisierung der Komponente, also eine Methode, in der all die verschiedenen Canvas-Context-Elemente, die wir jetzt mit Grafik gefüllt haben, übereinander gezeichnet werden. Das geschieht in der Methode paintComponent() (Listing 7). Es handelt sich hierbei um eine öffentliche Methode, da man sie von außen aufrufen können muss. Nachdem die Komponente initialisiert ist, wird der Canvas Context des sich im DOM befindlichen Canvas-Elements gelöscht. Daraufhin werden alle Buffer nacheinander übereinander gezeichnet. Hier findet auch die Unterscheidung nach dem jeweiligen Zustand statt (AN oder AUS). Letztendlich fehlt jetzt nur noch der HTML-Code, der benötigt wird, um die Komponente im Browser anzuzeigen. Abbildung 4 zeigt die fertige HTML5-Komponente der LED im Browser.

Listing 7
...
  this.paintComponent = function() {
    if (!initialized) {
      init();
    }
    mainCtx.save();
    mainCtx.clearRect(0, 0, this.width, this.height);
    mainCtx.drawImage(bkgBuffer, 0, 0);
    if (on) {
     mainCtx.drawImage(onBuffer, 0, 0);
    } else {
     mainCtx.drawImage(offBuffer, 0, 0);
    }
   mainCtx.drawImage(fgdBuffer, 0, 0);
    mainCtx.restore();
  }
...
Listing 8

  
    Led Komponente  
Abb. 4: HTML5-Led-Komponente im Browser

Auf der letzten Seite folgen:

  • Einbindung in Sencha Touch
  • Fazit
  • Unsere Redaktion empfiehlt:

    Relevante Beiträge

    Meinungen zu diesem Beitrag

    X
    - Gib Deinen Standort ein -
    - or -