Categories
Vue

Vue Konva — Animation and Caching

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.

Animation

We can animation shapes with the Konva.Animation constructor.

For example, we can write:

<template>
  <v-stage ref="stage" :config="stageSize">
    <v-layer ref="layer">
      <v-rect
        ref="rect"
        @dragstart="changeSize"
        @dragend="changeSize"
        :config="{
          width: 50,
          height: 50,
          fill: 'green',
          draggable: true,
        }"
      />
      <v-regular-polygon
        ref="octagon"
        :config="{
          x: 200,
          y: 200,
          sides: 8,
          radius: 20,
          fill: 'red',
          stroke: 'black',
          strokeWidth: 4,
        }"
      />
    </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,
      },
    };
  },
  methods: {
    changeSize(e) {
      e.target.to({
        scaleX: Math.random() + 0.8,
        scaleY: Math.random() + 0.8,
        duration: 0.2,
      });
    },
  },
  mounted() {
    const vm = this;
    const amplitude = 100;
    const period = 5000;
    const centerX = vm.$refs.stage.getNode().getWidth() / 2;
    const octagon = this.$refs.octagon.getNode();
    const anim = new Konva.Animation((frame) => {
      octagon.setX(
        amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX
      );
    }, octagon.getLayer());
    anim.start();
  },
};
</script>

We call the Konva.Animation constructor with a callback to change the x coordinate of the item with setX .

Then we call anim.start to start the animation.

The changeSize method is called when we’re dragging the rectangle.

It changes the position when we drag on the object.

Cache

We can cache the items rendered with the cache method.

For example, we can write:

<template>
  <div>
    <v-stage ref="stage" :config="stageConfig">
      <v-layer ref="layer">
        <v-group ref="group">
          <v-star
            v-for="item in list"
            :key="item.id"
            :config="{
              x: item.x,
              y: item.y,
              rotation: item.rotation,
              id: item.id,
              numPoints: 5,
              innerRadius: 30,
              outerRadius: 50,
              fill: 'lightgreen',
              opacity: 0.8,
              shadowColor: 'black',
              shadowBlur: 10,
              shadowOpacity: 0.6,
              scaleX: item.scale,
              scaleY: item.scale,
            }"
          />
        </v-group>
      </v-layer>
    </v-stage>
    <div class="cache">
      <input type="checkbox" @change="handleCacheChange" /> cache shapes
    </div>
  </div>
</template>

<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
  data() {
    return {
      list: [],
      dragItemId: null,
      stageConfig: {
        width: width,
        height: height,
        draggable: true,
      },
    };
  },
  methods: {
    handleCacheChange(e) {
      const shouldCache = e.target.checked;
      if (shouldCache) {
        this.$refs.group.getNode().cache();
      } else {
        this.$refs.group.getNode().clearCache();
      }
    },
  },
  mounted() {
    for (let n = 0; n < 300; n++) {
      this.list.push({
        id: n.toString(),
        x: Math.random() * width,
        y: Math.random() * height,
        rotation: Math.random() * 180,
        scale: Math.random(),
      });
    }
  },
};
</script>

<style>
body {
  margin: 0;
  padding: 0;
}

.cache {
  position: absolute;
  top: 0;
  left: 0;
}
</style>

to add 300 stars.

In the handleCacheChange method, we call cache on the v-group ‘s ref to cache the content if the checkbox is checked.

Otherwise, we clear the cache with clearCache .

Conclusion

We can animate shapes and cache them with Vue Konva.

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
JavaScript

Electron — Architecture and Notifications

Electron is a framework that lets us create cross-platform desktop apps.

The apps are created by creating web apps that are wrapped with a wrapper.

In this article, we’ll look at the architecture and notifications of an Electron app.

Main and Renderer Processes

An electron app runs on a main and rendered process.

The main process creates the GUI.

The rendered process is the process that Chromium runs on to render the content.

Electron has the power to use Node APIs to allow lower-level operating system operations.

The main process creates a web page with a BrowserWindow instance.

The BrowserWindow instance runs on its own rendered process.

When the BrowserWindow instance is destroyed, the rendered process is also terminated.

Using Electron APIs

We can use Electron’s APIs to do some operations.

For instance, we would use the BrowserWindow API to create the window.

Our Electron project has a main.js file to draw the window:

const { app, BrowserWindow } = require('electron')

function createWindow() {
  const win = new BrowserWindow()
  win.loadFile('index.html')
}

app.whenReady().then(createWindow)

We create a BrowserWindow instance and call loadFile on it to load a web page in the window.

Node.js APIs

We can use Node.js APIs in addition to Electron APIs.

For instance, we can use the fs module by writing:

const { app, BrowserWindow } = require('electron')
const fs = require('fs')
const text = fs.readFileSync('./foo.txt')

function createWindow() {
  const win = new BrowserWindow()
  console.log(text.toString())
  win.loadFile('index.html')
}

app.whenReady().then(createWindow)

We called readFileSync to read a foo.txt file and the content will show up in the console log.

Using Native Node Modules

We can use native Node modules if we compile them against the V8 version of the Node binary that’s installed in our system.

Otherwise, we’ll get errors because of incompatible V8 versions.

We can use the electron-rebuild package to build our native modules.

To use it, we install the package by running:

npm install --save-dev electron-rebuild

Then we can run:

./node_modules/.bin/electron-rebuild

to rebuild native modules.

We can also run:

.node_modules.binelectron-rebuild.cmd

on Windows to do the same thing.

We can also set some environment variables to install NPM modules directly.

For instance, we can write:

export npm_config_target=1.2.3
export npm_config_arch=x64
export npm_config_target_arch=x64
export npm_config_disturl=https://electronjs.org/headers
export npm_config_runtime=electron
export npm_config_build_from_source=true
HOME=~/.electron-gyp npm install

We set the Electron’s version in the first line.

The architecture is set in the 2nd and 3rd line

The headers for Electron are downloaded by setting the npm_config_disturl environment variable.

Then we tell node-pre-gyp to build for Electron and install all dependencies in the last 3 lines.

Add Notifications

We can add notifications in our Electron app by creating one with the Notification constructor.

The constructor can only be run in the renderer process, so we should put it in our web page.

For example, we can write:

<!DOCTYPE html>
<html><head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
  <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
  <meta http-equiv="Content-Security-Policy"
    content="script-src 'self' 'unsafe-inline';" />
</head>
<body>
  <h1>Hello World!</h1>
  We are using node
  <script>document.write(process.versions.node)</script>,
  Chrome
  <script>document.write(process.versions.chrome)</script>,
  and Electron
  <script>document.write(process.versions.electron)</script>. 
  <script>
    const notification = new Notification('Title', {
      body: 'hello world'
    }) 

    notification.onclick = () => {
      console.log('Notification clicked')
    }
  </script>
</body>
</html>

We created the notification object with the Notification constructor.

The first argument is the title.

The 2nd is an object with the body property which has the content.

onclick lets us handle clicks on the notification.

More advanced notifications like toasts and tile notifications are also available.

Conclusion

We can compile native modules and use them in our Node apps.