O que é Canvas

É uma especificação que define o contexto 2D no browser, implementando um elemento HTML que provê métodos e propriedades e métodos para desenhar e manipular gráficos. - W3C 2D Context

Canvas é um elemento da HTML5 destinado a delimitar uma área para renderização dinâmica de gráficos. Todo o trabalho de criação e animação é realizado atráves de linguagens de programação dinâmica (usualmente Javascript).O elemento foi originalmente introduzido pela Apple Inc. para o navegador Safari. As aplicações da Mozilla ganharam suporte ao canvas começando pelo Gecko 1.8 (Firefox 1.5). O Internet Explorer possui suporte ao elemento a partir da versão 10. Para adicionar suporte as versões anteriores desse navegador, basta incluir um script feito pela Google chamado Explorer Canvas. Google Chrome e Opera também suportam o canvas. - Wikipedia

Funções Básicas

O Canvas é um elemento HTML uma tag Html como também é conhecido, é nesse elemento em que os contextos 2D e 3D serão instanciados. É importante salientar que o elemento canvas não refere-se ao contexto 2D mas a área do documento será dedicada a apresentar conteúdo seja ele 2D ou 3D.

O contexto pode ser 2d ou webgl (3D), cada elemento canvas pode ter somente um contexto. Para criar um contexto, chamamos o método do getContext().

Primeiro passo

    <!DOCTYPE html>
    <html>
    <body>
        <canvas id="canvas" width="578" height="200"></canvas>
        <script>
            var canvas = document.getElementById('canvas');
            var context = canvas.getContext('2d');
        </script>
    </body>
    </html>
        

Segundo passo

Iremos começar desenhando uma linha, nós iremos usar os seguintes métodos para essa tarefa: beginPath() moveTo() lineTo() stroke().

O métodobeginTo() serve para declarar que vamos desenhar um novo traço. O moveTo() tem a função de mudar a posição do cursor do contexto, por fim stroke() usamos esse método para torna o que desenhamos visível, como se disséssemos ao browser desenhe no elemento canvas. Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');

              context.beginPath();
              context.moveTo(100, 150);
              context.lineTo(450, 50);
              context.stroke();
            </script>
          </body>
        </html>
        

Resultado

Terceiro passo

Iremos agora criar linhas com estilos diferentes. Para isso vamos alterar a propriedade lineCap com um dos seguintes parâmetros: butt, round e square. Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');

              // butt line cap (top line)
              context.beginPath();
              context.moveTo(200, canvas.height / 2 - 50);
              context.lineTo(canvas.width - 200, canvas.height / 2 - 50);
              context.lineWidth = 20;
              context.strokeStyle = '#FF9900';
              context.lineCap = 'butt';
              context.stroke();

              // round line cap (middle line)
              context.beginPath();
              context.moveTo(200, canvas.height / 2);
              context.lineTo(canvas.width - 200, canvas.height / 2);
              context.lineWidth = 20;
              context.strokeStyle = '#52FF00';
              context.lineCap = 'round';
              context.stroke();

              // square line cap (bottom line)
              context.beginPath();
              context.moveTo(200, canvas.height / 2 + 50);
              context.lineTo(canvas.width - 200, canvas.height / 2 + 50);
              context.lineWidth = 20;
              context.strokeStyle = '#FF0047';
              context.lineCap = 'square';
              context.stroke();
            </script>
          </body>
        </html>
        

Resultado

Quarto passo

Agora vamos criar desenhar mais complexo utilizando curva quadrática e curva de bezier.

Será necessário utilizar os seguintes métodos: lineTo(), arcTo(), quadraticCurveTo() e bezierCurveTo(), para desenhar cada parte. Ver código.

<!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');

              context.beginPath();
              context.moveTo(100, 20);

              // line 1
              context.lineTo(200, 160);

              // quadratic curve
              context.quadraticCurveTo(230, 200, 250, 120);

              // bezier curve
              context.bezierCurveTo(290, -40, 300, 200, 400, 150);

              // line 2
              context.lineTo(500, 90);

              context.lineWidth = 2;
              context.strokeStyle = 'orange';
              context.stroke();
            </script>
          </body>
        </html>
        

Resultado

Quinto Passo

Nesse passo vamos criar um retângulo, para isso iremos utilizar o método rect(), definir a posição do quadrado no modelo cartesiano alterando as propriedades x e y e por fim definindo a largura (width) e altura (height). Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');

              context.beginPath();
              context.rect(10, 10, 50, 40);
              context.fillStyle = 'orange';
              context.fill();
              context.lineWidth = 2;
              context.strokeStyle = 'grey';
              context.stroke();
            </script>
          </body>
        </html>
        

Resultado

Sexto passo

Vamos desenhar um círculo, para isso iremos usar a função arc(). iniciando no ângulo de 0º até 2*PI. Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');
              var centerX = canvas.width / 2;
              var centerY = canvas.height / 2;
              var radius = 70;

              context.beginPath();
              context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
              context.fillStyle = 'magenta';
              context.fill();
              context.lineWidth = 2;
              context.strokeStyle = '#003300';
              context.stroke();
            </script>
          </body>
        </html>
        

Resultado

Funções Avançadas

Com base do que vimos, vamos manipular o elemento canvas com exemplos mais difíceis.

Primeiro Passo

Escrevendo com canvas, para realizar essa tarefa iremos alterar a propriedade strokeStyle e chamar o métodostrokeText() Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');
              var x = 80;
              var y = 110;

              context.font = '60pt Verdana';
              context.lineWidth = 3;
              // stroke color
              context.strokeStyle = 'green';
              context.strokeText('Olá Mundo!', x, y);
            </script>
          </body>
        </html>
        

Resultado

Segundo Passo

Rotacionando um retângulo, iremos utilizar o método rotate() Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');
              var rectWidth = 150;
              var rectHeight = 75;

              // translate context to center of canvas
              context.translate(canvas.width / 2, canvas.height / 2);

              // rotate 45 degrees clockwise
              context.rotate(Math.PI * 1.5);

              context.fillStyle = 'orange';
              context.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);
            </script>
          </body>
        </html>
        

Resultado

Terceiro passo

Vamos animar um retângulo, oscilando ele de um lado para o outro, para definir cada posição iremos utilizar a seguinte equação: x(t) = amplitude * sin(t * 2PI / period) + x0 . Ver código.

    <!DOCTYPE HTML>
        <html>
          <head>
            <style>
              body {
                margin: 0px;
                padding: 0px;
              }
            </style>
          </head>
          <body>
            <canvas id="canvas" width="578" height="200"></canvas>
            <script>
              window.requestAnimFrame = (function(callback) {
                return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
                function(callback) {
                  window.setTimeout(callback, 1000 / 60);
                };
              })();

              function drawRectangle(myRectangle, context) {
                context.beginPath();
                context.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height);
                context.fillStyle = '#8ED6FF';
                context.fill();
                context.lineWidth = myRectangle.borderWidth;
                context.strokeStyle = 'black';
                context.stroke();
              }
              function animate(myRectangle, canvas, context, startTime) {
                // update
                var time = (new Date()).getTime() - startTime;
                var amplitude = 150;

                // in ms
                var period = 2000;
                var centerX = canvas.width / 2 - myRectangle.width / 2;
                var nextX = amplitude * Math.sin(time * 2 * Math.PI / period) + centerX;
                myRectangle.x = nextX;

                // clear
                context.clearRect(0, 0, canvas.width, canvas.height);

                // draw
                drawRectangle(myRectangle, context);

                // request new frame
                requestAnimFrame(function() {
                  animate(myRectangle, canvas, context, startTime);
                });
              }
              var canvas = document.getElementById('canvas');
              var context = canvas.getContext('2d');

              var myRectangle = {
                x: 250,
                y: 70,
                width: 100,
                height: 50,
                borderWidth: 5
              };

              drawRectangle(myRectangle, context);

              // wait one second before starting animation
              setTimeout(function() {
                var startTime = (new Date()).getTime();
                animate(myRectangle, canvas, context, startTime);
              }, 1000);
            </script>
          </body>
        </html>
        

Resultado

Quarto passo

Criando uma foto a partir da câmera usando o elemento canvas, video o método getUserMedia() presente no DOM e objeto chamado Blob para armazenar dados de stream, que no nosso caso será a foto gerada, os dados presente no objeto do tipo Blob permitem que nós enviemos para um servidor que irá armazena-la. Ver código.

<!DOCTYPE html>
<html>
<head>
    <title></title>

    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css">

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script type="text/javascript" src="//netdna.bootstrapcdn.com/bootstrap/2.3.2/js/bootstrap.min.js"></script>

</head>
<body>
     <style type="text/css">
        .hide { display:none; }
    </style>

<div class="container">
    <div class="hide cam-disable">
        <p><i class="icon-camera-retro icon-4x"></i> Enable access to camera</p>    
    </div>
    <div id="cam-preview-container">
        <h2>Smile for us!</h2>
        <div class="row-fluid">
            <div id="cam-preview" class="span12">
                <video id="video" width="640" height="480" autoplay></video>
                <canvas id="canvas" class="hide" width="640" height="480"></canvas>
            </div>
            <div class="span12">
                <a id="snap" class="btn btn-large btn-success "><i class="icon-camera-retro"></i> Take Photo</a>
                <span class="pull-left offset1">
                    <span>1 s</span> <input id="delay" type="range" min='1' max="15" value="3"> <span>15 s</span>
                </span>
            </div>
        </div>
    </div>

    <div id="photo-preview-container" class="hide">
        <h1>Smile for us!</h1>
        <div class="row-fluid">
            <div class="span12">
                <img id="latest-photo" src="" width="640" height="480">
            </div>
        
            <div class="span12">
                <span class="pull-left offset2"><a id="other-photo-btn" class="btn btn-large btn-primary"><i class="icon-camera-retro"></i> Take an Other Photo</a></span>
            </div>
        </div>
    </div>

    <a id='toggle-cam-status-btn' class="cam-stop-btn" onclick="App.controller.dashboard.toogleCam(this);"><i class="icon-eye-close"></i> <span>Close Camera</span></a>

    </div>

    <script type="text/javascript">
        if(!App) var App = new function(){ return(this); }
        if(!App.controller) App.controller = new function(){ return(this); }

        App.controller.dashboard = new (function(){
    
        var self = this;
        this.localMediaStream = null;

        this.toogleCam = function(e){
            if($(e).attr('class') === 'cam-stop-btn'){
                self.stop(); $(e).text('').attr('class', 'cam-start-btn');
                $('<i class="icon-eye-open"></i> <span>Open Camera</span>').appendTo($(e));
                return;
            }
            if($(e).attr('class') === 'cam-start-btn'){
                self.start(); $(e).text('').attr('class', 'cam-stop-btn');
                $('<i class="icon-eye-close"></i> <span>Close Camera</span>').appendTo($(e));
                return;
            }
        }

        this.stop = function(){
            if(self.video) self.video.pause();
            
            if(self.localMediaStream){
                self.localMediaStream.stop();
                self.localMediaStream = null;
            }
        }

        this.start = function(response){
            
            // Grab elements, create settings, etc.
            var canvas = self.canvas = document.getElementById("canvas"),
            context = self.context = canvas.getContext("2d"),
            video =  self.video = document.getElementById("video"),
            videoObj = { "video": true };
            
            function errBack(error) {
                console.log("Video capture error: ", error.code); 

                $("#cam-preview-container").hide();
                $(".cam-disable").show();
            };

            if(self.localMediaStream === null){
                // Put video listeners into place
                if(navigator.getUserMedia) { // Standard
                    navigator.getUserMedia(videoObj, function(stream) {
                        video.src = self.localMediaStream = stream;
                        video.play();
                    }, errBack);
                } else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
                    navigator.webkitGetUserMedia(videoObj, function(stream){
                        self.localMediaStream = stream;
                        video.src = window.webkitURL.createObjectURL(stream);
                        video.play();
                    }, errBack);
                } else if(navigator.mozGetUserMedia){ //Moz-prefixed
                    navigator.mozGetUserMedia(videoObj, function(stream){
                        video.mozSrcObject = self.localMediaStream = stream;
                        video.play();
                    }, errBack);
                }
            } else {
                if(navigator.getUserMedia) video.src = self.localMediaStream;
                if(navigator.mozGetUserMedia) video.src = self.localMediaStream;
                if(navigator.webkitGetUserMedia) video.src = window.webkitURL.createObjectURL(self.localMediaStream);
                video.play();
            }


            function snap() {
                context.drawImage(video, 0, 0, 640, 480);
                document.getElementById('latest-photo').src = canvas.toDataURL('image/jpeg', 0.5);
                
                self.video.pause();
                $("#cam-preview-container").hide();
                $("#photo-preview-container").show();
            }

            // Trigger photo take
            $("#snap").click(function(){
                var delay = parseInt($("#delay").val());
                setTimeout(snap, delay*1000);
            });

            $("#other-photo-btn").click(function(){
                self.video.play();
                $("#cam-preview-container").show();
                $("#photo-preview-container").hide();
            });
        }


        /*
        *    Implementation of toBlob(dataURI, mimetype)
        *
        *    - Convert DataURI in Blob object.
        *    - Return Blob
        */
        function toBlob(dataURI, mimetype) {
          if (!dataURI) {
            return new Uint8Array(0);
          }

          var BASE64_MARKER = ';base64,';
          var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
          var base64 = dataURI.substring(base64Index);
          var raw = window.atob(base64);
          var uInt8Array = new Uint8Array(raw.length);

          for (var i = 0; i < uInt8Array.length; ++i) {
            uInt8Array[i] = raw.charCodeAt(i);
          }

          return new Blob([uInt8Array], {type: mimetype});
        }

        //Append function in Window tree
        window.toBlob = toBlob;

        return(this);
        });
    </script>

    <!-- See My Party -->
    <script type="text/javascript">
    //Start Camera
    App.controller.dashboard.start();

    //For share photo
    $("#share-photo-btn").click(App.controller.dashboard.publishPhoto);

    //For Preferennces
    $("#config-btn").click(function(){
        $('#preferences').modal({show: true});
    });
    </script>
