working with vue-konva (svg based canvas library) right now. I'm trying to animate all shapes which are defined by a v-loop. While trying to use the Konva Animation library functions, I'm getting "Cannot read property 'getNode' of undefined" error. I'm assuming this is due to the fact that a ref has to have one specific element and not be adressed inside a v-for loop. How can I simultaneaously animate all the polygons?
SVG / canvas element:
<v-regular-polygon
v-for="el in results"
ref="hexagon"
:key="el.index"
:config="{
x: 200 * Math.abs(el.land),
y: 200,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
}"
/>
function responsible for animation
mounted() {
this.fetchTemperature()
const vm = this
const amplitude = 100
const period = 5000
// in ms
const centerX = vm.$refs.stage.getNode().getWidth() / 2
const hexagon = this.$refs.hexagon.getNode()
// example of Konva.Animation
const anim = new Konva.Animation(function (frame) {
hexagon.setX(amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX)
}, hexagon.getLayer())
anim.start()
},
You can set a unique ref to every polygon by adding an index.
<v-regular-polygon
v-for="(el, i) in results"
:ref="`hexagon_${i}`"
...
Here's an example:
<template>
<div id="app">
<v-stage :config="configKonva">
<v-layer>
<v-circle
v-for="(item, i) in items"
:config="item"
:key="i"
:ref="`circle_${i}`"
></v-circle>
</v-layer>
</v-stage>
</div>
</template>
<script>
import Konva from "konva";
export default {
name: "App",
data() {
return {
items: [
{
x: 30,
y: 100,
radius: 20,
fill: "red",
stroke: "black",
strokeWidth: 2,
},
{
x: 100,
y: 100,
radius: 20,
fill: "red",
stroke: "black",
strokeWidth: 2,
},
],
configKonva: {
width: 200,
height: 200,
},
};
},
mounted() {
for (let i = 0; i < this.items.length; i++) {
const node = this.$refs[`circle_${i}`][0].getNode();
const period = 1000;
const amplitude = 10;
const anim = new Konva.Animation((frame) => {
node.setX(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
this.items[i].x
);
}, node.getLayer());
anim.start();
}
},
};
</script>
Codesandbox
Related
I followed the documentation and Github I did the following steps:
install vue-konva and konva and canvas using npm install vue-konva konva --save and npm install canvas --save.
Created vuekonva.js under plugins folder with below content:
import Vue from 'vue'
import VueKonva from 'vue-konva'
Vue.use(VueKonva)
Added plugins: [ "~/plugins/vuekonva"], under nuxt.config.js
I tried adding under nuxt-config.js but still no luck:
build: {
standalone: true
},
Created a page under pages folder and added code from documenation:
<template>
<div>
<v-stage ref="stage" :config="stageSize">
<v-layer>
<v-text :config="{ text: 'Some text on canvas', fontSize: 15 }" />
<v-rect
:config="{
x: 20,
y: 50,
width: 100,
height: 100,
fill: 'red',
shadowBlur: 10,
}"
/>
<v-circle
:config="{
x: 200,
y: 100,
radius: 50,
fill: 'green',
}"
/>
<v-line
:config="{
x: 20,
y: 200,
points: [0, 0, 100, 0, 100, 100],
tension: 0.5,
closed: true,
stroke: 'black',
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow'],
}"
/>
</v-layer>
<v-layer ref="dragLayer" />
</v-stage>
</div>
</template>
<script>
export default {
data () {
return {
stageSize: {
width,
height
}
}
},
mounted () {
if (process.browser) {
this.stageSize.width = window.innerWidth
this.stageSize.height = window.innerHeight
}
}
}
</script>
I get the error:
Must use import to load ES Module:
I tried without plugins and it is throwing the error:
vue.runtime.esm.js:620 [Vue warn]: Unknown custom element: <v-stage> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
Not understanding whats the issue please help.
According to Nuxt documentation some plugins export an ES6 module. I think this is the case for konva node module. I followed the steps you mentioned above. But in the nuxt.config.js file I configured the plugin as follow:
plugins: [
{ src: "~/plugins/vuekonva", mode: 'client' }
],
build: {
transpile: ['konva']
},
After that I replaced the code of your page with the code of konvajs as follows:
<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: 200,
height: 200
},
configCircle: {
x: 100,
y: 100,
radius: 70,
fill: "red",
stroke: "black",
strokeWidth: 4
}
};
}
};
</script>
That is working for me when I link to the page with nuxt-link. but if I refresh the page I get some errors that is maybe for SSR. I am not sure but if you read this documentation you maybe could solve the problem for SSR.
I'm new to Vue.js and I learned some basic skills. Right now I'm trying to solve real problems with it.
I'm trying to draw some donuts with Vue.js. I can make it without Vue easily, but it confuse me while I'm trying to use Vue.js.
/*
canvas: HTMLCanvas node
ratio: number range: [0, 1]
*/
function pie (canvas, ratio) {
function arc (ctx, color, radius, radian) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, radius, 0, radian);
ctx.closePath();
ctx.fill();
}
var width = canvas.clientWidth,
height = canvas.clientHeight,
outRadius = Math.ceil(width / 2 - 10),
innerRadius = Math.ceil(outRadius * 0.8);
var ctx = canvas.getContext("2d");
ctx.save();
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.translate(width / 2, height / 2);
ctx.rotate(-Math.PI / 2);
arc(ctx, "steelblue", outRadius, Math.PI * 2);
arc(ctx, "yellow", outRadius + 5, Math.PI * 2 * ratio);
arc(ctx, "white", innerRadius, Math.PI * 2);
ctx.restore();
}
Vue.component("pie", {
props: ["pies"],
methods: {
draw: pie
},
template: `
<ul class="pie">
<li v-for="pie in pies">
<div class="pie__content">
<h3 class="pie__header">{{pie.ratio}}</h3>
<canvas v-on:click="draw($event.target, pie.ratio)" width="200" height="200"></canvas>
</div>
<h3 class="pie__name">{{pie.name}}</h3>
</li>
</ul>
`
});
var vm = new Vue({
el: "#app",
data: {
pies: [
{ratio: 0.45, name: "CPU"},
{ratio: 0.75, name: "Memory"},
{ratio: 0.15, name: "Drive"},
]
}
});
canvas { border: 1px solid gray; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<pie v-bind:pies="pies"></pie>
</div>
firstly, I don't know how to initialize those canvas. Run those code above you will find those canvas are blank unless user click on them. Absolutely this is not whta I want, but it seems event is the only way make it work;
secondly, If I change ratio, such as: vm.pies[0].ratio = 0.78, the
related canvas give no response.
Any comments will be appreciated!
What I miss is the Vue Instance Lifecycle. Vue instance offer a mounted property which will run after the component is rendered.
Firstly, I give the canvas element a ref attribute which make it easy refered on late manipulation.
Creating an mounted property function which will run after component is rendered. This could be the initialize function.
Creating an computed getter function formatRatio which will redraw the canvas if availble and finally return the formated ratio string in percent format. This part will make the the pie charts reactive with bind data.
Here is the code:
function pie (canvas, ratio) {
function arc (ctx, color, radius, radian) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, radius, 0, radian);
ctx.closePath();
ctx.fill();
}
var width = canvas.clientWidth,
height = canvas.clientHeight,
outRadius = Math.ceil(width / 2 - 10),
innerRadius = Math.ceil(outRadius * 0.8);
var ctx = canvas.getContext("2d");
ctx.save();
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.translate(width / 2, height / 2);
ctx.rotate(-Math.PI / 2);
arc(ctx, "steelblue", outRadius, Math.PI * 2);
arc(ctx, "yellow", outRadius + 5, Math.PI * 2 * ratio);
arc(ctx, "white", innerRadius, Math.PI * 2);
ctx.restore();
}
Vue.component("pie-canvas", {
props: ["pie"],
computed: {
formatRatio: function () {
// when component is rendered canvas element will be avaible
if (this.$refs.canvas) {
pie(this.$refs.canvas, this.pie.ratio);
}
return Math.round(this.pie.ratio * 1000) / 10 + "%";
}
},
mounted: function () {
pie(this.$refs.canvas, this.pie.ratio);
},
template: `
<div class="pie__content">
<h3 class="pie__header">{{ formatRatio }}</h3>
<canvas ref="canvas" width="200" height="200"></canvas>
</div>
`
});
var vm = new Vue({
el: "#app",
data: {
pies: [
{ratio: 0.45, name: "CPU"},
{ratio: 0.75, name: "Memory"},
{ratio: 0.15, name: "Drive"},
]
}
});
canvas { border: 1px solid gray; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul class="pie">
<li v-for="pie in pies">
<pie-canvas v-bind:pie="pie"></pie-canvas>
<h3 class="pie__name">{{pie.name}}</h3>
</li>
</ul>
</div>
When an element has a computed style, the style changes are not applied if the element is going through a leave transition:
new Vue({
el: "#app",
data: {
selected: 1,
items: [{
color: 'red'
},
{
color: 'blue'
},
{
color: 'green'
},
],
tweened: {
height: 50,
},
},
computed: {
divStyles() {
return {
height: this.tweened.height + 'px',
background: this.displayed.color,
'margin-left': this.selected * 100 + 'px',
width: '100px',
}
},
displayed() {
return this.items[this.selected - 1]
}
},
watch: {
selected(newVal) {
function animate() {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween(this.tweened)
.to({
height: newVal * 50
}, 2000)
.easing(TWEEN.Easing.Quadratic.InOut)
.start()
animate()
}
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
}
}
})
.colored-div {
opacity: 1;
position: absolute;
}
.switcher-leave-to,
.switcher-enter {
opacity: 0;
}
.switcher-enter-to,
.switcher-leave {
opacity: 1;
}
.switcher-leave-active,
.switcher-enter-active {
transition: opacity 5s linear;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.21/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/16.3.5/Tween.min.js"></script>
<div id="app">
<button #click="selected--" :disabled="selected <= 1">
Previous
</button>
<button #click="selected++" :disabled="selected >= 3">
Next
</button>
<span>Selected: {{selected}}</span>
<transition name="switcher">
<div v-for="(item, index) in items" v-if="index + 1 === selected" :key="index" :style="divStyles" class="colored-div" />
</transition>
</div>
https://jsfiddle.net/64syzru5/12/
I would expect the leaving element to continue resizing as it fades out, but it doesn't. What can be done to have the computed styles applied to the leaving element during the leave-active transition?
Since you're using CSS for the transitions, Javascript doesn't execute at each intermediate step. That's a good thing for performance, but it means that the computed properties aren't recomputed. As best as I can tell, though, you're just trying to animate the height. That's easily accomplished in pure CSS. Use a before-leave hook to set it to an initial value via an inline style or CSS variable, and then remove that property in the after-leave hook.
More to the point, though, it looks like your application might be more suitable for a transition-group instead of a simple transition.
Currently I'm using Vue-Konva.js to help me build a 2d editor for panorama.
Does konva.js support GLSL code for apply image effect (ex: stereographic projection)?
Maybe like this format.
KonvaScens.vue
<template>
<v-stage :config="configKonva">
<v-layer>
<v-image :config="configImage"></v-image>
</v-layer>
</v-stage>
</template>
<script>
import {myVertexShader, myFragmentShader} from 'imageShader'
export default {
data () {
return {
testImg: new Image(100, 100),
configKonva: {
width: 500,
height: 500
}
}
},
computed: {
configImage () {
return {
x: 20,
y: 20,
image: this.testImg,
width: 500,
height: 500,
vertexShader: myVertexShader,
fragmentShader: myFragmentShader
}
}
},
mounted () {
this.testImg.src = "https://konvajs.github.io/assets/lion.png"
}
}
</script>
No, vue-konva doesn't support GLSL. GLSL can be used in webgl canvas context. But Konva uses only 2d context.
I want to set the height of a vue component (to be specific it is this -> https://github.com/jbaysolutions/vue-grid-layout).
The height of which (in vue-grid-layout -> grid-item) should be same as of its parent.
And this also should be only in page load time. So how this can be achieved in vue js?
I don't want to do this using CSS. I need height in pixels. as to vue-grid-layout -> item height needs in pixel initially. as it is resizable, it can be changed afterwards.
it would be easier to provide an accurate answer with some example html (your actual code), but the following should work in general.
export default {
...
data() {
return {
parentHeight: 0
}
},
mounted() {
this.parentHeight = this.$parent.$el.offsetHeight;
}
...
}
So in the mounted lifecycle hook you can read the height of the parent and then set it where ever you need it.
No need for advanced javascript to calculate the height, just use styling:
height: 100%;
Demo:
.parent {
resize: both;
overflow: auto;
height: 100px;
display: block;
width: 100px;
border: 1px solid black;
}
.child {
height: 100%;
background: pink;
}
<div class="parent">
</div>
<div class="parent">
<div class="child">
I'm a child!
</div>
</div>
I found a solution to fix the grid-layout height depending on available space. For that I have used folowing props: max-rows, row-height, margins, autoSize=false
And ResizeObserver which will adjust grid-layout row-height according to available height after window resize. Also be aware that I used some Bootstrap classes for styling
<template>
<div class="flex-grow-1 w-100">
<grid-layout
:layout="layout"
:col-num="colCount"
:maxRows="rowCount"
:row-height="rowHeight"
:autoSize="false"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:preventCollision="true"
:vertical-compact="false"
:margin="margin"
:use-css-transforms="true"
>
<grid-item
v-for="item in layout"
class="bg-primary"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
>
<span class="remove" #click="removeItem(item.i)">x</span>
</grid-item>
</grid-layout>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/runtime-core';
interface GridItem {
x: number;
y: number;
w: number;
h: number;
i: string;
}
interface State {
layout: GridItem[];
index: number;
colCount: number;
rowCount: number;
rowHeight: number;
observer: null | ResizeObserver;
margin: number[];
}
export default defineComponent({
name: 'VideoWall',
data(): State {
return {
layout: [
{ x: 0, y: 0, w: 2, h: 2, i: '0' },
{ x: 2, y: 0, w: 2, h: 4, i: '1' },
{ x: 4, y: 0, w: 2, h: 5, i: '2' },
{ x: 6, y: 0, w: 2, h: 3, i: '3' },
{ x: 8, y: 0, w: 2, h: 3, i: '4' },
],
index: 0,
colCount: 36,
rowCount: 36,
rowHeight: 40,
margin: [5, 5],
observer: null,
};
},
mounted() {
this.observer = new ResizeObserver(this.onResize);
if (this.$el instanceof Element) {
this.observer.observe(this.$el);
} else {
console.log('VideoWall: Failed to bind observer');
}
},
unmounted() {
if (this.observer) {
this.observer.disconnect();
}
},
methods: {
onResize(entries: ResizeObserverEntry[]): void {
this.rowHeight =
Math.floor(entries[0].contentRect.height / this.rowCount) -
this.margin[1];
},
},
});
</script>