ThreeJs black screen in VueJs application - vue.js

I have been studying some basics of Three.js into
a Vue.JS application.
I am following a tutorial and the code is the same as the original autor.
A cube geometry was added as a initial test, but my screen canvas goes black, and since i'm new to vuejs I can't find the error.
See some of the code below..
thank you all!
<template>
<div id="space">
<div id="container"></div>
</div>
</template>
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
export default {
name: "space",
data() {
return {
scene: null,
camera: null,
renderer: null,
controls: null,
iR: null,
};
},
mounted() {
let that = this;
this.Awake();
window.addEventListener("resize", onWindowResize, false);
function onWindowResize() {
that.camera.aspect = window.innerWidth / window.innerHeight;
that.camera.updateProjectionMatrix();
that.renderer.setSize(window.innerWidth, window.innerHeight);
}
onWindowResize();
},
methods: {
Awake() {
let container = document.getElementById("container");
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x222222);
this.camera = new THREE.PerspectiveCamera(
25,
window.clientWidth / window.clientHeight,
1,
100
);
this.camera.position.set(8, 4, 0);
this.iR = new THREE.Group();
this.iR = "Interactive Root";
this.scene.add(this.iR);
let light0 = new THREE.AmbientLight(0xfafafa, 0.25);
let light1 = new THREE.PointLight(0xfafafa, 0.4);
light1.position.set(200, 90, 40);
let light2 = new THREE.PointLight(0xfafafa, 0.4);
light2.position.set(200, 90, -40);
this.scene.add(light0);
this.scene.add(light1);
this.scene.add(light2);
let gridHelper = new THREE.GridHelper(
60,
150,
new THREE.Color(0x555555),
new THREE.Color(0x333333)
);
this.scene.add(gridHelper);
//mesh geometry to test
let geometry = new THREE.BoxGeometry(1, 1, 1);
let material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
let mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(this.renderer.domElement);
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.25;
this.controls.screenSpacePanning = false;
this.controls.maxDistance = 800;
this.controls.update();
this.Update();
this.GetGeoJson();
},
Update() {
requestAnimationFrame(this.Update);
this.renderer.render(this.scene, this.camera);
this.controls.update();
},
GetGeoJson() {
fetch("./assets/ufpe.geojson").then((res) => {
res.json().then((data) => {
console.log(data);
//this.LoadBuildings(data)
});
});
},
},
};
</script>
<style scoped>
</style>

Related

Selecting a field in an ArcGis layer/map in a Nuxt application result in adding legend colorSlider on top of each other in the same div

