Recentemente, tive a oportunidade de escrever um serviço para uma loja online que ajudaria a fazer um pedido para imprimir minhas fotos.
O serviço assumiu um editor de imagens "simples", cuja criação eu gostaria de compartilhar. E tudo porque, entre a abundância de todos os tipos de plugins, não encontrei funcionalidade adequada; além disso, as nuances das transformações de CSS, de repente se tornaram uma tarefa não trivial para mim.

As principais tarefas:
- Capacidade de fazer upload de imagens do seu dispositivo, Google Drive e Instagram.
- Edição de imagem: movimento, rotação, reflexão horizontal e vertical, zoom, alinhamento automático da imagem para preencher a área de corte.
Se o tópico for interessante, na próxima publicação, descreverei em detalhes a integração com o Google Drive e o Instagram na parte de back-end do aplicativo, onde o popular pacote NodeJS + Express foi usado.
Para a organização do front-end, escolhi o maravilhoso framework Vue. Só porque me inspira após um React angular e irritante. Eu acho que não faz sentido descrever a arquitetura, rotas e outros componentes, vamos direto ao editor.
A propósito, a demonstração do editor pode ser cutucada
aqui .
Vamos precisar de dois componentes:
Editar - conterá a lógica e os controles principais
Visualizar - será responsável por exibir a imagem
Edição de modelo 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>
O componente Visualizar pode disparar 3 eventos:
carregado - evento de carregamento de imagem
redimensionado - evento de
redimensionamento de janela
moving - evento em movimento da imagem
Parâmetros:
imagem - link da
imagemmatrix - matriz de transformação para a propriedade CSS de transformação
transformar - um objeto que descreve a transformação
Para controlar melhor a posição da imagem, img possui posicionamento absoluto, e a propriedade
transform-origin , o ponto de referência da
transformação , é definida como o valor inicial "0 0", que corresponde à origem no canto superior esquerdo da imagem original (antes da transformação!).
O principal problema que encontrei é que você precisa garantir que o ponto de origem da transformação esteja sempre no centro da área de edição; caso contrário, durante as transformações, a parte selecionada da imagem será alterada. O uso dessa
matriz de transformação ajuda a resolver esse problema.
Edição de componentes
Propriedades do componente Editar: export default { components: { Preview }, data () { return { image: null, imageReady: false, imageRect: {},
Os valores de imageRect e areaRect são transmitidos a nós pelo componente Preview, chamando os métodos imageLoaded e areaResized, respectivamente, os objetos têm a estrutura:
{ size: { width: 100, height: 100 }, center: { x: 50, y: 50 } }
Os valores centrais podem ser calculados a cada vez, mas é mais fácil anotá-los uma vez.
A propriedade da matriz calculada é os mesmos coeficientes da matriz de transformação.
A primeira tarefa que precisa ser resolvida é centralizar a imagem com uma proporção arbitrária na área de corte, enquanto a imagem deve caber completamente, áreas não preenchidas (somente) acima e abaixo ou (somente) esquerda e direita são aceitáveis. Com quaisquer transformações, essa condição deve ser mantida.
Primeiramente, limitaremos os valores para o zoom, para isso verificaremos a proporção, levando em consideração a orientação da imagem.
Métodos de 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; },
Agora vamos para as transformações. Para começar, descrevemos as reflexões, porque elas não mudam a região visível da imagem.
Métodos de 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; },
As transformações de zoom, rotação e deslocamento já exigirão ajustes na origem da transformação.
Métodos de 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(); },
É o método
_translate que é responsável por todas as sutilezas das transformações. Dois sistemas de referência devem ser introduzidos. O primeiro, vamos chamá-lo de zero, começa no canto superior esquerdo da imagem, quando multiplicamos as coordenadas pela matriz de transformação, vamos para outro sistema de coordenadas, local. Nesse caso, podemos reverter a transição do local para o zero, localizando a
matriz de transformação
inversa .
Acontece que precisamos de duas funções.
O primeiro é ir do zero ao sistema local, o navegador executa as mesmas conversões quando especificamos a propriedade css transform.
img { transform: matrix(a, b, c, d, tx, ty); }
O segundo - para encontrar as coordenadas originais da imagem, já tendo transformadas as coordenadas.
É mais conveniente escrever essas funções usando métodos de uma classe separada.
Classe de transformação: 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){
_Translate método com comentários detalhados: _translate(checkAlign = true){ const tr = new Transform(this.transform.center, this.matrix);
O alinhamento cria o efeito de "colar" a imagem nas bordas do recorte, evitando campos vazios.
Componente de visualização
A principal tarefa desse componente é exibir a imagem, aplicar transformações e responder ao movimento do botão do mouse sobre a imagem. Calculando o deslocamento, atualizamos os parâmetros
transform.x e
transform.y , no final do movimento, acionamos o evento
movido , informando ao componente Edit que precisamos recalcular a posição do centro de transformação e ajustar transform.x e transform.y.
Modelo de componente de visualização:<div ref = "area" class = "zona de edição"
@ mousedown = "onMoveStart"
@ touchstart = "onMoveStart"
mouseup = "onMoveEnd"
@ touchend = "onMoveEnd"
@ mousemove = "onMove"
@ touchmove = "onMove">
<img
v-if = "imagem"
ref = "imagem"
load = "imageLoaded"
: src = "imagem"
: style = "{'transform': transformStyle, 'transform-origin': transformOrigin}">
A funcionalidade do editor é bem separada do projeto principal e está
aqui .
Espero que este material seja útil para você. Obrigada