Categories
Vue

Vue Konva — Saving and Loading Canvas and Transform Shapes

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.

Categories
Vue

Vue Konva - Events, Images, and Filters

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.

Events

We can listen to events from input devices easily with Vue Konva.

For example, we can write:

<template>
  <v-stage ref="stage" :config="stageSize">
    <v-layer ref="layer">
      <v-circle
        @mousemove="handleMouseMove"
        @mouseout="handleMouseOut"
        :config="configCircle"
      />
      <v-text
        ref="text"
        :config="{
          x: 10,
          y: 10,
          fontFamily: 'Calibri',
          fontSize: 24,
          text: text,
          fill: 'black',
        }"
      />
    </v-layer>
  </v-stage>
</template>

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

export default {
  data() {
    return {
      stageSize: {
        width: width,
        height: height,
      },
      text: "",
      configCircle: {
        x: 200,
        y: 200,
        radius: 70,
        fill: "red",
        stroke: "black",
        strokeWidth: 4,
      },
    };
  },
  methods: {
    writeMessage(message) {
      this.text = message;
    },
    handleMouseOut(event) {
      this.writeMessage("Mouseout circle");
    },
    handleMouseMove(event) {
      const mousePos = this.$refs.stage.getNode().getPointerPosition();
      const x = mousePos.x - 190;
      const y = mousePos.y - 40;
      this.writeMessage(`(${x}, ${y})`);
    },
  },
};
</script>

We added a circle and listen to the mouseover and mouseout events of the circle.

In the handleMouseMove method, we get the v-stage ‘s ref and get the mouse position from it.

Then we can this.writeMessage to set the text reactive property, which is used in the v-text component.

We also use the handleMouseOut method to listen to mouseout events.

Images

We can add images into the canvas with the v-image component.

For example, we can write:

<template>
  <v-stage ref="stage" :config="stageSize">
    <v-layer ref="layer">
      <v-image
        :config="{
          image,
        }"
      />
    </v-layer>
  </v-stage>
</template>

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

export default {
  data() {
    return {
      stageSize: {
        width,
        height,
      },
      image: null,
    };
  },
  created() {
    const image = new window.Image();
    image.src =
      "https://i.picsum.photos/id/100/200/200.jpg?hmac=-Ffd_UnIv9DLflvK15Fq_1gRuN8t2wWU4UiuwAu4Rqs";
    image.onload = () => {
      this.image = image;
    };
  },
};
</script>

to add our image.

We set the stageSize to the window’s height and width.

And we create an Image instance in the created hook to load the image.

The config is set to the image we want to display.

Filters

We can add filters as a background of shapes.

For example, we can write:

<template>
  <v-stage ref="stage" :config="stageSize">
    <v-layer ref="layer">
      <v-circle
        ref="circle"
        [@mousemove](https://medium.com/r/?url=http%3A%2F%2Ftwitter.com%2Fmousemove "Twitter profile for @mousemove")="handleMouseMove"
        :config="{
          filters,
          noise: 1,
          x: 40,
          y: 40,
          width: 50,
          height: 50,
          fill: color,
          shadowBlur: 10,
        }"
      />
    </v-layer>
  </v-stage>
</template>

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

export default {
  data() {
    return {
      stageSize: {
        width: width,
        height: height,
      },
      color: "green",
      filters: [Konva.Filters.Noise],
    };
  },
  methods: {
    handleMouseMove() {
      this.color = Konva.Util.getRandomColor();
    },
  },
  mounted() {
    const circleNode = this.$refs.circle.getNode();
    circleNode.cache();
    circleNode.getLayer().batchDraw();
  },
  updated() {
    const circleNode = this.$refs.circle.getNode();
    circleNode.cache();
  },
};
</script>

We added our v-circle component.

The filters reactive property has the array of filters we want to set.

Then we listen to the mousemove event on it by setting the handleMouseMove method as the mousemove event handler.

In the handleMouseMove method, we set the color reactive property to a random color.

We cache the circle in the updated hook.