</body>
</html>

Resultado

Quinto passo

Utilizando o exemplo acima, iremos deixar a foto gerada em preto e branco. Ver código.

<!DOCTYPE html>
<html>
<head>
    <title></title>

    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css">

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script type="text/javascript" src="//netdna.bootstrapcdn.com/bootstrap/2.3.2/js/bootstrap.min.js"></script>

</head>
<body>
     <style type="text/css">
        .hide { display:none; }
    </style>

<div class="container">
    <div class="hide cam-disable">
        <p><i class="icon-camera-retro icon-4x"></i> Habilitar camera</p>    
    </div>
    <div id="cam-preview-container">
        <h2>Sorria!</h2>
        <div class="row-fluid">
            <div id="cam-preview" class="span12">
                <video id="video" width="640" height="480" autoplay></video>
                <canvas id="canvas" class="hide" width="640" height="480"></canvas>
            </div>
            <div class="span12">
                <a id="snap" class="btn btn-large btn-success "><i class="icon-camera-retro"></i> Tirar foto</a>
                <span class="pull-left offset1">
                    <span>1 s</span> <input id="delay" type="range" min='1' max="15" value="3"> <span>15 s</span>
                </span>
            </div>
        </div>
    </div>

    <div id="photo-preview-container" class="hide">
        <h1>Sorria!</h1>
        <div class="row-fluid">
            <div class="span12">
                <img id="latest-photo" src="" width="640" height="480">
            </div>
        
            <div class="span12">
                <span class="pull-left offset2"><a id="other-photo-btn" class="btn btn-large btn-primary"><i class="icon-camera-retro"></i> Tirar outra foto</a></span>
            </div>
        </div>
    </div>

    <a id='toggle-cam-status-btn' class="cam-stop-btn" onclick="App.controller.dashboard.toogleCam(this);"><i class="icon-eye-close"></i> <span>Desligar camera</span></a>

    </div>

    <script type="text/javascript">
        if(!App) var App = new function(){ return(this); }
        if(!App.controller) App.controller = new function(){ return(this); }

        App.controller.dashboard = new (function(){
    
        var self = this;
        this.localMediaStream = null;

        this.toogleCam = function(e){
            if($(e).attr('class') === 'cam-stop-btn'){
                self.stop(); $(e).text('').attr('class', 'cam-start-btn');
                $('<i class="icon-eye-open"></i> <span>Open Camera</span>').appendTo($(e));
                return;
            }
            if($(e).attr('class') === 'cam-start-btn'){
                self.start(); $(e).text('').attr('class', 'cam-stop-btn');
                $('<i class="icon-eye-close"></i> <span>Close Camera</span>').appendTo($(e));
                return;
            }
        }

        this.stop = function(){
            if(self.video) self.video.pause();
            
            if(self.localMediaStream){
                self.localMediaStream.stop();
                self.localMediaStream = null;
            }
        }

        this.start = function(response){
            
            // Grab elements, create settings, etc.
            var canvas = self.canvas = document.getElementById("canvas"),
            context = self.context = canvas.getContext("2d"),
            video =  self.video = document.getElementById("video"),
            videoObj = { "video": true };
            
            function errBack(error) {
                console.log("Video capture error: ", error.code); 

                $("#cam-preview-container").hide();
                $(".cam-disable").show();
            };

            if(self.localMediaStream === null){
                // Put video listeners into place
                if(navigator.getUserMedia) { // Standard
                    navigator.getUserMedia(videoObj, function(stream) {
                        video.src = self.localMediaStream = stream;
                        video.play();
                    }, errBack);
                } else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
                    navigator.webkitGetUserMedia(videoObj, function(stream){
                        self.localMediaStream = stream;
                        video.src = window.webkitURL.createObjectURL(stream);
                        video.play();
                    }, errBack);
                } else if(navigator.mozGetUserMedia){ //Moz-prefixed
                    navigator.mozGetUserMedia(videoObj, function(stream){
                        video.mozSrcObject = self.localMediaStream = stream;
                        video.play();
                    }, errBack);
                }
            } else {
                if(navigator.getUserMedia) video.src = self.localMediaStream;
                if(navigator.mozGetUserMedia) video.src = self.localMediaStream;
                if(navigator.webkitGetUserMedia) video.src = window.webkitURL.createObjectURL(self.localMediaStream);
                video.play();
            }


            function snap() {
                context.drawImage(video, 0, 0, 640, 480);
                //document.getElementById('latest-photo').src = canvas.toDataURL('image/jpeg', 0.5);

                //Transform image color to grayscale
                var imageObj = new Image();
                imageObj.src = canvas.toDataURL('image/jpeg', 0.5);
                imageObj.onload = function() {
                    (function(imageObj){
                        var canvas = document.createElement('canvas');
                        canvas.width = 640;
                        canvas.height = 480;
                        var context = canvas.getContext('2d');
                        var x,y; x=y=0;

                        context.drawImage(imageObj, x, y);

                        var imageData = context.getImageData(x, y, imageObj.width, imageObj.height);
                        var data = imageData.data;

                        for(var i = 0; i < data.length; i += 4) {
                          var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
                          // red
                          data[i] = brightness;
                          // green
                          data[i + 1] = brightness;
                          // blue
                          data[i + 2] = brightness;
                        }

                        // overwrite original image
                        context.putImageData(imageData, x, y);

                        document.getElementById('latest-photo').src = canvas.toDataURL('image/jpeg', 0.5);
                    })(this);
                };
                


                self.video.pause();
                $("#cam-preview-container").hide();
                $("#photo-preview-container").show();
            }

            // Trigger photo take
            $("#snap").click(function(){
                var delay = parseInt($("#delay").val());
                setTimeout(snap, delay*1000);
            });

            $("#other-photo-btn").click(function(){
                self.video.play();
                $("#cam-preview-container").show();
                $("#photo-preview-container").hide();
            });
        }


        /*
        *    Implementation of toBlob(dataURI, mimetype)
        *
        *    - Convert DataURI in Blob object.
        *    - Return Blob
        */
        function toBlob(dataURI, mimetype) {
          if (!dataURI) {
            return new Uint8Array(0);
          }

          var BASE64_MARKER = ';base64,';
          var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
          var base64 = dataURI.substring(base64Index);
          var raw = window.atob(base64);
          var uInt8Array = new Uint8Array(raw.length);

          for (var i = 0; i < uInt8Array.length; ++i) {
            uInt8Array[i] = raw.charCodeAt(i);
          }

          return new Blob([uInt8Array], {type: mimetype});
        }

        //Append function in Window tree
        window.toBlob = toBlob;

        return(this);
        });
    </script>

    <!-- See My Party -->
    <script type="text/javascript">
    //Start Camera
    App.controller.dashboard.start();

    //For share photo
    $("#share-photo-btn").click(App.controller.dashboard.publishPhoto);

    //For Preferennces
    $("#config-btn").click(function(){
        $('#preferences').modal({show: true});
    });
    </script>
