Recientemente, tuve la oportunidad de escribir un servicio para una tienda en línea que ayudaría a hacer un pedido para imprimir mis fotos.
El servicio asumió un editor de imágenes "simple", cuya creación me gustaría compartir. Y todo porque entre la abundancia de todo tipo de complementos no encontré la funcionalidad adecuada, además, los matices de las transformaciones CSS, de repente se convirtieron en una tarea muy trivial para mí.

Las tareas principales:
- Posibilidad de subir imágenes desde su dispositivo, Google Drive e Instagram.
- Edición de imágenes: movimiento, rotación, reflexión horizontal y vertical, zoom, alineación automática de imágenes para llenar el área de recorte.
Si el tema resulta interesante, en la próxima publicación describiré en detalle la integración con Google Drive e Instagram en la parte de back-end de la aplicación, donde se utilizó la popular combinación NodeJS + Express.
Para la organización de la interfaz, elegí el maravilloso marco Vue. Solo porque me inspira después de Reacciones angulares y molestas. Creo que no tiene sentido describir la arquitectura, las rutas y otros componentes, vayamos directamente al editor.
Por cierto, la demostración del editor se puede pinchar
aquí .
Necesitaremos dos componentes:
Editar : contendrá la lógica principal y los controles
Vista previa : será responsable de mostrar la imagen
Edición de plantilla de componente:<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>
El componente Vista previa puede desencadenar 3 eventos:
cargado - evento de carga de imagen
redimensionado - evento de cambio de
tamaño de ventana
movido - evento de imagen en movimiento
Parámetros:
imagen - enlace de
imagenmatriz - matriz de transformación para transformar la propiedad CSS
transformar - un objeto que describe la transformación
Para controlar mejor la posición de la imagen, img tiene un posicionamiento absoluto, y la propiedad de
origen de transformación , el punto de referencia de
transformación , se establece en el valor inicial "0 0", que corresponde al origen en la esquina superior izquierda de la imagen original (¡antes de la transformación!).
El principal problema que encontré es que debe asegurarse de que el punto de origen de la transformación esté siempre en el centro del área de edición, de lo contrario, durante las transformaciones, la parte seleccionada de la imagen cambiará. El uso de esta
matriz de transformación ayuda a resolver este problema.
Edición de componentes
Propiedades del componente Editar: export default { components: { Preview }, data () { return { image: null, imageReady: false, imageRect: {},
El componente Vista previa nos pasa los valores de imageRect y areaRect, llamando a los métodos imageLoaded y areaResized, respectivamente, los objetos tienen la estructura:
{ size: { width: 100, height: 100 }, center: { x: 50, y: 50 } }
Los valores centrales se pueden calcular cada vez, pero es más fácil escribirlos una vez.
La propiedad de matriz calculada es los mismos coeficientes de la matriz de transformación.
La primera tarea que debe resolverse es centrar la imagen con una relación de aspecto arbitraria en el área de recorte, mientras que la imagen debe poder ajustarse completamente, las áreas sin rellenar (solo) arriba y abajo, o (solo) izquierda y derecha son aceptables. Con cualquier transformación, esta condición debe mantenerse.
En primer lugar, limitaremos los valores de zoom, para esto verificaremos la relación de aspecto, teniendo en cuenta la orientación de la imagen.
Métodos componentes _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; },
Ahora pasemos a las transformaciones. Para comenzar, describimos los reflejos, porque no cambian la región visible de la imagen.
Métodos componentes 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; },
Las transformaciones de zoom, rotación y desplazamiento ya requerirán ajustes de origen de transformación.
Métodos componentes 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(); },
Es el método
_translate el responsable de todas las sutilezas de las transformaciones. Se deben introducir dos sistemas de referencia. El primero, llamémoslo cero, comienza en la esquina superior izquierda de la imagen, cuando multiplicamos las coordenadas por la matriz de transformación, vamos a otro sistema de coordenadas, lo llamamos local. En este caso, podemos revertir la transición de local a cero al encontrar la
matriz de transformación
inversa .
Resulta que necesitamos dos funciones.
El primero es ir de cero al sistema local, el navegador realiza las mismas conversiones cuando especificamos la propiedad de transformación css.
img { transform: matrix(a, b, c, d, tx, ty); }
El segundo: para encontrar las coordenadas originales de la imagen, que ya ha transformado las coordenadas.
Es más conveniente escribir estas funciones utilizando métodos de una clase separada.
Transformar clase: 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étodo de traducción con comentarios detallados: _translate(checkAlign = true){ const tr = new Transform(this.transform.center, this.matrix);
La alineación crea el efecto de "pegar" la imagen a los bordes del recorte, evitando campos vacíos.
Componente de vista previa
La tarea principal de este componente es mostrar la imagen, aplicar transformaciones y responder al movimiento del botón del mouse sobre la imagen. Calculando el desplazamiento, actualizamos los parámetros
transform.x y
transform.y , al final del movimiento activamos el evento
movido , diciéndole al componente Editar que necesitamos volver a calcular la posición del centro de transformación y ajustar transform.x y transform.y.
Vista previa de la plantilla del componente:<div ref = "area" class = "edit-zone"
@ mousedown = "onMoveStart"
@ touchstart = "onMoveStart"
mouseup = "onMoveEnd"
@ touchend = "onMoveEnd"
@ mousemove = "onMove"
@ touchmove = "onMove">
<img
v-if = "imagen"
ref = "imagen"
load = "imageLoaded"
: src = "imagen"
: style = "{'transform': transformStyle, 'transform-origin': transformOrigin}">
La funcionalidad del editor está claramente separada del proyecto principal y se encuentra
aquí .
Espero que este material te sea útil. Gracias