And draw the circle in the mounted hook.

Now when we move our mouse over the circle, we see the color of the filter change.

Conclusion

We can listen to events and add images and filters easily with Vue Konva.

Categories
Vue

Work with the Canvas Easily in Vue Apps with Vue Konva

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.

Installation

We install the Vue Konva library by running:

npm install vue-konva konva --save

Then we register the plugin by writing:

import Vue from "vue";
import App from "./App.vue";
import VueKonva from "vue-konva";

Vue.use(VueKonva);
Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App)
}).$mount("#app");

in main.js

Simple Shapes

Now we can use it to add some shapes.

For example, we can add some simple shapes to our canvas by writing:

<template>
  <v-stage :config="configKonva">
    <v-layer>
      <v-circle :config="configCircle"></v-circle>
    </v-layer>
  </v-stage>
</template>

<script>
export default {
  data() {
    return {
      configKonva: {
        width: 400,
        height: 400,
      },
      configCircle: {
        x: 200,
        y: 200,
        radius: 70,
        fill: "red",
        stroke: "black",
        strokeWidth: 4,
      },
    };
  },
};
</script>

We add the v-stage component to house the canvas content.

Then we add the v-circle in the v-layer and we configure them with the settings.

The config prop has the config.

width and height have the width and height of the canvas.

x and y are the x and y coordinates of the center of the circle.

radius is the radius.

fill is the background color of the circle.

stroke has the border color.

strokeWidth has the border width.

We can also use Vue Konva and Konva from the CDN:

<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
  </head>
  <body>
    <div id="app">
      <v-stage ref="stage" :config="configKonva">
        <v-layer ref="layer">
          <v-circle :config="configCircle"></v-circle>
        </v-layer>
      </v-stage>
    </div>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/konva@4.0.0/konva.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-konva@2.1.6/umd/vue-konva.min.js"></script>
    <script>
      new Vue({
        el: "#app",
        data: {
          configKonva: {
            width: 400,
            height: 400
          },
          configCircle: {
            x: 200,
            y: 200,
            radius: 70,
            fill: "red",
            stroke: "black",
            strokeWidth: 4
          }
        }
      });
    </script>
  </body>
</html>

We don’t have to register the plugin as we did with the NPM version.

The rest of the code is the same.

Core Shapes

Vue Konva comes with some prebuilt shapes.

They include v-rect, v-circle, v-ellipse, v-line, v-image, v-text, v-text-path, v-star, v-label, v-path, and v-regular-polygon.

For example, we can add some text and a rectangle by writing:

<template>
  <v-stage :config="configKonva">
    <v-layer>
      <v-text :config="{ text: 'Some text', fontSize: 15 }" />
      <v-rect
        :config="{
          x: 20,
          y: 50,
          width: 120,
          height: 120,
          fill: 'red',
          shadowBlur: 10,
        }"
      />
    </v-layer>
  </v-stage>
</template>

<script>
export default {
  data() {
    return {
      configKonva: {
        width: 400,
        height: 400,
      },
    };
  },
};
</script>

For example, we can write:

<template>
  <v-stage :config="configKonva">
    <v-layer>
      <v-line
        :config="{
          x: 20,
          y: 200,
          points: [0, 0, 220, 0, 160, 100],
          tension: 0.5,
          closed: true,
          stroke: 'black',
          fillLinearGradientStartPoint: { x: -50, y: -50 },
          fillLinearGradientEndPoint: { x: 50, y: 50 },
          fillLinearGradientColorStops: [0, 'green', 1, 'yellow'],
        }"
      />
    </v-layer>
  </v-stage>
</template>

<script>
export default {
  data() {
    return {
      configKonva: {
        width: 400,
        height: 400,
      },
    };
  },
};
</script>

to use the v-line component to make our own shape with the points array having the x and y coordinates of the points.

fillLinearGradientStartPoint has the gradient start coordinates.

fillLinearGradientEndPoint has the gradient end coordinates.

fillLinearGradientColorStops has the colors for the start and end of the gradient.