I am using ArcGis in a Nuxt application. I have got a map with a feature layer (hosted on ArcGis) and a legend with a color slider in the top-right corner. The user can visualise different fields from the layer. For each field selected a new renderer is generated and therefore a new colorSlider. My problem is that every time the user select a new field, a new colorSlider is added above the previous one and I end up with three coloSliders in the legend. How can I fix that ?? I tried to destroy the previous colorSlider when I select a new field but it seems to destroy the div which contains the slider and then I have no slider at all anymore... This is the code =>
<template>
<div>
<div id="viewDiv"></div>
<div id="legend"></div>
<div id="containerDiv" class="esri-widget">
<span id="title" class="esri-widget">impact legend</span>
<div id="slider" ref="sliderr"></div>
</div>
</div>
</template>
<script>
import Map from '#arcgis/core/Map'
import MapView from '#arcgis/core/views/MapView'
import esriConfig from '#arcgis/core/config'
import FeatureLayer from '#arcgis/core/layers/FeatureLayer'
import * as colorRendererCreator from '#arcgis/core/smartMapping/renderers/color'
import ColorSlider from '#arcgis/core/widgets/smartMapping/ColorSlider'
export default {
props: {
selectedTab: {
type: Number,
default: 1,
},
},
data() {
return {
url: 'https://blablabla',
countries:
'https://blablabla',
projectLyr: undefined,
countryLyr: undefined,
map: new Map({ basemap: 'osm-light-gray' }),
view: undefined,
fieldName: '',
renderer: {},
filter: '',
rendererResult: undefined,
colorSlider: undefined,
}
},
mounted() {
esriConfig.apiKey =
'myApiKey'
this.projectLyr = new FeatureLayer({
url: this.url,
outFields: ['*'],
})
this.countryLyr = new FeatureLayer({
url: this.countries,
outFields: ['*'],
})
this.view = new MapView({
map: this.map,
center: [15, 50],
zoom: 6,
container: 'viewDiv',
})
this.updateLayer({ layer: this.projectLyr, value: 'Impact_PA_area' })
this.$nuxt.$on('filter-selected', this.updateLayer)
},
beforeDestroy() {
this.$nuxt.$off('tab-selected')
this.$nuxt.$off('filter-selected')
},
methods: {
generateRenderer(lyr) {
const colorParams = {
layer: lyr.layer,
field: `${lyr.field}`,
view: this.view,
theme: 'above-and-below',
}
colorRendererCreator
.createContinuousRenderer(colorParams)
.then((response) => {
// Set the renderer to the layer and add it to the map
this.rendererResult = response
lyr.layer.renderer = this.rendererResult.renderer
})
.then(() => {
// Construct a color slider from the result of smart mapping renderer
this.colorSlider = ColorSlider.fromRendererResult(this.rendererResult)
this.colorSlider.container = 'slider'
this.colorSlider.primaryHandleEnabled = true
this.colorSlider.viewModel.precision = 1
this.view.ui.add('containerDiv', 'top-right')
function changeEventHandler() {
const renderer = lyr.layer.renderer.clone()
const colorVariable = renderer.visualVariables[0].clone()
const outlineVariable = renderer.visualVariables[1]
colorVariable.stops = this.colorSlider.stops
renderer.visualVariables = [colorVariable, outlineVariable]
lyr.layer.renderer = renderer
}
this.colorSlider.on(
['thumb-change', 'thumb-drag', 'min-change', 'max-change'],
changeEventHandler
)
})
.catch((error) => {
console.error('Error: ', error)
})
},
filtering(value) {
if (value.value.isFilter) {
this.filter = `${value.value.value}`
this.projectLyr.definitionExpression = this.filter
} else {
this.projectLyr.definitionExpression = `${value.value.value} AND IS NOT NULL`
if (this.filter !== '') {
this.projectLyr.definitionExpression = this.filter
}
value.isCountry
? this.generateRenderer({
layer: this.countryLyr,
field: value.value.value,
})
: this.generateRenderer({
layer: this.projectLyr,
field: value.value.value,
})
}
},
updateLayer(value) {
this.$nextTick(() => {
if (this.selectedTab === 0) {
this.map.remove(this.projectLyr)
this.map.add(this.countryLyr)
this.filtering({ value, isCountry: true })
} else {
this.map.remove(this.countryLyr)
this.map.add(this.projectLyr)
this.filtering({ value, isCountry: false })
}
})
},
},
}
</script>
<style scoped>
#import 'https://js.arcgis.com/4.23/#arcgis/core/assets/esri/themes/light/main.css';
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#containerDiv {
background-color: white;
padding: 3px;
text-align: center;
min-width: 260px;
}
</style>
I think you can just update the ColorSlider with the new ContinuousRendererResult data instead of trying to destroy/recreate. In a similar way you create it, use updateFromRendererResult method to update it (ArcGIS JS API - ColorSlider).

How can I connect Three.js correctly to the Vue component?

I tried to connect Three.js is the standard way to the Vue component, but for some reason this method turned out to be non-working for me.. The component connects, but does not display anything. What could be the problem?
Here is my test component:
<template>
<div id="container"></div>
</template>
<script>
import * as THREE from 'three'
export default {
name: 'ThreeTest',
data() {
return {
}
},
methods: {
init: function() {
let container = document.getElementById('container');
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(
75,
container.innerWidth / container.innerHeight,
0.1,
1000
)
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(container.innerWidth, container.innerHeight)
document.body.appendChild(this.renderer.domElement)
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
this.cube = new THREE.Mesh(geometry, material)
this.scene.add(this.cube)
this.camera.position.z = 5
},
animate: function() {
requestAnimationFrame(this.animate)
this.cube.rotation.x += 0.01
this.cube.rotation.y += 0.01
this.renderer.render(this.scene, this.camera)
}
},
mounted() {
this.init()
this.animate()
}
}
</script>
<style scoped>
#container {
width: 500px;
height: 500px;
}
</style>

