Points clés à retenir
- Stockez les positions de recadrage par rapport aux dimensions de la source ou sous forme de fractions normalisées.
- Corrigez l’orientation EXIF avant de convertir les coordonnées du pointeur.
- Séparez le rectangle d’aperçu à l’écran des dimensions de sortie finales.
Un outil de recadrage JavaScript remplit deux fonctions : aider une personne à choisir une région et décrire cette région sans ambiguïté. La confusion entre les pixels de l’aperçu, les coordonnées mises à l’échelle par CSS et les pixels source est la cause la plus fréquente des recadrages incorrects.
L’essentiel
- Évitez de décoder à répétition de très grandes images sur les appareils disposant de peu de mémoire.
- Validez à nouveau les coordonnées et les dimensions minimales sur le serveur.
Établir un modèle de coordonnées de référence unique
Un outil de recadrage dans le navigateur gère au moins trois espaces : les coordonnées de la zone d’affichage issues des événements du pointeur, les coordonnées de rendu dans l’aperçu et les pixels source de l’image décodée. clientWidth et clientHeight décrivent la boîte CSS, tandis que naturalWidth et naturalHeight décrivent les dimensions décodées. Enregistrer le rectangle de l’aperçu comme s’il était exprimé en pixels source provoque un décalage de la sélection dès que la taille de l’aperçu change.
Utilisez des coordonnées source normalisées pour le modèle persistant. Stockez x, y, width et height sous forme de fractions comprises entre zéro et un, ou stockez les coordonnées normalisées des coins. Convertissez-les en valeurs de rendu uniquement pour l’affichage, et en pixels source entiers uniquement lors du rendu. Précisez si les bords inférieur et droit sont exclus afin que chaque implémentation arrondisse les coordonnées et mesure la même région.
function normalizeCrop(crop, sourceWidth, sourceHeight) {
return {
x: crop.x / sourceWidth,
y: crop.y / sourceHeight,
width: crop.width / sourceWidth,
height: crop.height / sourceHeight,
}
}
function cropToSourcePixels(crop, sourceWidth, sourceHeight) {
return {
left: Math.round(crop.x * sourceWidth),
top: Math.round(crop.y * sourceHeight),
right: Math.round((crop.x + crop.width) * sourceWidth),
bottom: Math.round((crop.y + crop.height) * sourceHeight),
}
}Espace de la zone d’affichage
Coordonnées du pointeur relatives à la zone d’affichage du navigateur, avant soustraction des coordonnées du rectangle englobant de l’aperçu.
Espace de l’aperçu
Pixels CSS utilisés pour dessiner la sélection interactive.
Espace source
Pixels de l’image à l’orientation normalisée utilisée pour le recadrage de référence.
Espace de sortie
Pixels du dérivé final encodé, dont les dimensions peuvent différer de celles de la région de recadrage dans la source.
Faire correspondre les pixels affichés, pas seulement la boîte de l’élément
Le contenu peut ne pas occuper la totalité de l’élément img. object-fit: contain peut créer des bandes de remplissage, tandis que object-fit: cover masque une partie de la source avant l’application de la superposition de recadrage. Calculez le rectangle réel de l’image rendue, en tenant compte de son échelle et de son décalage, puis inversez cette transformation. Avec un aperçu complet sans transformation, une correspondance de base est sourceX = previewX multiplié par naturalWidth divisé par renderedImageWidth.
Les événements du pointeur indiquent des positions dans la zone d’affichage, et getBoundingClientRect renvoie des coordonnées relatives à cette zone. Soustraire les coordonnées gauche et supérieure du rectangle du contenu donne donc des valeurs relatives à l’élément, sans correction distincte du défilement. Les décalages de défilement ne comptent que lorsque des coordonnées de page telles que pageX interviennent dans le calcul. Si des transformations CSS réalisent le zoom ou la rotation de l’aperçu, incluez leurs inverses dans la conversion des coordonnées ou conservez ces transformations dans un modèle unique. Bornez les valeurs normalisées finales et rejetez toute région vide ou inversée au lieu de la corriger silencieusement.
Normaliser l’orientation avant les calculs de coordonnées
Les fichiers issus d’appareils photo stockent souvent des pixels au format paysage, avec des métadonnées qui indiquent aux logiciels de visualisation de faire pivoter l’image. La largeur, la hauteur et les axes affichés peuvent donc différer de ceux de la matrice encodée. Définissez les coordonnées de recadrage par rapport à une source dont l’orientation est corrigée, créez l’aperçu selon cette convention et transmettez l’état de l’orientation avec la requête de recadrage. Mélanger des coordonnées d’affichage corrigées avec des pixels source non corrigés produit des recadrages pivotés ou inversés en miroir.
Ne supposez pas que tous les chemins de décodage et de traitement par canvas appliquent les métadonnées de manière identique. Testez des jeux de données couvrant tous les cas d’orientation présents dans les fichiers que vos utilisateurs téléversent, y compris les rotations de 90 degrés qui permutent la largeur et la hauteur. Une fois que le backend a normalisé l’orientation, supprimez ou mettez à jour les anciennes métadonnées d’orientation dans le résultat afin que les logiciels de visualisation en aval ne fassent pas à nouveau pivoter les pixels déjà corrigés.
Séparer l’interaction de l’encodage par canvas
Le glissement devrait mettre à jour un petit modèle de recadrage et une surcouche peu coûteuse, sans réencoder sans cesse une grande image matricielle. Utilisez la capture du pointeur pour que le glissement reste actif lorsque le pointeur quitte une poignée, et limitez le mouvement dans l’espace des coordonnées normalisées. Produisez un aperçu de résolution inférieure qui conserve le rapport largeur/hauteur de la source. La sélection finale peut toujours se rapporter à l’original, car la correspondance est explicite.
Canvas est utile pour obtenir un aperçu immédiat. La forme à neuf arguments de drawImage accepte un rectangle source et un rectangle de destination, ce qui permet de dessiner les pixels source sélectionnés dans un petit canvas d’aperçu. Le rapport de pixels de l’appareil devrait modifier la résolution de l’image matricielle interne du canvas, sans modifier le recadrage enregistré. Différez les traitements d’aperçu non essentiels jusqu’à une pause dans les interactions et évitez de lire les données des pixels à chaque mouvement du pointeur.
function renderCrop(image, crop, maxDimension = 1024) {
const width = crop.right - crop.left
const height = crop.bottom - crop.top
if (![width, height, maxDimension].every((value) => Number.isFinite(value) && value > 0)) {
throw new Error('Crop dimensions must be finite and positive')
}
const scale = Math.min(1, maxDimension / Math.max(width, height))
const canvas = document.createElement('canvas')
canvas.width = Math.max(1, Math.round(width * scale))
canvas.height = Math.max(1, Math.round(height * scale))
const context = canvas.getContext('2d')
if (context == null) throw new Error('2D canvas is unavailable')
context.drawImage(
image,
crop.left,
crop.top,
width,
height,
0,
0,
canvas.width,
canvas.height,
)
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob == null) reject(new Error('The browser could not encode the crop'))
// Browsers fall back to image/png when the requested type is unsupported
else if (blob.type !== 'image/webp') reject(new Error('Encoder fell back to ' + blob.type))
else resolve(blob)
}, 'image/webp', 0.86)
})
}Mise à jour du modèle
Enregistrez la sélection normalisée de manière synchrone pour que l’interaction reste prévisible.
Rendu de l’aperçu
Dessinez une représentation à l’échelle après chaque modification de la sélection, sans la considérer comme le fichier de production.
Rendu de référence
Appliquez les coordonnées source validées dans un pipeline backend après la soumission.
Maîtriser la mémoire du navigateur et les effets secondaires de l’export
La taille du fichier compressé donne une mauvaise estimation de la mémoire nécessaire après décodage. Une grande photographie occupe alors un volume égal à sa largeur multipliée par sa hauteur et par l’espace de stockage d’un pixel, et canvas peut nécessiter des tampons supplémentaires. Limitez le nombre de pixels source, plafonnez la résolution de l’aperçu et évitez de conserver plusieurs canvas ou copies décodées. Les URL de Blob évitent l’augmentation de taille liée au base64, mais révoquez chaque URL après son remplacement ou lors du nettoyage des ressources.
L’export par canvas peut modifier les métadonnées, les profils colorimétriques, l’animation et la qualité d’encodage. Il peut aussi échouer lorsqu’une image provenant d’une autre origine ne dispose pas de l’autorisation nécessaire pour être utilisée dans un canvas, ce qui rend ce dernier contaminé. Considérez le résultat du navigateur comme une simple commodité, sauf si ces changements sont acceptables et ont été testés. Téléverser l’original avec les métadonnées de recadrage préserve une image maîtresse récupérable et produit un encodage cohérent d’un appareil client à l’autre.
Envoyer un contrat de recadrage restreint et validé
Transmettez l’identifiant de la source, la convention d’orientation, les coordonnées normalisées, le préréglage cible et une version du schéma. Le serveur doit analyser les nombres, rejeter les valeurs non finies, faire respecter 0 <= x1 < x2 <= 1 et 0 <= y1 < y2 <= 1, imposer des dimensions minimales exploitables et limiter les préréglages de sortie acceptés. La validation côté client améliore le retour d’information, mais ne peut pas autoriser des traitements coûteux ni des chemins de stockage de confiance.
Gardez les informations d’identification nécessaires au traitement et les options de transformation sans restriction hors du navigateur. Envoyez l’identifiant du média original et la géométrie normalisée du recadrage à un point de terminaison backend au périmètre restreint. Le backend devrait autoriser l’utilisation du média, borner les coordonnées, rejeter les zones vides ou trop petites, appliquer le recadrage à la source dont l’orientation a été normalisée et effectuer le redimensionnement dans une opération distincte lorsqu’une variante finale exacte est également requise.
Tester l’équivalence entre l’aperçu et le résultat
Créez des jeux de données de test déterministes pour des sources carrées, au format portrait, panoramiques, transparentes, pivotées et de très grande taille. Sélectionnez des zones sur chaque bord et comparez le résultat du backend à l’aperçu du navigateur en utilisant des tolérances de coordonnées qui tiennent compte des arrondis documentés. Incluez les bandes ajoutées par object-fit, le zoom de l’aperçu, le défilement de la page, le zoom du navigateur et les dimensions de l’image matricielle interne du canvas à haute densité.
Testez l’interaction sans pointeur. Les poignées de recadrage doivent disposer d’un indicateur de focus visible, de noms accessibles clairs et de commandes au clavier pour déplacer et redimensionner la zone. Annoncez les échecs de validation importants et la fin de l’opération, mais pas chaque incrément de glissement. Testez aussi l’annulation, l’échec du téléversement, une URL d’aperçu révoquée, des métadonnées mal formées, un contenu non pris en charge et la réexécution d’une requête, afin que la fiabilité ne se limite pas au recadrage dans le scénario nominal.
Détails techniques à connaître
- naturalWidth et naturalHeight décrivent les dimensions de l’image décodée, tandis que clientWidth et clientHeight décrivent la boîte CSS. Les coordonnées de recadrage doivent être converties entre ces espaces.
- Les images issues d’appareils photo peuvent contenir des métadonnées d’orientation qui modifient la largeur, la hauteur et les axes visibles sans changer l’ordre des pixels stockés. L’orientation doit donc être normalisée avant les calculs de coordonnées.
- Les URL de Blob évitent le surcoût de taille d’environ un tiers des URL de données en base64, mais chaque URL conserve le Blob sous-jacent jusqu’à l’appel de URL.revokeObjectURL ou au déchargement du document.
- L’export par canvas peut modifier les profils colorimétriques, les métadonnées, l’animation et la qualité d’encodage, ce qui constitue une raison supplémentaire de considérer le résultat du navigateur comme un aperçu plutôt que comme l’image maîtresse.
- Les coordonnées du pointeur sont relatives à la zone d’affichage jusqu’à leur conversion prenant en compte le rectangle englobant de l’élément, le défilement, le zoom et les éventuelles transformations CSS appliquées.
- Les très grandes images décodées peuvent dépasser les limites de canvas sur mobile, même lorsque le fichier téléversé compressé est petit. Les dimensions de l’aperçu et le nombre de pixels source nécessitent donc des limites distinctes.
Une approche pratique
- 1
Lisez les dimensions et l’orientation de la source une seule fois, puis définissez un système de coordonnées unique.
- 2
Affichez un aperçu léger et mettez à jour un modèle de recadrage plutôt que de réécrire le fichier à chaque glissement.
- 3
Envoyez les coordonnées normalisées avec le téléversement ou avec la requête de traitement ultérieure.
- 4
Comparez le résultat du backend à l’aperçu à l’aide d’images de test pivotées, panoramiques et au format portrait.
Périmètre architectural
Le canvas du navigateur est utile pour les aperçus interactifs, mais il consomme de la mémoire côté client et ne garantit pas un encodage identique sur tous les appareils. Ne confiez pas à un téléphone la génération de tous les dérivés utilisés en production.
Questions fréquentes
Faut-il stocker les coordonnées de recadrage JavaScript en pixels ou en pourcentages ?
Les fractions normalisées sont généralement les plus portables. Convertissez-les en pixels source côté backend après avoir vérifié l’orientation et les dimensions de la source.
Pourquoi un recadrage effectué par le backend diffère-t-il de la sélection dans le navigateur ?
Les causes courantes sont l’utilisation de la boîte de l’élément img au lieu du rectangle du contenu rendu, l’oubli des décalages liés à object-fit, la confusion entre pixels CSS et pixels source, ou une application différente de l’orientation EXIF.
Un Blob généré par un canvas convient-il comme image maîtresse ?
Généralement, non. L’export depuis un canvas peut modifier les métadonnées, les profils, l’animation et l’encodage. Conservez l’original téléversé et utilisez le résultat du canvas comme aperçu, sauf si ces changements sont intentionnels.
Pourquoi le canvas peut-il échouer avec une image chargée depuis un autre domaine ?
Si le serveur distant n’accorde pas l’accès interorigine requis, le dessin de l’image peut rendre le canvas non sûr et bloquer la lecture des pixels ou l’export.
Que doit valider le serveur pour une requête de recadrage ?
Validez les bornes numériques, l’ordre des coordonnées, la convention d’orientation, la propriété de la source, les dimensions minimales utiles, le préréglage de sortie, le type de fichier, les limites en pixels et l’autorisation de lancer le traitement.