Conclusion

We can add basic shapes to tyhe HTNML canvas easily in a Vue app with Vue Konva.

Categories
Vue Ionic

Mobile Development with Ionic and Vue — Footer, Button Group, and Text

If we know how to create Vue web apps but want to develop mobile apps, we can use the Ionic framework.

In this article, we’ll look at how to get started with mobile development with the Ionic framework with Vue.

Footer

We can add a footer with Ionic Vue.

To do that, we use the ion-footer component:

<template>
  <ion-page>
    <ion-content></ion-content>
    <ion-footer>
      <ion-toolbar>
        <ion-title>Footer</ion-title>
      </ion-toolbar>
    </ion-footer>
  </ion-page>
</template>

<script lang='ts'>
import {
  IonContent,
  IonFooter,
  IonTitle,
  IonToolbar,
  IonPage,
} from "@ionic/vue";
import { defineComponent } from "vue";

export default defineComponent({
  components: { IonContent, IonFooter, IonTitle, IonToolbar, IonPage },
});
</script>

We can also add one with no border:

<template>
  <ion-page>
    <ion-content></ion-content>
    <ion-footer class="ion-no-border">
      <ion-toolbar>
        <ion-title>Footer - No Border</ion-title>
      </ion-toolbar>
    </ion-footer>
  </ion-page>
</template>

<script lang='ts'>
import {
  IonContent,
  IonFooter,
  IonTitle,
  IonToolbar,
  IonPage,
} from "@ionic/vue";
import { defineComponent } from "vue";

export default defineComponent({
  components: { IonContent, IonFooter, IonTitle, IonToolbar, IonPage },
});
</script>

Then ion-content component holds the content on top.

The ion-title component has the title for the footer.

Button Group

We can add button groups with the ion-buttons component.

For example, we can write:

<template>
  <ion-page>
    <ion-toolbar>
      <ion-buttons slot="primary">
        <ion-button @click="clickedStar()">
          <ion-icon slot="icon-only" name="star"></ion-icon>
        </ion-button>
      </ion-buttons>
      <ion-title>Right side menu toggle</ion-title>
      <ion-buttons slot="end">
        <ion-menu-button auto-hide="false"></ion-menu-button>
      </ion-buttons>
    </ion-toolbar>
  </ion-page>
</template>

<script>
import {
  IonBackButton,
  IonButton,
  IonButtons,
  IonIcon,
  IonMenuButton,
  IonTitle,
  IonToolbar,
  IonPage
} from "@ionic/vue";
import { personCircle, search } from "ionicons/icons";
import { defineComponent } from "vue";

export default defineComponent({
  components: {
    IonBackButton,
    IonButton,
    IonButtons,
    IonIcon,
    IonMenuButton,
    IonTitle,
    IonToolbar,
    IonPage
  },
  setup() {
    const clickedStar = () => {
      console.log("Star clicked!");
    };
    return { personCircle, search, clickedStar };
  },
});
</script>

to add the ion-buttons component.

Then we can add the ion-button components inside to add the buttons.

ion-button emits the click event so we can run something when we click it.

Text

We can add text into our app with the ion-text component.

For example, we can write:

<template>
  <ion-page>
    <ion-text color="danger">
      <h4>H4: The quick brown fox jumps over the lazy dog</h4>
    </ion-text>
  </ion-page>
</template>

<script>
import { IonPage, IonText } from "@ionic/vue";
import { defineComponent } from "vue";

export default defineComponent({
  components: {
    IonPage,
    IonText,
  },
});
</script>

to add the text.

color has the color of the text.

Conclusion

We can add a footer, button group and text with Ionic Vue.

Categories
Vue Ionic

Mobile Development with Ionic and Vue — Toggle, Toolbar, and Header

If we know how to create Vue web apps but want to develop mobile apps, we can use the Ionic framework.

In this article, we’ll look at how to get started with mobile development with the Ionic framework with Vue.

Toggle Switches

We can add toggle switches with the ion-toggle component.

