In my app I have a tabbed interface, and in one of the tab's "windows," there is a canvas that automatically fits to the window size every time the window is resized. Everything works fine, but when I switch tabs and switch back, then try to resize the window, it still works but throws a "this.$refs.canvas is undefined" error.
I've set up a simplified example that toggles the canvas element on and off. The code is complete if you want to try debugging it. There is no problem if you resize the window immediately after load, and it works without error, but if you toggle the canvas on and off, it will throw an error
Main App file:
<template>
<div id="app">
<button class="toggleBtn" @click="toggleCanvas()">Toggle Canvas</button>
<component class="canvasBox" :is="canvasElX" />
</div>
</template>
<script>
import ResizeCanvas from './components/ResizeCanvas.vue'
export default {
name: 'App',
components: {
ResizeCanvas
},
data(){
return {
canvasShow: true,
canvasEl: null
}
},
computed: {
canvasElX(){
if (this.canvasShow){
return ResizeCanvas;
}
else{
return null;
}
}
},
methods: {
toggleCanvas(){
this.canvasShow = !this.canvasShow;
}
}
}
</script>
<style>
html, body{
margin: 0px;
padding: 0px;
width: 100%;
height: 100%;
}
#app {
display: flex;
width: 100%;
height: 100%;
}
.toggleBtn{
width: 150px;
}
.canvasBox{
flex-grow: 1;
}
</style>
ResizeCanvas component:
<template>
<div ref="resizeCanvas" class="resizeCanvas">
<canvas ref="canvas" class="canvas">
//Error loading canvas
</canvas>
</div>
</template>
<script>
export default {
name: 'ResizeCanvas',
mounted(){
window.addEventListener('resize', ()=>{this.resizeCanvas()});
this.resizeCanvas();
this.drawCanvas();
},
beforeDestroy(){
window.removeEventListener('resize', ()=>{this.resizeCanvas()});
},
methods: {
resizeCanvas(){
let canvas = this.$refs.canvas;
canvas.width = this.$refs.resizeCanvas.clientWidth;
canvas.height = this.$refs.resizeCanvas.offsetHeight;
this.drawCanvas();
},
drawCanvas(){
let canvas = this.$refs.canvas;
let ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 50, 50);
ctx.fillRect(canvas.width - 50, canvas.height - 50, 50, 50);
}
}
}
</script>
<style scoped>
.resizeCanvas{
position: relative;
width: 100%;
height: 100%;
}
.canvas{
position: absolute;
}
</style>
It's a pretty puzzling issue. While it appears to work anyways, I'm worried this might be causing sneaky issues in the background that I'll have to find out the hard way in the future.
The issue here is that you will be continuously adding new event listeners and the old ones are still running with stale references to this.$refs.
This is because you are unfortunately not using removeEventListener correctly (which is an incredibly easy mistake to make). To properly work, you need to pass it the exact same function you registered with addEventListener.
Provided you pass in references to component methods, you can do away with the anonymous arrow functions
mounted() {
window.addEventListener('resize', this.resizeCanvas) // pass a method reference
this.resizeCanvas()
this.drawCanvas()
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeCanvas) // same function reference
},
If you're interested in not loading your canvas fresh each time the tab is activated, you might be interested in the <keep-alive> component.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With