</body>
</html>

Resultado

Bibliotecas

As bibliotecas para desenvolvimento de aplicações em Canvas tornam a manipulação de objetos primitivos(quadrado, círculo, triângulo, retângulo) mais intuitiva e prática além de permitir o uso de recursos físicos(simulação de gravidade e monitoramento de colisão). É importante salientar que para aplicações com um nível de complexidade pequeno não é recomendado o uso de bibliotecas pois será um gasto de recursos computacionais desnecessário. Existem dois tipos de bibliotecas para desenvolvimento de aplicações e jogos, especificamente para jogos podemos dividi-las em "game makers libraries" e "game engine libraries".

Game makers Libraries

Estas bibliotecas permitem o desenvolvimento rápido de aplicações, disponibilizando elementos gráficos prontos para o desenvolvedor, porém deixa o desenvolvimento limitado aos recursos que a biblioteca oferece. Um ótimo "game maker" é o Construct 2.

Game engine libraries

São bibliotecas que oferecem em sua maioria suporte para desenvolvimento de aplicações em 2D e 3D, com suporte para carregamento de conteúdo, sprites de imagem e áudio, recursos físicos(colisão) e objetos primitivos prontos(círculo, quadrado e etc).

Bibliotecas populares

via HTML5 Engine

Bibliotecas recomendadas (Game engines)

