Comprendre les opérations sur Canvas
L'API Canvas du HTML5 offre des méthodes puissantes pour manipuler des graphiques, y compris la rotation. Il est important de noter que ctx.rotate() ne fait pas pivoter la toile elle-même dans le DOM. Au lieu de cela, il modifie le système de coordonnées de la toile pour les opérations futures. Pensez-y comme à la manipulation d'une "toile virtuelle" qui influence la toile visible.
Par défaut, le point d'origine (0,0) de la toile se trouve dans son coin supérieur gauche. Vous pouvez le déplacer à l'aide de ctx.translate(x, y).
Concepts clés du Canvas
- Rotation :
ctx.rotate(angle)fait pivoter le système de coordonnées dans le sens des aiguilles d'une montre. Un angle positif fait pivoter dans le sens horaire, un angle négatif dans le sens antihoraire. L'angle est spécifié en radians (par exemple,90 * Math.PI / 180pour 90 degrés). - Déplacement :
ctx.translate(x, y)modifie l'origine du système de coordonnées. - Dessin d'images : La méthode
context.drawImage()est polyvalente. Elle peut être utilisée pour dessiner une image entière, une partie d'une image, ou pour redimensionner une image sur la toile.drawImage(image, x, y): Dessine l'image aux coordonnées (x, y).drawImage(image, x, y, width, height): Dessine l'image aux coordonnées (x, y) avec les dimensions spécifiées.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight): Découpe une partie de l'image (à partir de sx, sy avec la largeur sWidth et la hauteur sHeight) et la dessine à la destination (dx, dy) avec les dimensions dWidth et dHeight.
- Gestion de l'état :
ctx.save()ernegistre l'état actuel du contexte (transformations, couleurs, etc.), etctx.restore()restaure l'état précédemment sauvegardé. Ceci est crucial pour isoler les transformations appliquées à différentes parties du dessin.
Exemple d'utilisation de rotate, translate, save et restore
Sans save() et restore(), les rotations s'accumulent :
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext('2d');
context.lineWidth = "10";
context.strokeStyle = "#fe9901";
context.translate(canvas.width / 2, canvas.height / 2); // Déplacer l'origine au centre
// Première rotation et dessin
context.rotate(30 * Math.PI / 180);
context.beginPath();
context.moveTo(0, -180);
context.lineTo(0, -200);
context.stroke();
// Deuxième rotation (cumulative) et dessin
context.rotate(30 * Math.PI / 180); // S'ajoute à la rotation précédente
context.beginPath();
context.moveTo(0, -140);
context.lineTo(0, -160);
context.stroke();
Avec save() et restore(), chaque dessin est effectué à partir de l'état sauvegardé :
context.save(); // Sauvegarde l'état initial
context.rotate(30 * Math.PI / 180);
context.beginPath();
context.moveTo(0, -180);
context.lineTo(0, -200);
context.stroke();
context.restore(); // Restaure l'état initial (0 degrés)
// Deuxième dessin, commence à partir de l'état restauré
context.rotate(30 * Math.PI / 180);
context.beginPath();
context.moveTo(0, -140);
context.lineTo(0, -160);
context.stroke();
Rotation d'images sur Canvas
Considérons une image dessinée sur une toile. Nous voulons la faire pivoter et ensuite sauvegarder le résultat.
Exemple de code pour la rotation d'image
Cet exemple dessine une image sur une toile, puis crée une nouvelle toile pour appliquer une rotation de 90 degrés avant de dessiner l'image tournée.
<div class="canvasparent" id="canvasparent">
<canvas id="canvas"></canvas>
</div>
<img src="" id="rotatedImage" style="border: 1px solid #ccc"/>
<script>
var sourceCanvas = document.getElementById('canvas');
var sourceCtx = sourceCanvas.getContext('2d');
sourceCanvas.width = 100;
sourceCanvas.height = 200;
var originalImage = new Image();
originalImage.src = './images/sample.jpg'; // Assurez-vous que le chemin est correct
originalImage.onload = function() {
// Dessiner l'image originale sur la première toile
sourceCtx.drawImage(originalImage, 0, 0, sourceCanvas.width, sourceCanvas.height);
var imageDataUrl = sourceCanvas.toDataURL('image/png', 1);
var rotatedImageElement = new Image();
rotatedImageElement.src = imageDataUrl;
rotatedImageElement.onload = function() {
// Créer une nouvelle toile pour l'image tournée
var rotatedCanvas = document.createElement("canvas");
var rotatedCtx = rotatedCanvas.getContext('2d');
// Définir les dimensions de la toile tournée (inverse des dimensions originales)
rotatedCanvas.width = 200;
rotatedCanvas.height = 100;
// Appliquer la rotation et le dessin
rotatedCtx.translate(0, rotatedCanvas.height); // Déplacer l'origine
rotatedCtx.rotate(-90 * Math.PI / 180); // Rotation anti-horaire
// Dessiner l'image en tenant compte de la transformation
rotatedCtx.drawImage(rotatedImageElement, 0, 0, sourceCanvas.width, sourceCanvas.height, 0, 0, rotatedCanvas.width, rotatedCanvas.height);
var rotatedDataUrl = rotatedCanvas.toDataURL('image/png', 1);
document.getElementById('rotatedImage').src = rotatedDataUrl;
}
}
</script>
Une alternative sans translate, ajustant directement les coordonnées drawImage après rotation :
// ... (initialisation et chargement de l'image identiques) ...
rotatedImageElement.onload = function() {
var rotatedCanvas = document.createElement("canvas");
var rotatedCtx = rotatedCanvas.getContext('2d');
rotatedCanvas.width = 200;
rotatedCanvas.height = 100;
// Appliquer directement la rotation
rotatedCtx.rotate(-90 * Math.PI / 180);
// Ajuster les coordonnées de drawImage pour compenser la rotation
rotatedCtx.drawImage(rotatedImageElement, -100, 0, sourceCanvas.width, sourceCanvas.height);
var rotatedDataUrl = rotatedCanvas.toDataURL('image/png', 1);
document.getElementById('rotatedImage').src = rotatedDataUrl;
}
// ...
Implémentation d'une signature manuscrite sur mobile
Ce système permet aux utilisateurs de signer numériquement sur un appareil mobile. Il gère également la rotation de l'appareil (passage de portrait à paysage) pendant la signature.
Structure HTML et CSS
Le HTML définit les boutons pour démarrer la signature, un espace pour afficher l'image de signature résultante, et une fenêtre modale (#signature-pop) contenant la toile de signature.
Le CSS gère l'apparence, le positionnement fixe de la fenêtre modale et l'ajustement dynamique de la toile en fonction de l'orientation de l'appareil.
Logique JavaScript
Une classe SignatureClass encapsule la logique de signature.
init(): Initialise la détection de l'orientation, efface la toile et configure la soumission.hengshuping(): Détecte l'orientation de l'appareil (window.orientation) et ajuste la taille et la rotation de la toile (canvasoucanvas2) en conséquence. En mode paysage, la toile est tournée de 90 degrés.drawEvent(): Gère les événements tactiles (touchstart,touchmove,touchend) pour dessiner sur la toile. Elle enregistre les points de début et de fin pour déterminer les limites du contenu dessiné.clear(): Ajoute un écouteur d'événement au bouton "Effacer" pour nettoyer la toile.submit(): Gère le clic sur le bouton "Confirmer". Si du contenu a été dessiné, il convertit la toile en une image PNG, puis la découpe et la redimensionne en fonction des limites détectées et de l'orientation de l'appareil. Un callback est exécuté avec l'image résultante en base64.pos(event): Calcule les coordonnées du toucher par rapport à la toile.judgeSize(value): Met à jour les coordonnées minimales et maximales pour délimiter la zone de dessin.
Le code principal déclenche l'instance SignatureClass lors du clic sur le bouton "Commencer la signature" et gère l'affichage/masquage de la fenêtre modale.
// Classe pour la gestion de la signature manuscrite
var SignatureClass = (function() {
function getStyle(element, property) {
if (element.currentStyle) {
return element.currentStyle[property];
} else {
return getComputedStyle(element, false)[property];
}
}
function SignaturePad(options) {
this.canvas = null;
this.context = null;
this.minCoords = { x: Infinity, y: Infinity };
this.maxCoords = { x: -Infinity, y: -Infinity };
this.isPortrait = true; // Vrai si l'appareil est en mode portrait
this.hasDrawing = false; // Indique si un dessin a été effectué
this.options = options || {};
}
SignaturePad.prototype.initialize = function() {
var self = this;
this.handleOrientationChange();
this.setupClearButton();
this.setupSubmitButton();
window.addEventListener('resize', function() {
self.handleOrientationChange();
}, false);
};
SignaturePad.prototype.handleOrientationChange = function() {
var signatureContainer = document.querySelector("#signature-container");
var portraitCanvas = document.getElementById("canvas");
var landscapeCanvas = document.getElementById("canvas2");
if (window.orientation === 90 || window.orientation === -90) {
// Mode paysage
this.isPortrait = false;
landscapeCanvas.style.display = "none";
portraitCanvas.style.display = "block";
this.canvas = portraitCanvas;
this.context = this.canvas.getContext("2d");
// Ajuster la taille et la transformation pour le paysage
signatureContainer.style.width = window.innerWidth + "px";
signatureContainer.style.height = window.innerHeight + "px";
this.canvas.height = parseInt(getStyle(this.canvas.parentNode, 'height'));
this.canvas.width = parseInt(getStyle(this.canvas.parentNode, 'width'));
signatureContainer.style.transform = "rotate(0deg)";
signatureContainer.style.transformOrigin = "0 0";
signatureContainer.style.marginLeft = "0px";
} else {
// Mode portrait
this.isPortrait = true;
portraitCanvas.style.display = "none";
landscapeCanvas.style.display = "block";
this.canvas = landscapeCanvas;
this.context = this.canvas.getContext("2d");
// Ajuster la taille et la transformation pour le portrait
this.canvas.height = window.innerHeight;
this.canvas.width = window.innerWidth - 80; // Marges latérales
signatureContainer.style.width = window.innerHeight + "px";
signatureContainer.style.height = window.innerWidth + "px";
signatureContainer.style.transform = "rotate(90deg)";
signatureContainer.style.transformOrigin = "0 0";
signatureContainer.style.marginLeft = window.innerWidth + "px";
}
this.context.lineWidth = 1;
this.context.strokeStyle = "#000000";
this.setupDrawingEvents();
this.resetBounds(); // Réinitialiser les limites pour une nouvelle orientation
};
SignaturePad.prototype.setupDrawingEvents = function() {
var self = this;
this.canvas.addEventListener("touchstart", function(evt) {
evt.preventDefault();
var startPos = self.getTouchPosition(evt);
self.context.beginPath();
self.context.moveTo(startPos.x, startPos.y);
self.updateBounds(startPos);
self.hasDrawing = false; // Réinitialiser au début d'un nouveau tracé
var touchMoveHandler = function(e) {
e.preventDefault();
var currentPos = self.getTouchPosition(e);
self.context.lineTo(currentPos.x, currentPos.y);
self.updateBounds(currentPos);
self.context.stroke();
};
var touchEndHandler = function(e) {
e.preventDefault();
self.hasDrawing = true; // Marquer qu'un dessin a été effectué
self.canvas.removeEventListener("touchmove", touchMoveHandler);
document.removeEventListener("touchend", touchEndHandler);
};
self.canvas.addEventListener("touchmove", touchMoveHandler, false);
document.addEventListener("touchend", touchEndHandler, false);
});
};
SignaturePad.prototype.setupClearButton = function() {
var self = this;
document.getElementById("clean_canvas").addEventListener("click", function() {
self.context.clearRect(0, 0, self.canvas.width, self.canvas.height);
self.resetBounds();
self.hasDrawing = false;
});
};
SignaturePad.prototype.setupSubmitButton = function() {
var self = this;
document.getElementById("sure_canvas").addEventListener("click", function() {
if (self.hasDrawing) {
var imageDataUrl = self.canvas.toDataURL('image/png', 1);
var image = new Image();
image.src = imageDataUrl;
image.onload = function() {
var drawnWidth = self.maxCoords.x - self.minCoords.x;
var drawnHeight = self.maxCoords.y - self.minCoords.y;
var targetWidth = 200;
var targetHeight = 100;
var clipCanvas = document.createElement("canvas");
clipCanvas.width = targetWidth;
clipCanvas.height = targetHeight;
var clipCtx = clipCanvas.getContext("2d");
if (self.isPortrait) {
// Pour le portrait, la toile est tournée, donc on ajuste le dessin
clipCtx.translate(0, targetHeight);
clipCtx.rotate(-90 * Math.PI / 180);
clipCtx.drawImage(image, self.minCoords.x, self.minCoords.y, drawnWidth, drawnHeight, 0, 0, targetHeight, targetWidth);
} else {
// Pour le paysage, dessin direct
clipCtx.drawImage(image, self.minCoords.x, self.minCoords.y, drawnWidth, drawnHeight, 0, 0, targetWidth, targetHeight);
}
var finalDataUrl = clipCanvas.toDataURL('image/png', 1);
if (self.options.callback && typeof self.options.callback === 'function') {
self.options.callback(finalDataUrl);
}
// Nettoyer les références
self.canvas = null;
self.context = null;
clipCtx = null;
clipCanvas = null;
};
} else {
alert("Veuillez d'abord dessiner votre signature.");
}
});
};
SignaturePad.prototype.getTouchPosition = function(event) {
var touch = event.touches[0];
var rect = event.target.getBoundingClientRect();
var x = touch.clientX - rect.left;
var y = touch.clientY - rect.top;
return { x: x, y: y };
};
SignaturePad.prototype.updateBounds = function(point) {
this.minCoords.x = Math.min(this.minCoords.x, point.x);
this.minCoords.y = Math.min(this.minCoords.y, point.y);
this.maxCoords.x = Math.max(this.maxCoords.x, point.x);
this.maxCoords.y = Math.max(this.maxCoords.y, point.y);
};
SignaturePad.prototype.resetBounds = function() {
this.minCoords = { x: Infinity, y: Infinity };
this.maxCoords = { x: -Infinity, y: -Infinity };
};
return SignaturePad;
})();
// Gestionnaire d'événements pour le bouton de signature
document.getElementById('signature-button').addEventListener('click', function() {
document.getElementById('signature-pop').style.display = 'block';
var signaturePad = new SignatureClass({
callback: function(base64Signature) {
document.getElementById('signature-img').src = base64Signature;
document.getElementById('signature-pop').style.display = 'none';
document.getElementById('signature-img').style.display = 'block';
}
});
signaturePad.initialize();
}, false);
// Gestionnaire d'événements pour le bouton de retour
document.getElementById('return').addEventListener('click', function() {
document.getElementById('signature-pop').style.display = 'none';
}, false);