Vuex Store + PixiJs using Mounted to render too soon?

If I edit on the code then suddenly the connections will render in the browser, but on initial load in the browser they do no display and its appear the array configConnections is not ready / empty, so do I need to call drawPixi() later somehow ? maybe use nexttick? or am I missing something here should it be async ?
<template>
<div class="connections">
<canvas id="pixi"></canvas>
</div>
</template>
<script>
import { mapState } from 'vuex'
import * as PIXI from 'pixi.js'
export default {
name: 'ConnectionsLayer',
computed: mapState({
configConnections: (state) => state.configConnections,
}),
methods: {
drawPixi() {
var i
var canvas = document.getElementById('pixi')
const app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
antialias: true,
transparent: true,
view: canvas,
})
let graphics = new PIXI.Graphics()
graphics.lineStyle(8, 0xcab6ff)
for (i = 0; i < Object.keys(this.configConnections).length; i++) {
//start
graphics.moveTo(
this.configConnections[i].x_pos_start,
this.configConnections[i].y_pos_start
)
//end
graphics.lineTo(
this.configConnections[i].x_pos_end,
this.configConnections[i].y_pos_end
)
}
app.stage.addChild(graphics)
},
},
mounted() {
this.drawPixi()
},
}
</script>
<div class="connections">
<canvas ref="pixi" id="pixi"></canvas>
</div>
</template>
<script>
import { mapState } from 'vuex'
import * as PIXI from 'pixi.js'
export default {
name: 'ConnectionsLayer',
computed: mapState({
configConnections: (state) => state.configConnections,
}),
watch: {
configConnections: {
deep: true,
handler() {
this.drawPixi()
},
},
},
methods: {
drawPixi() {
var i
this.canvas = this.$refs.pixi
const stage = this.PIXIApp.stage
let graphics = new PIXI.Graphics()
graphics.lineStyle(8, 0xcab6ff)
for (i = 0; i < Object.keys(this.configConnections).length; i++) {
//start
graphics.moveTo(
this.configConnections[i].x_pos_start,
this.configConnections[i].y_pos_start
)
//end
graphics.lineTo(
this.configConnections[i].x_pos_end,
this.configConnections[i].y_pos_end
)
}
for (var j = stage.children.length - 1; j >= 0; j--) {
stage.removeChild(stage.children[j])
}
stage.addChild(graphics)
},
},
mounted() {
const canvas = this.$refs.pixi
this.PIXIApp = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
antialias: true,
transparent: true,
view: canvas,
})
this.drawPixi()
},
}

How to create a video component with vue-konva?

