Erster Versuch mit HTML5 Canvas: ein kleines Feuerwerk

Ich habe bei den Musik-Animationen die ersten Erfahrungen mit dem Canvas-Element gesammelt, und ich muss sagen, ich bin sehr angetan. Das eröffnet wirklich ungeahnte Möglichkeiten, und ich finde es wesentlich eingänginger und intuitiver zu bedienen als CSS-Animationen, wo man sich ja ewig mit der Cross-Browser-Kompatibilität herumschlagen muss. Canvas wird von den meisten gängigen modernen Browsern unterstützt, genauere Info hier auf der Seite der Canvas Community.

Statt langer Vorreden legen wir gleich los: passend zum Jahreswechsel habe ich eine kleine Feuerwerksanimation geschrieben. Sowas gibt es schon, auch als downloadbare Freeware, aber mir kam der Sourcecode der Beispiele immer unnötig aufgeblasen vor, ich dachte mir, das muss doch auch einfacher gehen…. und es geht einfacher, deutlich.

Die Vorgabe

Meine Vorgabe ist folgende: auf einem dunklen Sternenhimmel sollen farbige Feuerwerksraketen sternförmig explodieren, an zufälligen Stellen auf dem Canvas. Die Animation soll per Klick startbar sein und dann in einer Endlosschleife laufen. Aussehen soll das ganze etwa so:

screenshot_feuerwerk

screenshot_feuerwerk

Dafür brauche ich zwei Zeitschleifen. Die äussere Schleife wird endlos wiederholt (setInterval…) und erzeugt immer einen neuen zufälligen Mittelpunkt der nächsten Explosion. An einem gegebenen Mittelpunkt startet dann die zweite Zeitschleife, hier soll die Sternenexplosion in mehreren Iterationsschritten von der Mitte aus zeitverzögert ablaufem, dafür nehme ich einen setTimeout. Für die Skriptsteuerung brauche ich Jquery, das binde ich mal lokal ein.

Los gehts: wir basteln uns erst einmal die Leinwand, den canvas, das ist straightes HTML:

<canvas id="leinwand" width="400" height="400" 
style="display: block; background: url('sternenhimmel.jpg')" ></canvas>

Ich lege hier gleich mal einen netten Sternenhimmel in den Hintergrund.

Dann brauchen wir noch einen Startknopf:

<input type="button" value = "Abspielen" onclick = "kontrolle()" >

Die Funktion kontrolle() tut nichts weiter als die Funktion zeichnen() aufzurufen, hier könnte man noch Parameter übergeben, z.B. eine Wiederholungsrate oder die Dauer einer Schleife, aber ich hab das mal gelassen und setze die entsprechenden Werte direkt im Code. Als allererste Massnahme definiert die Funtkion zeichnen() einen Canvas-2d-Context, mit dem arbeiten wir dann weiter:

