H2 - P2019

Développement web

Cours 10 - 2015-11-16

Canvas

Espace de pixels permettant de dessiner à l'aide d'une multitude de méthodes javascript

Le flash killer ?

Compatibilité

Can I Use : http://caniuse.com/#feat=canvas

Démonstrations

Mise en place



                    

var canvas  = document.querySelector('.canvas'),
    context = canvas.getContext('2d');
                    
  • canvas correspond à l'élément dans le DOM
  • context sera utilisé pour dessiner

Ce code ne sera pas répété dans les exemples qui suivent

Lignes et remplissages

Pour dessiner, la méthode classique consiste à

  • Indiquer qu'on commence un tracé
  • Dessiner la forme
  • Définir le style
  • Faire apparaitre le tracé sur le canvas

Lignes


context.beginPath();     // Commencer un tracé

context.moveTo(50,50);   // Placer le tracé
context.lineTo(200,200); // Tracer une ligne
context.lineTo(50,200);  // Tracer autre une ligne
context.closePath();     // Tracer une dernière ligne qui ferme la forme (pas obligatoire)

context.stroke();        // Faire apparaitre les lignes tracées
                    

Lignes

Remplissages


context.beginPath();     // Commencer un tracé

context.moveTo(50,50);   // Placer le tracé
context.lineTo(200,200); // Tracer une ligne
context.lineTo(50,200);  // Tracer autre une ligne

context.fill();          // Faire apparaitre la forme dessinée
                    

Remplissages

Style

Il existe de nombreuses propriétés pour changer le style du dessin

Cela peut concerner les lignes ou le remplissage

Le style s'appliquera sur tous les dessins suivant le changement de propriété

Modifier le style de la ligne


context.beginPath();

context.moveTo(50,50);
context.lineTo(200,200);
context.lineTo(50,200);

context.lineWidth   = 20;       // Largeur de la ligne
context.lineCap     = 'round';  // Fin de ligne (round | butt | square)
context.lineJoin    = 'bevel';  // Jointure des lignes (bevel | round | mitter)
context.strokeStyle = 'orange'; // Couleur de la ligne

context.stroke();
                    

Modifier le style de la ligne

Modifier le style de remplissage


context.beginPath();

context.moveTo(50,50);
context.lineTo(200,200);
context.lineTo(50,200);

context.fillStyle = 'rgba(255,0,0,0.5)'; //Couleur du remplissage
context.fill();
                    

Modifier le style de remplissage

Ombres


context.beginPath();

context.moveTo(50,50);
context.lineTo(200,200);
context.lineTo(50,200);

context.fillStyle     = 'rgba(255,0,0,1)';
context.shadowColor   = 'blue';   // Couleur de l'ombre
context.shadowBlur    = 50;       // Largeur du flou
context.shadowOffsetX = 5;        // Décalage en X
context.shadowOffsetY = 10;       // Décalage en Y

context.fill();
                    

Ombres

rect() et arc()

Certaines méthodes permettent de dessiner autre chose que des lignes

rect() permet de dessiner un rectangle

arc() permet de dessiner un arc de cercle

La variable globale Math possède plusieurs propriétés et méthodes mathématiques

Celle que nous allons utilisé est Math.PI permettant d'obtenir le nombre π utile pour dessiner des arc de cercle


// Style
contexts.fillStyle   = 'orange';
contexts.strokeStyle = 'orange';

// Remplissage d'un rectangle
contexts.beginPath();
contexts.rect(50,50,200,100);
contexts.fill();

// Remplissage d'un demi cercle
contexts.beginPath();
contexts.arc(400,50,100,0,Math.PI,false);
contexts.fill();

// Countour d'un recangle
contexts.beginPath();
contexts.fillStyle = 'orange';
contexts.rect(50,200,200,100);
contexts.stroke();

// Contour d'un demi cercle
contexts.beginPath();
contexts.arc(400,200,100,0,Math.PI,false);
contexts.stroke();
                    

fillRect() et clearRect()

fillRect() permet de remplir un rectangle sans passer par beginPath() et fill()

clearRect() permet d'effacer un rectangle


context.fillStyle = 'orange';

context.fillRect(50,50,300,160);
context.clearRect(50,50,100,80);

context.fillStyle = '#00EEFF';
context.fillRect(160,60,20,70);

