Categories
Vue

Vue Konva — Saving and Loading Canvas and Transform Shapes

Spread the love

We can make working with the HTML canvas easier in Vue apps with the Vue Konva library.

In this article, we’ll take a look at how to use Vue Konva to make working with the HTML canvas easier in a Vue app.

Saving and Loading Canvas

We can save the canvas content easily so that it’ll be kept when we reload the page.

For example, we can write:

<template>
  <div>
    <a href=".">Reload the page</a>.
    <v-stage ref="stage" :config="stageSize" @click="handleClick">
      <v-layer ref="layer">
        <v-circle
          v-for="item in list"
          :key="item.id"
          :config="{
            x: item.x,
            y: item.y,
            radius: 50,
            fill: 'green',
          }"
        ></v-circle>
      </v-layer>
      <v-layer ref="dragLayer"></v-layer>
    </v-stage>
  </div>
</template>

<script>
const width = window.innerWidth;
const height = window.innerHeight;

export default {
  data() {
    return {
      list: [{ x: 100, y: 100, radius: 50, fill: "blue" }],
      stageSize: {
        width: width,
        height: height,
      },
    };
  },
  methods: {
    handleClick(evt) {
      const stage = evt.target.getStage();
      const pos = stage.getPointerPosition();
      this.list.push(pos);
      this.save();
    },

    load() {
      const data = localStorage.getItem("storage") || "[]";
      this.list = JSON.parse(data);
    },

    save() {
      localStorage.setItem("storage", JSON.stringify(this.list));
    },
  },
  mounted() {
    this.load();
  },
};
</script>

We have the handleClick method that adds a circle when we click on the stage.

The list reactive property has a list of circles to render.

Then in the save method, we saver the canvas by saving the list value into local storage.

Then we can load that in the load method by parsing it.

The list reactive property is loaded with v-for in the template to recreate the circles.

Drag and Drop

We can add drag and drop easily with Vue Konva.

All we have to do is listen to the dragstart and dragend events in our shapes.

For example, we can write:

<template>
  <v-stage ref="stage" :config="stageSize">
    <v-layer ref="layer">
      <v-circle
        @dragstart="handleDragStart"
        @dragend="handleDragEnd"
        :config="{
          x: 200,
          y: 200,
          radius: 70,
          draggable: true,
          fill: isDragging ? 'green' : 'black',
        }"
      />
    </v-layer>
  </v-stage>
</template>

<script>
const width = window.innerWidth;
const height = window.innerHeight;

export default {
  data() {
    return {
      stageSize: {
        width,
        height,
      },
      isDragging: false,
    };
  },
  methods: {
    handleDragStart() {
      this.isDragging = true;
    },
    handleDragEnd() {
      this.isDragging = false;
    },
  },
};
</script>

We set the isDragging reactive property when we’re dragging.

This changes the fill.

Also, we set the draggable property to true to make the circle draggable.

Resizing Shapes

We can resize shapes easily with the v-transformer component.

For example, we can write:

<template>
  <v-stage
    ref="stage"
    :config="stageSize"
    @mousedown="handleStageMouseDown"
    @touchstart="handleStageMouseDown"
  >
    <v-layer ref="layer">
      <v-circle
        v-for="item in circles"
        :key="item.id"
        :config="item"
        @transformend="handleTransformEnd"
      />
      <v-transformer ref="transformer" />
    </v-layer>
  </v-stage>
</template>

<script>
import Konva from "konva";
const width = window.innerWidth;
const height = window.innerHeight;

export default {
  data() {
    return {
      stageSize: {
        width: width,
        height: height,
      },
      circles: [
        {
          rotation: 0,
          x: 60,
          y: 60,
          width: 100,
          height: 100,
          scaleX: 1,
          scaleY: 1,
          fill: "red",
          name: "circ1",
          draggable: true,
        },
        {
          rotation: 0,
          x: 150,
          y: 150,
          width: 100,
          height: 100,
          scaleX: 1,
          scaleY: 1,
          fill: "green",
          name: "circ2",
          draggable: true,
        },
      ],
      selectedShapeName: "",
    };
  },
  methods: {
    handleTransformEnd(e) {
      const rect = this.circles.find((r) => r.name === this.selectedShapeName);
      rect.x = e.target.x();
      rect.y = e.target.y();
      rect.rotation = e.target.rotation();
      rect.scaleX = e.target.scaleX();
      rect.scaleY = e.target.scaleY();
      rect.fill = Konva.Util.getRandomColor();
    },
    handleStageMouseDown(e) {
      if (e.target === e.target.getStage()) {
        this.selectedShapeName = "";
        this.updateTransformer();
        return;
      }

      const clickedOnTransformer =
        e.target.getParent().className === "Transformer";
      if (clickedOnTransformer) {
        return;
      }
      const name = e.target.name();
      const rect = this.circles.find((r) => r.name === name);
      if (rect) {
        this.selectedShapeName = name;
      } else {
        this.selectedShapeName = "";
      }
      this.updateTransformer();
    },
    updateTransformer() {
      const transformerNode = this.$refs.transformer.getNode();
      const stage = transformerNode.getStage();
      const { selectedShapeName } = this;

      const selectedNode = stage.findOne(`.${selectedShapeName}`);
      if (selectedNode === transformerNode.node()) {
        return;
      }

      if (selectedNode) {
        transformerNode.nodes([selectedNode]);
      } else {
        transformerNode.nodes([]);
      }
      transformerNode.getLayer().batchDraw();
    },
  },
};
</script>

We start with the mousedown handler, which is called when we click on the shape.

The handleStageMouseDown method is the mousedown handler.

We get the state with the getStage method.

If we found the stage, then we call updateTransformer to remove any selections

After that, we get the state by the name.

And then we call updateTransformer to select the shape.

Then when we drag the handles, the handleTransformEnd method is called.

We get all the event data from the e parameter and update the object that’s found by its name.

Conclusion

We can save and load canvas and transform shapes with Vue Konva.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *