Verification: 3c65545dd286b716
How To Create A Simple Image Editor On VueJS

Web Design

Project Details

I had the opportunity to write a service for an online store, which would help arrange an order for printing their photos.

The service assumed the existence of a “simple” image editor, the creation of which I would like to share. And all because among the abundance of various plug-ins I have not found a suitable functional, in addition, the nuances of CSS transformations unexpectedly became for me a very nontrivial task.

Main goals:

  1. The ability to download images from the device, Google Drive, and Instagram.
  2. Image editing: moving, rotating, mirroring horizontally and vertically, zooming, automatic image alignment to fill the crop area.

If the topic turns out to be interesting, in the next publication I will describe in detail the integration with Google Drive and Instagram in the back-end part of the application where the popular Node.js + Express bundle was used.

To organize the front-end, I chose the wonderful Vue.js framework. Just because it’s inspired me after an Angular and React.js. I think there is no point in describing architecture, routing and other components, we’ll go directly to the editor.

By the way, the demo of the editor can be checked right here.

We need two components:

  • Edit – will contain the basic logic and controls
  • Preview – will be responsible for displaying the picture

Edit Component Template:

<pre spellcheck="false"><span class="hljs-tag"><<span class="hljs-name">Edit</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">Preview</span> 
        <span class="hljs-attr">v-if</span>=<span class="hljs-string">"image"</span>
        <span class="hljs-attr">ref</span>=<span class="hljs-string">"preview"</span> 
        <span class="hljs-attr">:matrix</span>=<span class="hljs-string">"matrix"</span> 
        <span class="hljs-attr">:image</span>=<span class="hljs-string">"image"</span>
        <span class="hljs-attr">:transform</span>=<span class="hljs-string">"transform"</span> 
        @<span class="hljs-attr">resized</span>=<span class="hljs-string">"areaResized"</span> 
        @<span class="hljs-attr">loaded</span>=<span class="hljs-string">"imageLoaded"</span> 
        @<span class="hljs-attr">moved</span>=<span class="hljs-string">"imageMoved"</span> /></span>
    <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"range"</span> <span class="hljs-attr">:min</span>=<span class="hljs-string">"minZoom"</span> <span class="hljs-attr">:max</span>=<span class="hljs-string">"maxZoom"</span> <span class="hljs-attr">step</span>=<span class="hljs-string">"any"</span> @<span class="hljs-attr">change</span>=<span class="hljs-string">"onZoomEnd"</span> <span class="hljs-attr">v-model.number</span>=<span class="hljs-string">"transform.zoom"</span> <span class="hljs-attr">:disabled</span>=<span class="hljs-string">"!imageReady"</span> /></span>
    <span class="hljs-tag"><<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"rotateMinus"</span> <span class="hljs-attr">:disabled</span>=<span class="hljs-string">"!imageReady"</span>></span>Rotate left<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"rotatePlus"</span> <span class="hljs-attr">:disabled</span>=<span class="hljs-string">"!imageReady"</span>></span>Rotate right<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"flipY"</span> <span class="hljs-attr">:disabled</span>=<span class="hljs-string">"!imageReady"</span>></span>Flip horizontal<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"flipX"</span> <span class="hljs-attr">:disabled</span>=<span class="hljs-string">"!imageReady"</span>></span>Flip vertical<span class="hljs-tag"></<span class="hljs-name">button</span>></span></pre>

The Preview component can trigger 3 events:

  • loaded – image loading event
  • resized – window resizing event
  • moved – image moving event


  • image – link to the image
  • matrix – transformation matrix for the CSS property transform
  • transform – an object that describes transformations

In order to better control the position of the image, img has absolute positioning, and the transform-origin property, the reference point of the transformation, is given the initial value “0 0”, which corresponds to the origin in the upper left corner of the original (before the transformation!) Image.

The main problem I have encountered is to make sure that the transform-origin point is always in the center of the editing area, otherwise, under the transformations, the selected part of the image will be shifted. This task helps to solve the use of the transformation matrix.

The Edit component

<pre spellcheck="false"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    components: {Preview},
    data () {
        <span class="hljs-keyword">return</span> {
            image: <span class="hljs-literal">null</span>,
            imageReady: <span class="hljs-literal">false</span>,
            imageRect: {}, <span class="hljs-comment">// the size of the original image</span>
            areaRect: {}, <span class="hljs-comment">// the size of the crop area</span>
            minZoom: <span class="hljs-number">1</span>, <span class="hljs-comment">// the minimum zoom value</span>
            maxZoom: <span class="hljs-number">1</span>, <span class="hljs-comment">// maximum zoom value</span>
            <span class="hljs-comment">// describe the transformation</span>
            transform: {
                center: {
                x: <span class="hljs-number">0</span>,
                y: <span class="hljs-number">0</span>,
                zoom: <span class="hljs-number">1</span>,
                rotate: <span class="hljs-number">0</span>,
                flip: <span class="hljs-literal">false</span>,
                flop: <span class="hljs-literal">false</span>,
                x: <span class="hljs-number">0</span>,
                y: <span class="hljs-number">0</span>
    computed: {
        matrix () {
            <span class="hljs-keyword">let</span> scaleX = <span class="hljs-keyword">this</span>.transform.flop? -<span class="hljs-keyword">this</span>.transform.zoom: <span class="hljs-keyword">this</span>.transform.zoom;
            <span class="hljs-keyword">let</span> scaleY = <span class="hljs-keyword">this</span>.transform.flip? -<span class="hljs-keyword">this</span>.transform.zoom: <span class="hljs-keyword">this</span>.transform.zoom;
            <span class="hljs-keyword">let</span> tx = <span class="hljs-keyword">this</span>.transform.x;
            <span class="hljs-keyword">let</span> ty = <span class="hljs-keyword">this</span>.transform.y;
            <span class="hljs-keyword">const</span> cos = <span class="hljs-built_in">Math</span>.cos (<span class="hljs-keyword">this</span>.transform.rotate * <span class="hljs-built_in">Math</span>.PI / <span class="hljs-number">180</span>);
            <span class="hljs-keyword">const</span> sin = <span class="hljs-built_in">Math</span>.sin (<span class="hljs-keyword">this</span>.transform.rotate * <span class="hljs-built_in">Math</span>.PI / <span class="hljs-number">180</span>);
            <span class="hljs-keyword">let</span> a = <span class="hljs-built_in">Math</span>.round (cos) * scaleX;
            <span class="hljs-keyword">let</span> b = <span class="hljs-built_in">Math</span>.round (sin) * scaleX;
            <span class="hljs-keyword">let</span> c = -<span class="hljs-built_in">Math</span>.round (sin) * scaleY;
            <span class="hljs-keyword">let</span> d = <span class="hljs-built_in">Math</span>.round (cos) * scaleY;
            <span class="hljs-keyword">return</span> {a, b, c, d, tx, ty};

The imageRect and areaRect values are passed to the Preview component by calling the imageLoaded and areaResized methods, respectively, the objects have the structure:

<pre spellcheck="false">{
  <span class="hljs-attribute">size</span>: { width: <span class="hljs-number">100</span>, height: <span class="hljs-number">100</span> },
  center: { <span class="hljs-attribute">x</span>: <span class="hljs-number">50</span>, y: <span class="hljs-number">50</span> }
<pre spellcheck="false">}</pre>

For those to whom it sounds interested please follow this link for more detail description!

Launch Project
  • Category :

    Web Design

  • Date :

    Mar . 09 . 2019

  • Address :

    Client :

    How To Create A Simple Image Editor On VueJS

  • DIYme
error: Content is protected !!