For example, we can add switches with various colors:

<template>
  <div>
    <ion-toggle color="primary"></ion-toggle>
    <ion-toggle color="secondary"></ion-toggle>
    <ion-toggle color="danger"></ion-toggle>
    <ion-toggle color="light"></ion-toggle>
    <ion-toggle color="dark"></ion-toggle>
  </div>
</template>

<script>
import { IonLabel, IonList, IonItem, IonToggle } from "@ionic/vue";
import { defineComponent, ref, vue } from "vue";

export default defineComponent({
  components: { IonLabel, IonList, IonItem, IonToggle }
});
</script>

Also, we can write:

<template>
  <ion-item>
    <ion-label>Mushrooms</ion-label>
    <ion-toggle
      @ionChange="checked.value = !checked.value"
      value="mushrooms"
      :checked="checked"
    >
    </ion-toggle>
  </ion-item>
</template>

<script>
import { IonLabel, IonList, IonItem, IonToggle } from "@ionic/vue";
import { defineComponent, ref, vue } from "vue";

export default defineComponent({
  components: { IonLabel, IonList, IonItem, IonToggle },
  setup() {
    const checked = ref(false);
    return { checked };
  },
});
</script>

to set a reactive property when we toggle the value.

Toolbar

We can add a toolbar with the ion-toolbar component.

For instance, we can write:

<template>
  <ion-toolbar color="dark">
    <ion-buttons slot="secondary">
      <ion-button>
        <ion-icon slot="icon-only" :icon="personCircle"></ion-icon>
      </ion-button>
      <ion-button>
        <ion-icon slot="icon-only" :icon="search"></ion-icon>
      </ion-button>
    </ion-buttons>
    <ion-buttons slot="primary">
      <ion-button color="danger">
        <ion-icon
          slot="icon-only"
          :ios="ellipsisHorizontal"
          :md="ellipsisVertical"
        ></ion-icon>
      </ion-button>
    </ion-buttons>
    <ion-title>Dark Toolbar</ion-title>
  </ion-toolbar>
</template>

<script>
import {
  IonButton,
  IonButtons,
  IonIcon,
  IonTitle,
  IonToolbar,
} from "@ionic/vue";
import {
  ellipsisHorizontal,
  ellipsisVertical,
  helpCircle,
  personCircle,
  search,
  star,
} from "ionicons/icons";
import { defineComponent } from "vue";

export default defineComponent({
  components: {
    IonButton,
    IonButtons,
    IonIcon,
    IonTitle,
    IonToolbar,
  },
  setup() {
    return {
      ellipsisHorizontal,
      ellipsisVertical,
      helpCircle,
      personCircle,
      search,
      star,
    };
  },
});
</script>

to add a dark toolbar with some buttons inside it.

ion-title has the title of the toolbar, which is displayed on the left side.

Header

We can add a header with various styles.

For example, we can write:

<template>
  <ion-header>
    <ion-toolbar>
      <ion-buttons slot="start">
        <ion-back-button></ion-back-button>
      </ion-buttons>
      <ion-title>Navigation Bar</ion-title>
    </ion-toolbar>

    <ion-toolbar>
      <ion-title>Subheader</ion-title>
    </ion-toolbar>
  </ion-header>

  <!-- Header without a border -->
  <ion-header class="ion-no-border">
    <ion-toolbar>
      <ion-title>Header - No Border</ion-title>
    </ion-toolbar>
  </ion-header>
</template>

<script>
import {
  IonBackButton,
  IonButtons,
  IonContent,
  IonHeader,
  IonTitle,
  IonToolbar
} from '@ionic/vue';
import { defineComponent } from 'vue';

export default defineComponent({
  components: {
    IonBackButton,
    IonButtons,
    IonContent,
    IonHeader,
    IonTitle,
    IonToolbar
  }
});
</script>

to add the header with a toolbar inside.

We can add the ion-no-border class to remove the border from the header.

Conclusion

We can add a toggle switch, toolbar and header with Ionic Vue.