Iniciando em canvas usando bibliotecas

Collie
  • Criando um quadrado
     <!DOCTYPE html>
    <html>
    <head>
        <title>Rectangle Demo | Collie library</title>
        <script type="text/javascript" src="/experiences/libs/collie/collie.min.js"></script>
    </head>
    <body>    
        <div id="container" class="container"></div>
        <script type="text/javascript">
            var layer = new collie.Layer({
                width : 300,
                height : 300
            });
             
            var box = new collie.DisplayObject({
                width : 50,
                height : 50,
                x : 10,
                y : 10,
                backgroundColor : 'red'
            }).addTo(layer);
             
            collie.Renderer.addLayer(layer);
            collie.Renderer.load(document.getElementById("container"));
            collie.Renderer.start();
        </script>
    </body>
    </html>
    
  • Repetindo plano de fundo
     <!DOCTYPE html>
    <html>
    <head>
        <title>Background Repeat Demo | Collie library</title>
        <script type="text/javascript" src="/experiences/libs/collie/collie.min.js"></script>
    </head>
    <body>
        <div id="container" class="container"></div>
        <button id="start-btn">Iniciar</button>
        <script type="text/javascript">
        var layer = new collie.Layer({
            width : 300,
            height : 300
        });
        collie.ImageManager.add({
            "ground" : "/experiences/canvas_tutorial/img/background-repeat/ground.png",
            "sky" : "/experiences/canvas_tutorial/img/background-repeat/sky.png"
        });
        var oBackground = new collie.DisplayObject({
            x : 0,
            y : 0,
            width : 300,
            height : 300,
            backgroundRepeat : "repeat", // Repeat X-Axis
            backgroundImage : "sky"
        }).addTo(layer);
        var oGround = new collie.DisplayObject({
            x : 0,
            width : 320 * 2, // Using Double width for continuously horizontal move.
            height : 88,
            velocityX : -80,
            backgroundRepeat : "repeat-x", // Repeat X-Axis
            rangeX : [-320, 0], // This object can move from first position to second position.
            positionRepeat : true, // This object move the other side when It's on one end of the edge.
            backgroundImage : "ground"
        }).bottom(0).addTo(layer);
    
        collie.Renderer.addLayer(layer);
        collie.Renderer.load(document.getElementById("container"));
        //collie.Renderer.start();
        </script>
    
        <!-- Start/pause animation -->
        <script type="text/javascript">
            document.getElementById("start-btn").addEventListener("click", function(e){
                var elem = e.target;
    
                if(elem.getAttribute("started") === null || elem.getAttribute("started") === "false"){
                    collie.Renderer.start();
                    elem.textContent = "Parar";
                    elem.setAttribute("started", "true");    
                } else {
                    collie.Renderer.stop();
                    elem.textContent = "Iniciar";
                    elem.setAttribute("started", "false");
                }
            }, false);
        </script>
    </body>
    </html>
    