context.beginPath();
context.fillStyle = 'black';
context.arc(280,210,50,0,Math.PI,false);
context.arc(120,210,50,0,Math.PI,false);
context.fill();

                    

Textes


var text = 'Lorem ipsum dolor sit amet';

context.font         = '40px Arial'; // Font
context.textAlign    = 'center';     // Alignement horizontal (left | center | right)
context.textBaseline = 'top';        // Alignement vertical (top | bottom | middle | alphabetic | hanging)

console.log(context.measureText(text).width); // Affiche la largeur du texte dans la console (sans le dessiner)

context.fillText(text,300,100);      // Faire apparaitre le texte
context.strokeText(text,300,160);    // Faire apparaitre le contour du texte
                    

Images

Pour dessiner une image, il est nécessaire de l'avoir chargé

Il faut donc créer, en javascript, un objet Image et écouter son événement load


// Créé la variable image
var image = new Image();

// Écoute l'événement load
image.onload = function()
{
    // Dessine l'image
    context.drawImage(image,0,0,image.width / 6,image.height / 6);
};

// Ajoute le chemin de l'image
image.src = 'image-1.jpg';
                    

Dégradés

Dégradé linéaire


var gradient = context.createLinearGradient(50,50,250,250); // x1, y1, x2, y2

gradient.addColorStop(0,  'rgb(255,80,0)');    // Départ
gradient.addColorStop(0.5,'rgb(255,191,0)');   // Milieu
gradient.addColorStop(1,  'rgb(255,246,155)'); // Arrivée

context.fillStyle = gradient;  // Le dégradé devient le style de remplissage

context.fillRect(
    0,   // X du premier point
    0,   // Y du premier point
    400, // X du second point
    400  // Y du second point
);
                    

Dégradé linéaire

Dégradé Radial


var gradient = context.createRadialGradient(
    0,   // X du premier cercle
    0,   // Y du premier cercle
    50,  // Rayon du premier cercle
    0,   // X du second cercle
    250, // Y du second cercle
    350  // Rayon du second cercle
);

gradient.addColorStop(0,  'rgb(255,80,0)');    // Couleur de départ
gradient.addColorStop(0.5,'rgb(255,191,0)');   // Couleur de milieu
gradient.addColorStop(1,  'rgb(255,246,155)'); // Couleur de arrivée

context.fillStyle = gradient;  // Le dégradé devient le style de remplissage

context.fillRect(0,0,400,400); // Faire apparaître
                    

Dégradé Radial

save() et restore()

Les fonctions save() et restore() permettent de sauvegarder l'état du style et de le restaurer

Cela équivaut à un historique de pinceau


context.beginPath();
context.moveTo(50,50);
context.lineTo(300,50);
context.save();              // Sauvegarde les propriétés du context
context.lineWidth = 20;      // Changement d'une des propriétés
context.stroke();            // Dessin du trait

context.beginPath();
context.moveTo(50,100);
context.lineTo(300,100);
context.save();              // Nouvelle sauvegarde des propriétés du context
context.strokeStyle = 'red'; // Changement d'une autre propriété
context.stroke();            // Dessin du trait

context.beginPath();
context.moveTo(50,150);
context.lineTo(300,150);
context.restore();           // Restauration des propriétés à la derniène sauvegarde
context.restore();           // Restauration des propriétés à la sauvegarde encore avant
context.stroke();            // Dessin du trait
                    

Courbes

Les méthodes bezierCurveTo() et quadraticCurveTo() permettent de dessiner des courbes de bézier en spécifiant chacun des points

Courbe de bézier


context.beginPath();
context.moveTo(50,50); // X et Y du point de départ
context.bezierCurveTo(
    300, // X du premier point de tension
    100, // Y du premier point de tension
    100, // X du second point de tension
    300, // Y du second point de tension
    300, // X du point d'arrivée
    300  // Y du point d'arrivée
);
context.stroke();
                    

Courbe de bézier

Courbe de quadratique (de bézier)


context.beginPath();
context.moveTo(50,50); // X et Y du point de départ
context.quadraticCurveTo(
    300, // X du seul point de tension
    100, // Y du seul point de tension
    300, // X du point d'arrivée
    300  // Y du point d'arrivée
);
context.stroke();
                    