With konva.js you can create video components on your canvas. Here you can see the example in the docs (https://konvajs.org/docs/sandbox/Video_On_Canvas.html).
Now I'm using vue-konva and there is no component to create a video on the canvas. You need to do it with the v-image component but I am not able to make it work. Is it possible with vue-konva?
Here's a fiddle of a video working with v-image.
It's a working version of this codesandbox I found.
const width = window.innerWidth;
const height = window.innerHeight;
new Vue({
el: "#app",
data() {
return {
stageSize: {
width: width,
height: height
},
video: null,
animation: null
};
},
computed: {
imageConfig() {
return {
image: this.video,
x: 0,
y: 0,
width: 320,
height: 180
};
}
},
methods: {
play() {
this.video.play();
this.animation.start();
},
pause() {
this.video.pause();
this.animation.stop();
}
},
mounted() {
this.video = document.createElement("video");
this.video.src =
"https://upload.wikimedia.org/wikipedia/commons/transcoded/c/c4/Physicsworks.ogv/Physicsworks.ogv.240p.vp9.webm";
this.layer = this.$refs.image.getStage().getLayer();
this.animation = new Konva.Animation(() => {
// do nothing, animation just need to update the layer
}, this.layer);
this.video.addEventListener("canplay", () => {
this.play();
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.2.2/konva.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-konva#2.1.1/umd/vue-konva.min.js"></script>
<div id="app">
<button #click="play">Play</button>
<button #click="pause">Pause</button>
<v-stage ref="stage" :config="stageSize">
<v-layer ref="layer">
<v-image ref="image" :config="imageConfig" />
</v-layer>
</v-stage>
</div>

loading lottie-web animation issue vuejs

so I am trying to mimic this code pen https://codepen.io/airnan/pen/ZLVJmq but when I try to load the same animation in with lottie-web npm package everything works great until the last animation. Then it all gets messed up. I'm trying to figure it out but I am so confused. Any ideas as to why? The JSON file is exactly the same except for text_data variable. I just import the whole thing and access it directly. Heres the code. it's just the last frame that doesn't work. it doesn't animate the text at all instead it looks like this.
<template>
<div>
<button #click="render">Render</button>
<h2>Backgrounds</h2>
<template v-for="background in backgrounds">
<img
:src="background.poster"
class="backgrounds"
#click="changeBackground(background.video)"
/>
</template>
<h2>Images</h2>
<template v-for="image in images">
<img
:src="image.source"
#click="addImage(image.source, image.type || null)"
class="images"
/>
</template>
<br />
<button #click="addText">Add Text</button>
<button v-if="selectedNode" #click="removeNode">
Remove selected {{ selectedNode.type }}
</button>
<label>Font:</label>
<select v-model="selectedFont">
<option value="Arial">Arial</option>
<option value="Courier New">Courier New</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Desoto">Desoto</option>
<option value="Kalam">Kalam</option>
</select>
<label>Font Size</label>
<input type="number" v-model="selectedFontSize" />
<label>Font Style:</label>
<select v-model="selectedFontStyle">
<option value="normal">Normal</option>
<option value="bold">Bold</option>
<option value="italic">Italic</option>
</select>
<label>Color:</label>
<input type="color" v-model="selectedColor" />
<button
v-if="selectedNode && selectedNode.type === 'text'"
#click="updateText"
>
Update Text
</button>
<template v-if="selectedNode && selectedNode.lottie">
<input type="text" v-model="text">
<button #click="updateAnim(selectedNode.lottie.imgSrc, 'anim')">
Update Animation
</button>
</template>
<br />
<video
id="preview"
v-show="preview"
:src="preview"
:width="width"
:height="height"
preload="auto"
controls
/>
<a v-if="file" :href="file" download="dopeness.mp4">download</a>
<div id="container"></div>
</div>
</template>
<script>
import lottie from "lottie-web";
import * as animationData from "../data.json";
animationData.layers[0].t.d.k[0].s.t = "text";
animationData.layers[1].t.d.k[0].s.t = "text";
animationData.layers[2].t.d.k[0].s.t = "text";
animationData.layers[3].t.d.k[0].s.t = "text";
animationData.layers[4].t.d.k[0].s.t = "text";
export default {
data() {
return {
source: null,
stage: null,
layer: null,
video: null,
animations: [],
text: "",
animationData: animationData.default,
captures: [],
backgrounds: [
{
poster: "/api/files/stock/3oref310k1uud86w/poster/poster.jpg",
video:
"/api/files/stock/3oref310k1uud86w/main/1080/3oref310k1uud86w_1080.mp4"
},
{
poster: "/api/files/stock/3yj2e30tk5x6x0ww/poster/poster.jpg",
video:
"/api/files/stock/3yj2e30tk5x6x0ww/main/1080/3yj2e30tk5x6x0ww_1080.mp4"
},
{
poster: "/api/files/stock/2ez931ik1mggd6j/poster/poster.jpg",
video:
"/api/files/stock/2ez931ik1mggd6j/main/1080/2ez931ik1mggd6j_1080.mp4"
},
{
poster: "/api/files/stock/yxrt4ej4jvimyk15/poster/poster.jpg",
video:
"/api/files/stock/yxrt4ej4jvimyk15/main/1080/yxrt4ej4jvimyk15_1080.mp4"
},
{
poster:
"https://images.costco-static.com/ImageDelivery/imageService?profileId=12026540&itemId=100424771-847&recipeName=680",
video: "/api/files/jedi/surfing.mp4"
},
{
poster:
"https://thedefensepost.com/wp-content/uploads/2018/04/us-soldiers-afghanistan-4308413-1170x610.jpg",
video: "/api/files/jedi/soldiers.mp4"
}
],
images: [
{ source: "/api/files/jedi/solo.jpg" },
{ source: "api/files/jedi/yoda.jpg" },
{ source: "api/files/jedi/yodaChristmas.jpg" },
{ source: "api/files/jedi/darthMaul.jpg" },
{ source: "api/files/jedi/darthMaul1.jpg" },
{ source: "api/files/jedi/trump.jpg" },
{ source: "api/files/jedi/hat.png" },
{ source: "api/files/jedi/trump.png" },
{ source: "api/files/jedi/bernie.png" },
{ source: "api/files/jedi/skywalker.png" },
{ source: "api/files/jedi/vader.gif" },
{ source: "api/files/jedi/vader2.gif" },
{ source: "api/files/jedi/yoda.gif" },
{ source: "api/files/jedi/kylo.gif" },
{
source: "https://media3.giphy.com/media/R3IxJW14a3QNa/source.gif",
type: "anim"
}
],
backgroundVideo: null,
imageGroups: [],
anim: null,
selectedNode: null,
selectedFont: "Arial",
selectedColor: "black",
selectedFontSize: 20,
selectedFontStyle: "normal",
width: 1920,
height: 1080,
texts: [],
preview: null,
file: null,
canvas: null
};
},
mounted: function() {
this.initCanvas();
},
methods: {
changeBackground(source) {
this.source = source;
this.video.src = this.source;
this.anim.stop();
this.anim.start();
this.video.play();
},
removeNode() {
if (this.selectedNode && this.selectedNode.type === "text") {
this.selectedNode.transformer.destroy(
this.selectedNode.text.transformer
);
this.selectedNode.text.destroy(this.selectedNode.text);
this.texts.splice(this.selectedNode.text.index - 1, 1);
this.selectedNode = null;
this.layer.draw();
} else if (this.selectedNode && this.selectedNode.type == "image") {
this.selectedNode.group.destroy(this.selectedNode);
this.imageGroups.splice(this.selectedNode.group.index - 1, 1);
if (this.selectedNode.lottie) {
clearTimeout(this.animations.interval);
this.selectedNode.lottie.destroy();
this.animations.splice(this.selectedNode.lottie.index - 1, 1);
}
this.selectedNode = null;
this.layer.draw();
}
},
async addImage(src, anim, isUpdate) {
let lottieAnimation = null;
let imageObj = null;
const type = anim || src.slice(src.lastIndexOf("."));
const vm = this;
function process(img) {
return new Promise((resolve, reject) => {
img.onload = () => resolve({ width: img.width, height: img.height });
});
}
imageObj = new Image();
imageObj.src = src;
imageObj.width = 200;
imageObj.height = 200;
await process(imageObj);
if (type === ".gif") {
const canvas = document.createElement("canvas");
canvas.setAttribute("id", "gif");
async function onDrawFrame(ctx, frame) {
ctx.drawImage(frame.buffer, frame.x, frame.y);
// redraw the layer
vm.layer.draw();
}
gifler(src).frames(canvas, onDrawFrame);
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
imageObj = canvas;
const gif = new Image();
gif.src = src;
const gifImage = await process(gif);
imageObj.width = gifImage.width;
imageObj.height = gifImage.height;
} else if (type === "anim") {
if(!isUpdate){this.text = "new text";}
const canvas = document.createElement("canvas");
canvas.style.height = 200;
canvas.style.width = 200;
canvas.setAttribute("id", "animationCanvas");
const ctx = canvas.getContext("2d");
const div = document.createElement("div");
div.setAttribute("id", "animationContainer");
div.style.display = "none";
canvas.style.display = "none";
this.animationData.layers[0].t.d.k[0].s.t = this.text;
this.animationData.layers[1].t.d.k[0].s.t = this.text;
this.animationData.layers[2].t.d.k[0].s.t = this.text;
this.animationData.layers[3].t.d.k[0].s.t = this.text;
this.animationData.layers[4].t.d.k[0].s.t = this.text;
lottieAnimation = lottie.loadAnimation({
container: div, // the dom element that will contain the animation
renderer: "svg",
loop: true,
autoplay: true,
animationData: this.animationData
});
lottieAnimation.imgSrc = src;
lottieAnimation.text = this.text;
const timer = setInterval(async () => {
const svg = await div.getElementsByTagName("svg")[0];
const xml = new XMLSerializer().serializeToString(svg);
const svg64 = window.btoa(xml);
const b64Start = "data:image/svg+xml;base64,";
const image64 = b64Start + svg64;
imageObj = new Image({ width: 200, height: 200 });
imageObj.src = image64;
await process(imageObj);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
this.layer.draw();
}, 1000 / 30);
this.animations.push({ lottie: lottieAnimation, interval: timer });
imageObj = canvas;
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
}
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
width: imageObj.width,
height: imageObj.height,
position: (0, 0),
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
const group = new Konva.Group({
draggable: true
});
// add the shape to the layer
addAnchor(group, 0, 0, "topLeft");
addAnchor(group, imageObj.width, 0, "topRight");
addAnchor(group, imageObj.width, imageObj.height, "bottomRight");
addAnchor(group, 0, imageObj.height, "bottomLeft");
imageObj = null;
image.on("click", function () {
vm.hideAllHelpers();
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation
};
if(lottieAnimation && lottieAnimation.text){vm.text = lottieAnimation.text}
group.find("Circle").show();
vm.layer.draw();
});
image.on("mouseover", function(evt) {
if (vm.selectedNode && vm.selectedNode.type === "image") {
const index = image.getParent().index;
const groupId = vm.selectedNode.group.index;
if (index != groupId) {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
} else {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
});
image.on("mouseout", function(evt) {
evt.target.strokeEnabled(false);
vm.layer.draw();
});
vm.hideAllHelpers();
group.find("Circle").show();
group.add(image);
vm.layer.add(group);
vm.imageGroups.push(group);
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation
};
vm.layer.draw();
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let stage = vm.stage;
let layer = vm.layer;
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function() {
group.draggable(true);
layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
let layer = this.getLayer();
document.body.style.cursor = "pointer";
this.strokeWidth(4);
layer.draw();
});
anchor.on("mouseout", function() {
let layer = this.getLayer();
document.body.style.cursor = "default";
this.strokeWidth(2);
layer.draw();
});
group.add(anchor);
}
},
async updateAnim(src, type){
this.addImage(src, type, true);
this.removeNode();
},
hideAllHelpers() {
for (let i = 0; i < this.texts.length; i++) {
this.texts[i].transformer.hide();
}
for (let b = 0; b < this.imageGroups.length; b++) {
this.imageGroups[b].find("Circle").hide();
}
},
async startRecording(duration) {
const chunks = []; // here we will store our recorded media chunks (Blobs)
const stream = this.canvas.captureStream(30); // grab our canvas MediaStream
const rec = new MediaRecorder(stream, {
videoBitsPerSecond: 20000 * 1000
});
// every time the recorder has new data, we will store it in our array
rec.ondataavailable = e => chunks.push(e.data);
// only when the recorder stops, we construct a complete Blob from all the chunks
rec.onstop = async e => {
this.anim.stop();
const blob = new Blob(chunks, {
type: "video/webm"
});
this.preview = await URL.createObjectURL(blob);
const video = window.document.getElementById("preview");
const previewVideo = new Konva.Image({
image: video,
draggable: false,
width: this.width,
height: this.height
});
this.layer.add(previewVideo);
console.log("video", video);
video.addEventListener("ended", () => {
console.log("preview ended");
if (!this.file) {
const vid = new Whammy.fromImageArray(this.captures, 30);
this.file = URL.createObjectURL(vid);
}
previewVideo.destroy();
this.anim.stop();
this.anim.start();
this.video.play();
});
let seekResolve;
video.addEventListener("seeked", async () => {
if (seekResolve) seekResolve();
});
video.addEventListener("loadeddata", async () => {
let interval = 1 / 30;
let currentTime = 0;
while (currentTime <= duration && !this.file) {
video.currentTime = currentTime;
await new Promise(r => (seekResolve = r));
this.layer.draw();
let base64ImageData = this.canvas.toDataURL("image/webp");
this.captures.push(base64ImageData);
currentTime += interval;
video.currentTime = currentTime;
}
this.layer.draw();
});
};
rec.start();
setTimeout(() => rec.stop(), duration);
},
async render() {
this.captures = [];
this.preview = null;
this.file = null;
console.log(this.captures.length);
this.hideAllHelpers();
this.selectedNode = null;
this.video.currentTime = 0;
this.video.loop = false;
const duration = this.video.duration * 1000;
this.startRecording(duration);
this.layer.draw();
},
updateText() {
if (this.selectedNode && this.selectedNode.type === "text") {
const text = this.selectedNode.text;
const transformer = this.selectedNode.transformer;
text.fontSize(this.selectedFontSize);
text.fontFamily(this.selectedFont);
text.fontStyle(this.selectedFontStyle);
text.fill(this.selectedColor);
this.layer.draw();
}
},
addText() {
const vm = this;
const text = new Konva.Text({
text: "new text " + (vm.texts.length + 1),
x: 50,
y: 80,
fontSize: this.selectedFontSize,
fontFamily: this.selectedFont,
fontStyle: this.selectedFontStyle,
fill: this.selectedColor,
align: "center",
width: this.width * 0.5,
draggable: true
});
const transformer = new Konva.Transformer({
node: text,
keepRatio: true,
enabledAnchors: ["top-left", "top-right", "bottom-left", "bottom-right"]
});
text.on("click", async () => {
for (let i = 0; i < this.texts.length; i++) {
let item = this.texts[i];
if (item.index === text.index) {
let transformer = item.transformer;
this.selectedNode = { type: "text", text, transformer };
this.selectedFontSize = text.fontSize();
this.selectedFont = text.fontFamily();
this.selectedFontStyle = text.fontStyle();
this.selectedColor = text.fill();
vm.hideAllHelpers();
transformer.show();
transformer.moveToTop();
text.moveToTop();
vm.layer.draw();
break;
}
}
});
text.on("mouseover", () => {
transformer.show();
this.layer.draw();
});
text.on("mouseout", () => {
if (
(this.selectedNode &&
this.selectedNode.text &&
this.selectedNode.text.index != text.index) ||
(this.selectedNode && this.selectedNode.type === "image") ||
!this.selectedNode
) {
transformer.hide();
this.layer.draw();
}
});
text.on("dblclick", () => {
text.hide();
transformer.hide();
vm.layer.draw();
let textPosition = text.absolutePosition();
let stageBox = vm.stage.container().getBoundingClientRect();
let areaPosition = {
x: stageBox.left + textPosition.x,
y: stageBox.top + textPosition.y
};
let textarea = document.createElement("textarea");
window.document.body.appendChild(textarea);
textarea.value = text.text();
textarea.style.position = "absolute";
textarea.style.top = areaPosition.y + "px";
textarea.style.left = areaPosition.x + "px";
textarea.style.width = text.width() - text.padding() * 2 + "px";
textarea.style.height = text.height() - text.padding() * 2 + 5 + "px";
textarea.style.fontSize = text.fontSize() + "px";
textarea.style.border = "none";
textarea.style.padding = "0px";
textarea.style.margin = "0px";
textarea.style.overflow = "hidden";
textarea.style.background = "none";
textarea.style.outline = "none";
textarea.style.resize = "none";
textarea.style.lineHeight = text.lineHeight();
textarea.style.fontFamily = text.fontFamily();
textarea.style.transformOrigin = "left top";
textarea.style.textAlign = text.align();
textarea.style.color = text.fill();
let rotation = text.rotation();
let transform = "";
if (rotation) {
transform += "rotateZ(" + rotation + "deg)";
}
let px = 0;
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox) {
px += 2 + Math.round(text.fontSize() / 20);
}
transform += "translateY(-" + px + "px)";
textarea.style.transform = transform;
textarea.style.height = "auto";
textarea.focus();
// start
function removeTextarea() {
textarea.parentNode.removeChild(textarea);
window.removeEventListener("click", handleOutsideClick);
text.show();
transformer.show();
transformer.forceUpdate();
vm.layer.draw();
}
function setTextareaWidth(newWidth) {
if (!newWidth) {
// set width for placeholder
newWidth = text.placeholder.length * text.fontSize();
}
// some extra fixes on different browsers
let isSafari = /^((?!chrome|android).)*safari/i.test(
navigator.userAgent
);
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isSafari || isFirefox) {
newWidth = Math.ceil(newWidth);
}
let isEdge =
document.documentMode || /Edge/.test(navigator.userAgent);
if (isEdge) {
newWidth += 1;
}
textarea.style.width = newWidth + "px";
}
textarea.addEventListener("keydown", function(e) {
// hide on enter
// but don't hide on shift + enter
if (e.keyCode === 13 && !e.shiftKey) {
text.text(textarea.value);
removeTextarea();
}
// on esc do not set value back to node
if (e.keyCode === 27) {
removeTextarea();
}
});
textarea.addEventListener("keydown", function(e) {
let scale = text.getAbsoluteScale().x;
setTextareaWidth(text.width() * scale);
textarea.style.height = "auto";
textarea.style.height =
textarea.scrollHeight + text.fontSize() + "px";
});
function handleOutsideClick(e) {
if (e.target !== textarea) {
text.text(textarea.value);
removeTextarea();
}
}
setTimeout(() => {
window.addEventListener("click", handleOutsideClick);
});
// end
});
text.transformer = transformer;
this.texts.push(text);
this.layer.add(text);
this.layer.add(transformer);
this.hideAllHelpers();
this.selectedNode = { type: "text", text, transformer };
transformer.show();
this.layer.draw();
},
initCanvas() {
const vm = this;
this.stage = new Konva.Stage({
container: "container",
width: vm.width,
height: vm.height
});
this.layer = new Konva.Layer();
this.stage.add(this.layer);
let video = document.createElement("video");
video.setAttribute("id", "video");
video.setAttribute("ref", "video");
if (this.source) {
video.src = this.source;
}
video.preload = "auto";
video.loop = "loop";
video.style.display = "none";
this.video = video;
this.backgroundVideo = new Konva.Image({
image: vm.video,
draggable: false
});
this.video.addEventListener("loadedmetadata", function(e) {
vm.backgroundVideo.width(vm.width);
vm.backgroundVideo.height(vm.height);
});
this.video.addEventListener("ended", () => {
console.log("the video ended");
this.anim.stop();
this.anim.start();
this.video.loop = "loop";
this.video.play();
});
this.anim = new Konva.Animation(function() {
console.log("animation called");
// do nothing, animation just need to update the layer
}, vm.layer);
this.layer.add(this.backgroundVideo);
this.layer.draw();
const canvas = document.getElementsByTagName("canvas")[0];
canvas.style.border = "3px solid red";
this.canvas = canvas;
}
}
};
</script>
<style scoped>
body {
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
.backgrounds,
.images {
width: 100px;
height: 100px;
padding-left: 2px;
padding-right: 2px;
}
</style>
You must use keyframes in order to get the animation to render properly once you do that you will have no issues.