Crafty
  • Pong
     <!DOCTYPE html>
    <html>
    <head>
        <title>Pong Demo | Crafty</title>
    <script type="text/javascript" src="/experiences/libs/crafty/crafty-min.js"></script>
        <style>
            body, html { margin:0; padding: 0; overflow:hidden; font-family:Arial; font-size:20px }
            #cr-stage { color:white }
        </style>
    </head>
    <body>
    <script type="text/javascript">
        Crafty.init(600, 300);
        Crafty.background('rgb(127,127,127)');
    
        Crafty.scene("game", function() {
            //Paddles
            Crafty.e("Paddle, 2D, DOM, Color, Multiway")
                .color('rgb(255,0,0)')
                .attr({ x: 20, y: 100, w: 10, h: 100 })
                .multiway(4, { W: -90, S: 90 });
            Crafty.e("Paddle, 2D, DOM, Color, Multiway")
                .color('rgb(0,255,0)')
                .attr({ x: 580, y: 100, w: 10, h: 100 })
                .multiway(4, { UP_ARROW: -90, DOWN_ARROW: 90 });
    
            //Ball
            Crafty.e("2D, DOM, Color, Collision")
                .color('rgb(0,0,255)')
                .attr({ x: 300, y: 150, w: 10, h: 10, 
                        dX: Crafty.math.randomInt(2, 5), 
                        dY: Crafty.math.randomInt(2, 5) })
                .bind('EnterFrame', function () {
                    //hit floor or roof
                    if (this.y <= 0 || this.y >= 290)
                        this.dY *= -1;
    
                    if (this.x > 600) {
                        this.x = 300;
                        Crafty("LeftPoints").each(function () { 
                            this.text(++this.points + " Points") });
                    }
                    if (this.x < 10) {
                        this.x = 300;
                        Crafty("RightPoints").each(function () { 
                            this.text(++this.points + " Points") });
                    }
    
                    this.x += this.dX;
                    this.y += this.dY;
                })
                .onHit('Paddle', function () {
                this.dX *= -1;
            })
    
            //Score boards
            Crafty.e("LeftPoints, DOM, 2D, Text")
                .attr({ x: 20, y: 20, w: 100, h: 20, points: 0 })
                .text("0 Points");
            Crafty.e("RightPoints, DOM, 2D, Text")
                .attr({ x: 515, y: 20, w: 100, h: 20, points: 0 })
                .text("0 Points");
        })
        Crafty.e("2D, DOM, Text").attr({x:250, y:130, w: 300 }).text("Click to play...");
        Crafty.e("2D, DOM, Mouse").attr({x:0, y:0, h:300, w:600 }).bind("Click", function() { Crafty.scene("game");})
    </script>
    
    </body>
    </html>
    
  • Village Game

    Leia o tutorial passo a passo de como criar o jogo.

    Baixar código-fonte ou Jogue online