Courbe de quadratique (de bézier)

globalAlpha


context.globalAlpha = 0.3; /* Réduction de l'opacité */

context.fillStyle = '#ff0000';
context.fillRect(50,50,200,200);

context.fillStyle = '#00ff00';
context.fillRect(100,100,200,200);

context.fillStyle = '#0000ff';
context.fillRect(150,150,200,200);
                    

globalCopositeOperation

La propriété globalCopositeOperation permet de spécifier comment doivent se comporter les tracés en cours (source) par rapport aux tracés initiaux (destination)

Équivalent au pathfinder d'illustrator


context.globalCompositeOperation = 'lighter';

context.fillStyle = '#ff0000';
context.fillRect(50,50,200,200);

context.fillStyle = '#00ff00';
context.fillRect(100,100,200,200);

context.fillStyle = '#0000ff';
context.fillRect(150,150,200,200);
                    

context.fillStyle = 'red';
context.fillRect(200,200,200,200);

context.globalCompositeOperation = 'destination-out'; /* source-over | source-in | source-out | source-atop | destination-over | destination-in | destination-out | desination-atop | lighter | copy | xor */

context.beginPath();
context.fillStyle = 'blue';
context.arc(200,250,100,0,Math.PI,false);
context.fill();
                    

ImageData

  • getImageData() permet de récupérer les pixels d'une zone du canvas
  • Ces pixels sont stockés dans la propriétés data de l'objet renvoyé
  • Il s'agit d'un tableau
  • Les valeurs R, G, B et A de chaque pixel sont les unes à la suite des autres
  • Il faut donc parcourir ce tableau 4 par 4


/* Si vous utilisez une image dans le canvas, lancez MAMP/WAMP pour éviter les proplème de cross-domain */
var image = new Image();
image.onload = function()
{
    /* Dessiner l'image chargée dans le canvas */
    context.drawImage(image,0,0,image.width / 6,image.height / 6);

    /* Récupérer les pixels dans image_data */
    var image_data = context.getImageData(0,0,image.width / 6,image.height / 6);

    /* parcourir les pixels 4 par 4 */
    for(var i = 0; i < image_data.data.length; i += 4)
    {
        /* Traiter ces pixels couleur par couleur */
        /* Ici on rend l'image noir et blanc */
        var b = 0.4 * image_data.data[i] + 0.4 * image_data.data[i + 1] + 0.4 * image_data.data[i + 2];
        image_data.data[i]     = b;
        image_data.data[i + 1] = b;
        image_data.data[i + 2] = b;
        // image_data.data[i + 3] = 1; /* On ne touche pas à l'apha */
    }

    /* Dessiner la nouvelle image par dessus */
    context.putImageData(image_data,0,0);
};
image.src = 'image-1.jpg';
                    

Ne fonctionne pas en local !

Canvas cheat sheet

Canvas cheat sheet (plus visuel)

Animer

Pour animer un canvas, on l'efface complètement et on le redessine à chaque frame (jusqu'à 60 fois par seconde)

Il nous faut donc un fonction qui se déclenche à interval régulier : requestAnimationFrame

Request Animation Frame


/* 
    La fonction loop sera appelée dès que possible
    Plus l'ordinateur est performant, plus la fréquence sera rapide
    La fonction ne sera pas appelée tant que l'ordinateur n'est pas prêt
*/
function loop()
{
    window.requestAnimationFrame(loop);
    console.log('loop');
}
window.requestAnimationFrame(loop);
                    

Can I Use : http://caniuse.com/#feat=requestanimationframe

Le polyfill proposé par Paul Irish


/* Compatible avec tous les navigateurs */
(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame =
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());
                    

Exemple d'animation


var coords = {x:0,y:200};

function loop()
{
    requestAnimationFrame(loop); //Avant d'effectuer d'autre action
    
    //Mettre à jour la position
    coords.x += 4;
    if(coords.x > canvas.width + 50)
        coords.x = -50;
    
    //Redessiner le canvas
    context.clearRect(0,0,canvas.width,canvas.height);
    context.beginPath();
    context.arc(coords.x,coords.y,50,0,Math.PI*2);
    context.fillStyle = 'orange';
    context.fill();
}
loop();
                    

Exemple d'animation