I have a Paper instance with a tool that just draws a path on mouseMove and deletes the segments at the start of that path if the number of segments is greater than 50. Everything works perfect this far. this is the code:
<template>
<div>
<canvas id="canvas" resize></canvas>
</div>
</template>
<script>
import paper from 'paper';
export default {
name: 'home',
components: {
},
created() {
paper.install(window);
},
mounted() {
const canvas = this.$el.querySelector('#canvas');
paper.setup(canvas);
const path = new Path();
path.strokeColor = '#f5bb56';
path.strokeWidth = 2;
this.tool = new Tool()
this.tool.onMouseMove = event => {
if (path.segments.length > 50) {
path.removeSegment(0)
};
path.add(event.point);
path.smooth({
type: 'continuous'
});
};
view.draw()
},
};
</script>
<style lang="scss">
#canvas {
width: 100%;
height: 100%;
}
</style>
The problem is that now i want to start deleting segments from that path with an interval of 50 miliseconds but stop executing that when a new segment is added. I'm looking for something to set a variable into a timeout(() => {eraseFunction()}), when the event is not fired for about two seconds.
i added a clearTimeout pointing to the variable that contains it at the start of the mouseMove event and setting it at the end, so if there's a timeout running, i remove it when the mouseMove starts:
export default {
name: 'home',
components: {
},
data() {
return {
tool: null,
path: null,
erase: null,
}
},
created() {
paper.install(window);
},
mounted() {
const canvas = this.$el.querySelector('#canvas');
paper.setup(canvas);
this.path = new Path();
this.path.strokeColor = '#f5bb56';
this.path.strokeWidth = 2;
this.tool = new Tool()
this.tool.onMouseMove = event => {
clearTimeout(this.erase);
if (this.path.segments.length > 50) {
this.path.removeSegment(0)
};
this.path.add(event.point);
this.path.smooth({
type: 'continuous'
});
this.erase = setTimeout(() => {
this.eraseFunction()
}, 2000);
};
view.draw()
},
methods: {
eraseFunction() {
setInterval(() => {
this.path.removeSegment(0);
}, 500);
}
}
};
</script>
the problem is that the timeout is not removed and given a certain amount of time, i can´t draw new segments because they´re deleted inmediately.
You need to clear setInterval also. You are only clearing setTimeout. setInterval is still running an deleting your segments.
ClearInterval need the intervalID you want to clear. The intervalID is given by setInterval call.
You should return the result of setTimout call in eraseFunction:
eraseFunction() {
return setInterval(() => {
this.path.removeSegment(0);
}, 500);
}
And you should assign to this.erase the result of eraseFunction call, instead of the setTimeout:
setTimeout(() => {
this.erase = this.eraseFunction()
}, 2000);
Related
I'm using a Popup style UI component in a Nuxt.js base project. This is used by many pages and routes, so I declared and initiated as global component plugin when the app starts, like below:
// nuxt.config.js
plugins: [
{ src: '~/plugins/popup/index.js', mode: 'client' },
],
// plugins/toast/index.js
import Vue from 'vue';
import PopupComponent from './Popup.vue';
const PopupConstructor = Vue.extend(PopupComponent);
export default () => {
Vue.use({
install: () => {
let _popup = new PopupConstructor();
window.popup = Vue.prototype.popup = {
appear: _popup.appear,
disappear: _popup.disappear
};
_popup.vm = _popup.$mount();
_popup.dom = _popup.vm.$el;
document.body.appendChild(_popup.dom);
}
});
};
// Popup.vue
// some edit applied for the sake of simplicity
<template>
<div
class="popup"
:class="{
'--error': error,
'--visible': visible
}"
ref="popup"
>
<div class="content" ref="content">
<div class="title">{{title}}</div>
<div class="text">{{detail}}</div>
</div>
</div>
</template>
import gsap from 'gsap';
export default {
data: function () {
return {
visible: false,
title: '',
detail: '',
timer: 3000,
timeout: null,
animationTimeout: null,
};
},
created() {
},
mounted() {
this.$_appear = null;
this.$_disappear = null;
},
beforeDestroy() {
this.$_appear.kill();
this.$_appear = null;
this.$_disappear.kill();
this.$_disappear = null;
},
appear({ title, detail }) {
if (this.visible) {
this.clearTimeout();
}
this.visible = true;
this.$_appear.kill();
this.$_disappear.kill();
this.title = title;
this.detail = detail;
this.$_showAni = gsap.to(this.$refs.popup, 0.5, {
css: {
top: '100px',
opacity: 1
},
onComplete: () => {
this.$_appear = null;
}
});
this.timeout = window.setTimeout(() => {
this.disappear();
}, this.timer);
},
disappear() {
this.clearTimeout();
this.$_disappear.kill();
this.$_disappear = gsap.to(this.$refs.popup, 0.5, {
css: {
top: '100px',
opacity: 0
},
onComplete: () => {
this.$_disappear = null;
this.visible = false;
}
});
},
clearTimeout() {
if (this.timeout) {
window.clearTimeout(this.timeout);
this.timeout = null;
}
}
}
As you see, by this code the Popup vue component's methods(appear, disappear) will be accessible through window.popup, and the component itself will be created, mounted, attached on document.
This works just fine, but the problem is it seems this leads to memory leak. As I profile the memory allocation timeline using Chrome devtool, from some point of time memory allocated with window causes retained(dangling?; could be GC-ed but left due to reference using?) memory.
Is the usage of plugin like above okay? If not, to get the same utility while preventing memory leak, which part should be corrected?
EDIT:
I added the simple version implementation code for Popup which uses GSAP library for an animation. It uses the animation for appear and disappear sequentially.
i'm learning vue and i have small problem.
I've created code, which receive some informations from webserwer (via socket) and this code works fine.
But i would like to do very simple thing - display info as variable in HTML and i have problem with it.
My code is:
export default {
components: {
BCard,
BButton,
},
data() {
return {
connection: null,
json: {
cmd: 'start',
width: 1024,
height: 800,
url: 'https://www.google.com',
token: '',
service_id: 1,
thumbnail_width: 100,
thumbnail_height: 100,
},
}
},
created() {
console.log('Starting Connection to WebSocket')
this.connection = new WebSocket('ws://127.0.0.1:8080/')
// this.connection = new WebSocket('ws://echo.websocket.org')
this.connection.onopen = function (event) {
console.log(event)
console.log('Success')
}
this.connection.onmessage = webSocketOnMSG
},
methods: {
sendMessage(message) {
console.log(this.connection)
console.log(message)
console.log(JSON.stringify(message))
const dat = this.connection.send(JSON.stringify(message))
console.log('TT', dat)
},
drawItem() {
const img = document.createElement('img')
const canvas = document.getElementById('canvasId')
img.src = 'http://image...'
img.onload = function (a) {
const h = a.target.height
const w = a.target.width
const c = canvas.getContext('2d')
canvas.width = w
canvas.height = h
c.drawImage(img, 0, 0)
document.getElementById('draw-image-test').appendChild(canvas)
}
},
webSocketOnMSG(msg) {
console.log(msg)
},
},
}
and i would like to add code like this:
data: {
xposition: 'xpos',
yposition: 'ypos'
}
but when i'm adding it to created earlier data() i have error, so this doesn't work:
data() {
xposition: 'xpos',
yposition: 'ypos',
return {...}
}
where should i add code to replace variables {{xposition}} and {{yposition}} in HMTL?
You must put your new variables inside your returned object in the data function, alongside your 'json' variable. You need to declare them first as empty values, and then add the proper values in your API call callback
data() {
return {
xposition: '',
yposition: '',
...
}
}
webSocketOnMSG(msg) {
// this will change your component's xposition property
this.xposition = msg.propertyYouWantToAccess
},
I built a small boat visualizer. I am using AISHub APIs. After fetching data from the APIs I am able to obtain a json file with the vessels I am interested in and inject these vessels inside a table.
The user has to manually update the page pushing the refresh button on top left of the page to see the new updated table.
The problem: How to set a state to refresh the google-map content automatically every minute instead of the user doing it manually?
Below the code:
GoogleMap.js
class BoatMap extends Component {
constructor(props) {
super(props);
this.state = {
buttonEnabled: true,
buttonClickedAt: null,
progress: 0,
ships: [],
type: 'All',
shipTypes: [],
activeShipTypes: [],
logoMap: {}
};
this.updateRequest = this.updateRequest.bind(this);
this.countDownInterval = null;
}
async componentDidMount() {
this.countDownInterval = setInterval(() => {
if (!this.state.buttonClickedAt) return;
const date = new Date();
const diff = Math.floor((date.getTime() - this.state.buttonClickedAt.getTime()) / 1000);
if (diff < 90) {
this.setState({
progress: diff,
buttonEnabled: false
});
} else {
this.setState({
progress: 0,
buttonClickedAt: null,
buttonEnabled: true
});
}
}, 500);
await this.updateRequest();
const shipTypeResults = await Client.getEntries({
content_type: 'competitors'
});
console.log(shipTypeResults);
const shipTypes = shipTypeResults.items.map((data) => data.fields);
const logoMap = shipTypes.reduce((acc, type) => {
return {
...acc,
[type.name]: type.images.fields.file.url
};
}, {});
console.log({ shipTypes });
this.setState({
logoMap
});
}
componentDidUpdate(prevProps, prevState) {
if (this.state.type !== prevState.type) {
}
}
componentWillUnmount() {
clearInterval(this.countdownInterval);
}
async updateRequest() {
const url = 'http://localhost:3001/hello';
console.log(url);
const fetchingData = await fetch(url);
const ships = await fetchingData.json();
console.log(ships);
this.setState({
buttonEnabled: false,
buttonClickedAt: new Date(),
progress: 0,
ships
});
setTimeout(() => {
this.setState({ buttonEnabled: true });
});
}
render() {
return (
<div className="google-map">
<GoogleMapReact
bootstrapURLKeys={{ key: 'KEY' }}
center={{
lat: this.props.activeShip ? this.props.activeShip.latitude : 42.4,
lng: this.props.activeShip ? this.props.activeShip.longitude : -71.1
}}
zoom={8}
>
</GoogleMapReact>
</div>
);
}
}
What I have done so far:
A good way would be using a setTimeout() but would that be correct? Where should that be applied and how?
setTimeout(function () {
location.reload();
}, 60 * 1000);
Or maybe setting an interval as a refresh rate?
I am a bit confused on what would the best way to approach this.
On your request function i guess u want to disable the button while the api doesn't return, so maybe move this piece above the requests:
this.setState({
buttonEnabled: false,
buttonClickedAt: new Date(),
progress: 0,
ships
});
If im wrong you could remove the timeout from the second setState and call as a callback on the first like this:
this.setState({
buttonEnabled: false,
buttonClickedAt: new Date(),
progress: 0,
ships
}, () => {
this.setState({ buttonEnabled: true });
});
on the last part instead of location.reload() set a interval calling the update on ur componentDidMount:
let updateInterval = setInterval(() => {
this.updateRequest();
}, 60 * 1000);
this.setState({updateInterval})
then on the componentWillUnmount you clear the interval this.state.updateInterval
I made a codepen of my issue here https://codepen.io/stevemr/pen/VNQbYe
I have a root Vue instance which maintains the props for a component, VideoPlayer. My root instance has a method called setVideo, which is just assigning some dummy values right now.
Here's the object I'm using in the data of the root instance:
video: {
drive: '',
filename: '',
mediaType: '',
},
Here's the setVideo function:
setVideo: function() {
// Get the drive, filename, and mediaType
this.video.drive = 'hdd1';
this.video.filename = 'game-of-thrones_s01e04.mp4';
this.video.mediaType = 'show';
// Hide all modals and trigger the display of the video player
Event.trigger('hideModal');
Event.trigger('displayVideoPlayer');
},
The Event class is just a wrapper for basic Vue events:
window.Event = new class {
constructor() {
this.vue = new Vue();
}
trigger(event, data = null) {
this.vue.$emit(event, data);
}
listen(event, callback) {
this.vue.$on(event, callback);
}
};
Here's the DOM where my VideoPlayer component is initialized:
<video-player
v-bind:drive="video.drive"
v-bind:filename="video.filename"
v-bind:media-type="video.mediaType"
></video-player>
And finally, here's my VideoPlayer component:
<template>
<div>
<div id="movie-container">
<div
class="video-loader top-most"
v-if="showVideoPlayer && !loaded"
></div>
<video
id="video-player"
ref="video"
v-if="showVideoPlayer && src !== ''"
class="top-most"
v-bind:class="{ hidden: !loaded }"
v-on:click="togglePlay"
controls
autoplay
>
<source v-bind:src="src" v-bind:type="videoType"></source>
</video>
</div>
<div id="time-range-container" v-if="showTimeRange">
<input
id="time-range"
ref="timeRange"
type="range"
min="0"
v-bind:max="duration"
step="30"
v-model:value="currentTime"
/>
</div>
</div>
</template>
<script>
export default {
props: [
'drive',
'filename',
'mediaType',
],
data() {
return {
currentTime: 0,
duration: 0,
loaded: false,
showTimeRange: false,
showVideoPlayer: false,
}
},
computed: {
src: function() {
if(this.filename !== '') {
return
'/video/' + this.drive +
'/' + this.mediaType +
's/' + this.filename;
}
return '';
},
videoType: function() {
var ext = this.filename.split('.')[1];
var type = '';
switch(ext) {
case 'mk4':
case 'm4v':
type = 'webm';
break;
case 'avi':
type = 'ogg';
break;
default:
type = ext;
}
return 'video/' + type;
},
},
created() {
Event.listen('displayVideoPlayer', this.display);
},
methods: {
display: function() {
if(this.src === '') {
return;
}
this.showVideoPlayer = true;
this.loaded = false;
var self = this;
setTimeout(function() {
var interval = setInterval(function() {
var video = self.$refs.video;
if(video.readyState > 0) {
self.loaded = true;
self.duration = Math.round(video.duration);
self.currentTime = video.currentTime;
clearInterval(interval);
}
}, 500);
}, 800);
},
togglePlay: function() {
var video = this.$refs.video;
if(video.paused) {
video.play();
}
if(!video.paused) {
video.pause();
}
},
},
}
</script>
When setVideo is called it should set the VideoPlayer component's props to the dummy values and then the video player should be displayed. But instead when the displayVideoPlayer event is fired, the component props are still set to their default values (empty strings). Most importantly, the src computed property is not being updated before the display method is called, so the display function immediately returns without doing anything.
It's like my component's props and data aren't being updated, even though I can see with the dev tools that they are. It's like it's just not happening fast enough or something.
I've tried making src part of the component's data and setting it in the display function with another function, setSrc. But the same thing happened.
I've also tried moving Event.listen('displayVideoPlayer', this.display); into mounted() instead of created(), also did not fix anything.
If you look at the codepen, the first time you click the button to trigger the setVideo function, the video player component should be displayed, instead it takes 2 clicks.
It seems the problem is a race condition between Vue updates the value and You call display method:
display: function() {
console.log(this.src) // ""
setTimeout(() => console.log(this.src)) // "/video/hdd1/shows/game-of-thrones_s01e04.mp4"
if(this.src === '') {
return
}
This mean you call display method before the value is update.
One way to solve you is add some delay before your call display method:
setVideo: function() {
this.video.drive = 'hdd1'
this.video.filename = 'game-of-thrones_s01e04.mp4'
this.video.mediaType = 'show'
setTimeout(() => {
Event.trigger('displayVideoPlayer')
})
But I think this might get more problems in the future. If you want to rely on props then you should use watcher pattern instead:
watch: {
src (src) {
if(src === '') {
return
}
// ... display
}
}
Or pass those values through your event not on props like:
Event.trigger('displayVideoPlayer', this.video)
I have the following code:
const scenarioList = []
const randomScenario = () => {
return scenarioList[Math.floor(Math.random() * scenarioList.length--)]
}
class Scenario{
setBG(){
//screen.bg = this.bg
//screen.redraw()
}
write(text, buttons, callback){
//$('#gametext > span').html(`<span>${text}</span>`)
//input.setText(buttons)
//input.bindAll(callback)
}
constructor(imgsrc, text, actions, callback){
let img = new Image()
img.src = imgsrc
this.bg = img
this.text = text
this.actions = actions
this.callback = callback
scenarioList.push(this)
console.log(scenarioList)
}
}
I init the class the following (and this is in the global scope)
new Scenario('./bg/1.png', 'You look around and see a huge mountain, what do you do?',[
'Climb It!!',
'Walk around',
'Other Direction',
'Rest',
], [
() => {
alert('a')
},
() => {
alert('a')
},
() => {
alert('a')
},
() => {
alert('a')
},
])
And verify with console.log(scenarioList)
[Scenario]
So its appended, but when I later try to do a console.log() on the same variable it is the following:
[]
Code that causes it:
const startGame = () => {
alert('were here') // this executes at the correct time, but later then variable init.
let scn = randomScenario()
console.log(scenarioList)
scn.write()
scn.setBG()
}
I am not seeing why this would happen, anyone can give me a push in the right direction?
I've found the solution, this code actually removed the element from the array:
const randomScenario = () => {
return scenarioList[Math.floor(Math.random() * scenarioList.length--)]
}
instead I did this:
return scenarioList[Math.floor(Math.random() * scenarioList.length -1)]