Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue canvas $ref not defined on resize. Throws error but works appears to work anyways?

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.

like image 716
zachThePerson Avatar asked Dec 20 '25 04:12

zachThePerson


1 Answers

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.

like image 105
Phil Avatar answered Dec 21 '25 18:12

Phil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!