function zeichnen(param) {
    
    // get the context from the canvas to draw on
    var lwd = $("#leinwand").get()[0].getContext("2d");
...

lwd ist jetzt unser Zeichenbrett.

Wie man einen Stern zeichnet

Wir zäumen das Pferd jetzt mal von hinten auf. Wie zeichnet man überhaupt einen Stern auf den Canvas? Man braucht einen festen Mittelpunkt in Form einer x- und einer y-Koordinate, und einen festen Radius. Dann braucht man noch eine mathematische Formel, die einem ausrechnet bis zu welchem Endpunkt ein Strahl zu einem bestimmten Winkel gesetzt werden soll. Die Formel packt man in eine for…Schleife, und zählt den Winkel einfach hoch, bis man einmal rum ist.

//Begin funktion strahlen zeichnen        
function strahlen(fx,fy,radius){
                    
           var rad = radius;
                    //Beginn strahlen graduell
                    for (grad = -1; grad <=1; grad = grad + 0.2){
                        
                        theta = grad*Math.PI;
                        lwd.moveTo(fx, fy);
                        lwd.lineTo(fx + rad * Math.cos(theta), fy + rad * Math.sin(theta));
                                            
                        //jeder Strahl eine andere Farbe
                        lwd.strokeStyle = "hsl("+Math.floor((Math.random()*359))+", 100%, 50%)";
                        lwd.stroke();
                        
                        //Nach der letzten Schleife Leinwand leer machen
                        if (radius == 100){lwd.clearRect(0, 0, 400, 400);}
                        
                        //**Pfad neu zeichnen (ohne "Geisterbilder")
                        lwd.beginPath();
                                                                
                    }//end strahlen graduell
        
} // Ende von fucntion strahlen    zeichnen

Man setzt den Cursor mit .moveTo auf den Mittelpunkt, und zeichnet mit dem .lineTo den Strahl bis zur berechneten Koordinate. .stroke() führt die Zeichnung aus. Dann wird der Winkel hochgesetzt, man zeichnet den nächsten Strahl, bis man einmal um den Kreis von -1 bis +1 herum ist. Ich hab hier auch noch die Farbdefinition mit hineingepackt und bestimme für jeden Strahl eine zufällige hsl-Farbe, das macht sich ganz hübsch.

Nach dem letzten Aufruf (gegebener maximaler Radius von 100 ist erreicht) macht man die Leinwand leer, damit der Stern auch wieder verschwindet. .beginPath() macht dann noch klar Schiff auf dem internen Pfad-Objekt, wenn das fehlt hat man dann später „Geisterbilder“ von den vorher bereits gezeichneten Sternen.

Die erste Animation: Sterne mit steigenden Radien

Damit der Stern nicht in einem Flash auftaucht, sondern sich wie bei einer Explosion von innen heraus vergrößert, rufe ich die soeben definierte Funktion strahlen() mehrfach auf, immer mit dem selben Mittelpunkt, aber mit stetig wachsenden Radien. Dafür benutze ich einen setTimeout:

//*********begin function sternchen mit timeout    
function sternchen(koord1,koord2){

var i = 10;      //  zähler für den Radius auf 10 (px)
                
//Zufälliger Mittelpunkt des sterns kommt als Parameter rein koord zwischen 1 und 400
var x1 = koord1;
var y1 = koord2;
                
        function myLoop () {           //  Loop function definieren
           setTimeout(function () {    //  setTimeout Wenn die Loop-Funktion aufgerufen wird
                        
              strahlen(x1,y1,i);        //  Strahlen zeichnen an festem Mittelpunkt und Radius
              i=i+10;;                  //  Radius um 10 (px) hochsetzen
              if (i <=100) {            //  Radius <= 100?           
                 myLoop();              //  Loop function erneut aufrufen, Strahlen mit neuem Radius zeichnen
              }                           
              
           }, 50)                        //  ..  setTimeout() auf 50 ms
           
        } //*******Ende von function myloop

myLoop();        //Startet den Loop

} //******ende von function sternchen

Mein i geht in 10er-Schritten hoch, das benutze ich als Radius für den explodierenden Stern in px. Die x- und y-Koordinate des Mittelpunkts bleiben immer gleich, die hab ich als Parameter reingegeben. Der Timeout läuft auf 50 Millisekunden, das ergibt eine hübsch gleichmässige Bewegung. Die Schleife bricht erst ab, wenn der maximale Radius von 100 px erreicht ist.

Die zweite Animation: alle halbe Sekunde ein neuer zufälliger Mittelpunkt

Meine Funktion sternchen() wird jetzt alle 500 my aufgerufen, und bei jedem Aufruf wird eine zufällige x- und y-Koordinate für den Mittelpunkt der Explosion generiert. Das ganze wird mit einem setInterval in einer Endlosschleife gesteuert:

//Beginn Feuerwerk Endlosschleife
        //passiert alle x millisekunden
        var drawLinesInterval = setInterval(function() {
                
                //Leinwand putzen
                lwd.clearRect(0, 0, 400, 400);
                
                //Zufälligen Mittelpunkt des Sterns bestimmen Koordinaten zwischen 1 und 400
                var koord1 = Math.floor(Math.random() * 20 + 1)*20;
                var koord2 = Math.floor(Math.random() * 20 + 1)*20;
                
                //Funktion zum Sternzeichnen aufrufen            
                sternchen(koord1,koord2);
                
        }, 500); // end drawLinesInterval

Das wars auch schon! Morgen packe ich noch eine Demo mit dazu. da könnt ihr euch dann den Source direkt anschauen. Canvas macht Laune!