oCanvas
  • Planetas
    Veja o exemplo
        var canvas = oCanvas.create({ canvas: "#canvas", background: "#222" });
    
        // Center planet
        var center = canvas.display.ellipse({
            x: canvas.width / 2, y: canvas.height / 2,
            radius: canvas.width / 20,
            fill: "#fff"
        }).add();
    
        // Prototype objects that will be used to instantiate the others
        var satelliteProto = canvas.display.ellipse({ fill: "#eee" });
        var pathProto = canvas.display.ellipse({ stroke: "1px #999" });
    
        // Set up data
        var satellites = [], depth = 3;
        var satelliteColors = ["#107B99", "#5F92C0", "#c7509f"];
        var pathColors = ["#666", "#107B99", "#5F92C0"];
    
        // Create seven satellites and paths. Definition is further down.
        for (var i = 0, l = 7; i < l; i++) {
            createSatellite({
                parent: center, depth: 1,
                distance: (i + 1) * canvas.width / 6,
                radius: canvas.width / 100,
                speed: 1
            });
        }
    
        // Set up a tick function that will move all satellites each frame
        canvas.setLoop(function () {
            for (var i = 0, l = satellites.length; i < l; i++) {
                satellites[i].rotation += satellites[i].speed;
            }
        });
    
        // Definition for a satellite and its corresponding path
        function createSatellite (options) {
    
            // Create the path that the satellite will follow
            var path = pathProto.clone({
                radius: options.distance,
                x: options.x || 0, y: options.y || 0,
                strokeColor: pathColors[options.depth - 1]
            });
            options.parent.addChild(path);
    
            // Create a new satellite
            var satellite = satelliteProto.clone({
                origin: {
                    x: 0,
                    y: options.distance * (Math.round(Math.random()) ? 1 : -1)
                },
                speed: Math.random() * (2 * Math.random() - 0.5) + 0.5,
                radius: options.radius,
                x: options.x || 0, y: options.y || 0,
                fill: satelliteColors[options.depth - 1],
                rotation: Math.random() * 360
            });
            options.parent.addChild(satellite);
            satellites.push(satellite);
    
            // Create another satellite that will circle around this satellite
            if (options.depth < depth) {
                createSatellite({
                    parent: satellite, depth: options.depth + 1,
                    distance: options.radius * 7,
                    radius: options.radius / 1.5,
                    x: satellite.origin.x * -1, y: satellite.origin.y * -1,
                    speed: 10
                });
            }
        }
                        

Som

Em aplicações onde utiliza-se o recurso de animação, geralmente faz-se necessário o uso de sons que são reproduzidos de acordo com as ações do usuário por exemplo. Abaixo está uma lista de biblioteca que fazem "sprite" de um arquivo de áudio e bibliotecas que sintetizam o áudio.

Conclusões

A formalização do elemento canvas pela W3C, está refolucionando a Web como conhecemos, sendo um meio onde uma aplicação pode funcionar em diversas plataformas (Sitemas operacionais e arquiteturas), sem a necessidade do desenvolvedor se preocupar com os recursos necessários para executar sua aplicação. Este elemento tornou-se possível criar softwares que só eram possíveis no desktop, atualmente ferramentas CAD online são criadas a partir de canvas, jogos 2D e 3D e animações, recursos disponíveis antes somente através de plugins como Silverlight e Flash e com um diferencial da soluções em nuvem.

Com o canvas o desenvolvedor web tem a possibilidade de criar algoritmos para processamento de um arquivo que não há suporte nativo do browser e usar o contexto 2D ou 3D para exibir um determinado arquivo, os exemplos de aplicações que utilizam isso, são os leitores de PDF nativos dos browsers(Chrome e Firefox) e a Flash VM que tem como objetivo processar arquivos flash sem a necessidade do plugin com praticamente a mesma performance, como nesse jogo de corrida.

Além do canvas muitas aplicações vem utilizando CSS3(Cascading Style Sheets) para realizar animações não havendo a necessidade da utilização da Web API de canvas. Essa abordagem trás um performance um pouco maior devido a inexistência do processamento do código em javascript pela js engine, uma aplicação muito interessante dessa técnica é uma outra ferramenta CAD online chamada Tridiv.