Récemment, j'ai eu l'occasion d'écrire un service pour une boutique en ligne qui aiderait à passer une commande pour imprimer mes photos.
Le service a supposé un éditeur d'image «simple», dont je voudrais partager la création. Et tout cela parce que parmi l'abondance de toutes sortes de plug-ins, je n'ai pas trouvé de fonctionnalité appropriée, en plus, les nuances des transformations CSS, sont soudain devenues une tâche très non triviale pour moi.

Les tâches principales:
- Possibilité de télécharger des images depuis votre appareil, Google Drive et Instagram.
- Édition d'image: déplacement, rotation, réflexion horizontale et verticale, zoom, alignement automatique de l'image pour remplir la zone de recadrage.
Si le sujet s'avère intéressant, dans la prochaine publication, je décrirai en détail l'intégration avec Google Drive et Instagram dans la partie backend de l'application, où le populaire package NodeJS + Express a été utilisé.
Pour l'organisation du frontend, j'ai choisi le merveilleux framework Vue. Tout simplement parce que cela m'inspire après une réaction angulaire et agaçante. Je pense que cela n'a aucun sens de décrire l'architecture, les routes et autres composants, passons directement à l'éditeur.
À propos, la démo de l'éditeur peut être piquée
ici .
Nous aurons besoin de deux composants:
Modifier - contiendra la logique et les commandes principales
Aperçu - sera responsable de l'affichage de l'image
Modification du modèle de composant:<Edit> <Preview v-if="image" ref="preview" :matrix="matrix" :image="image" :transform="transform" @resized="areaResized" @loaded="imageLoaded" @moved="imageMoved" /> <input type="range" :min="minZoom" :max="maxZoom" step="any" @change="onZoomEnd" v-model.number="transform.zoom" :disabled="!imageReady" /> <button @click="rotateMinus" :disabled="!imageReady">Rotate left</button> <button @click="rotatePlus" :disabled="!imageReady">Rotate right</button> <button @click="flipY" :disabled="!imageReady">Flip horizontal</button> <button @click="flipX" :disabled="!imageReady">Flip vertical</button> </Edit>
Le composant Aperçu peut déclencher 3 événements:
chargé - événement de chargement d'image
redimensionné - événement de
redimensionnement de la fenêtre
déplacé - image événement en mouvement
Paramètres:
image - lien
imagematrix - matrice de transformation pour transformer la propriété CSS
transformer - un objet qui décrit la transformation
Afin de mieux contrôler la position de l'image, img a un positionnement absolu et la propriété
transform-origin , le point de référence de
transformation , est définie sur la valeur initiale "0 0", qui correspond à l'origine dans le coin supérieur gauche de l'original (avant transformation!) Image.
Le principal problème que j'ai rencontré est que vous devez vous assurer que le point d'origine de la transformation est toujours au centre de la zone d'édition, sinon, pendant les transformations, la partie sélectionnée de l'image se décale. L'utilisation de cette
matrice de transformation permet de résoudre ce problème.
Modification des composants
Propriétés du composant Edit: export default { components: { Preview }, data () { return { image: null, imageReady: false, imageRect: {},
Les valeurs de imageRect et areaRect nous sont transmises par le composant Preview, appelant respectivement les méthodes imageLoaded et areaResized, les objets ont la structure:
{ size: { width: 100, height: 100 }, center: { x: 50, y: 50 } }
Les valeurs centrales peuvent être calculées à chaque fois, mais il est plus facile de les noter une fois.
La propriété de matrice calculée correspond aux mêmes coefficients de la matrice de transformation.
La première tâche qui doit être résolue consiste à centrer l'image avec un rapport d'aspect arbitraire dans la zone de recadrage, tandis que l'image doit pouvoir s'adapter complètement, les zones non remplies (uniquement) au-dessus et en dessous, ou (uniquement) à gauche et à droite sont acceptables. Avec toutes les transformations, cette condition doit être maintenue.
Tout d'abord, nous limiterons les valeurs de zoom, pour cela nous vérifierons le rapport hauteur / largeur, en tenant compte de l'orientation de l'image.
Méthodes des composants: _setMinZoom(){ let rotate = this.matrix.c !== 0; let horizontal = this.imageRect.size.height < this.imageRect.size.width; let areaSize = (horizontal && !rotate || !horizontal && rotate) ? this.areaRect.size.width : this.areaRect.size.height; let imageSize = horizontal ? this.imageRect.size.width : this.imageRect.size.height; this.minZoom = areaSize/imageSize; if(this.transform.zoom < this.minZoom) this.transform.zoom = this.minZoom; }, _setMaxZoom(){ this.maxZoom = this.areaRect.size.width/config.image.minResolution; if(this.transform.zoom > this.maxZoom) this.transform.zoom = this.maxZoom; },
Passons maintenant aux transformations. Pour commencer, nous décrivons les reflets, car ils ne décalent pas la zone visible de l'image.
Méthodes des composants: flipX(){ this.matrix.b == 0 && this.matrix.c == 0 ? this.transform.flip = !this.transform.flip : this.transform.flop = !this.transform.flop; }, flipY(){ this.matrix.b == 0 && this.matrix.c == 0 ? this.transform.flop = !this.transform.flop : this.transform.flip = !this.transform.flip; },
Les transformations de zoom, de rotation et de déplacement nécessiteront déjà des ajustements de l'origine de la transformation.
Méthodes des composants: onZoomEnd(){ this._translate(); }, rotatePlus(){ this.transform.rotate += 90; this._setMinZoom(); this._translate(); }, rotateMinus(){ this.transform.rotate -= 90; this._setMinZoom(); this._translate(); }, imageMoved(translate){ this._translate(); },
C'est la méthode
_translate qui est responsable de toutes les subtilités des transformations. Deux référentiels doivent être introduits. Le premier, appelons-le zéro, commence dans le coin supérieur gauche de l'image, lorsque nous multiplions les coordonnées par la matrice de transformation, nous passons à un autre système de coordonnées, appelons-le local. Dans ce cas, nous pouvons inverser la transition du local au zéro en trouvant la
matrice de transformation
inverse .
Il s'avère que nous avons besoin de deux fonctions.
La première consiste à passer de zéro au système local, le navigateur effectue les mêmes conversions lorsque nous spécifions la propriété de transformation css.
img { transform: matrix(a, b, c, d, tx, ty); }
La seconde - pour trouver les coordonnées originales de l'image, ayant déjà transformé les coordonnées.
Il est plus pratique d'écrire ces fonctions en utilisant des méthodes d'une classe distincte.
Transformer la classe: class Transform { constructor(center, matrix){ this.init(center, matrix); } init(center, matrix){ if(center) this.center = Object.assign({},center); if(matrix) this.matrix = Object.assign({},matrix); } getOrigins(current){
_Méthode de traduction avec commentaires détaillés: _translate(checkAlign = true){ const tr = new Transform(this.transform.center, this.matrix);
L'alignement crée l'effet de «coller» l'image sur les bords du recadrage, en évitant les champs vides.
Composant d'aperçu
La tâche principale de ce composant est d'afficher l'image, d'appliquer des transformations et de répondre au mouvement du bouton de la souris pris en sandwich sur l'image. En calculant le décalage, nous mettons à jour les paramètres
transform.x et
transform.y , à la fin du mouvement, nous déclenchons l'événement
déplacé , indiquant au composant Edit que nous devons recalculer la position du centre de transformation et ajuster transform.x et transform.y.
Aperçu du modèle de composant:<div ref = "area" class = "edit-zone"
@ mousedown = "onMoveStart"
@ touchstart = "onMoveStart"
mouseup = "onMoveEnd"
@ touchend = "onMoveEnd"
@ mousemove = "onMove"
@ touchmove = "onMove">
<img
v-if = "image"
ref = "image"
load = "imageLoaded"
: src = "image"
: style = "{'transform': transformStyle, 'transform-origin': transformOrigin}">
La fonctionnalité de l'éditeur est parfaitement séparée du projet principal et se trouve
ici .
J'espère que ce matériel vous sera utile. Je vous remercie!