简体   繁体   中英

Drawing onto a canvas with vue.js

I don't think this is necessarily a Vue based issue, but I'm running into some trouble.

I'd like to write to the canvas a Vue variable. If I remove vue, my initial code works fine, however if I add Vue the canvas doesn't actually start up.

Here is my code

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.fillStyle = "black";
ctx.font="20px Georgia";
ctx.fillText("Hello World!",10,50);

var v = new Vue({
  el: '#app',
  data: {
    'exampleContent': 'This is TEXT'
  },
  watch: {
    exampleContent: function(val, oldVal) {
      ctx.clearRect(0,0,canvas.width,canvas.height);
      ctx.fillStyle = "black";
      ctx.font="20px Georgia";
      ctx.fillText(this.exampleContent,10,50);
    }
  }
});

If I comment out /* var v = new Vue({... the initial bit works. If I log the value of exampleContent in the watcher that also works. But something about the canvas isn't working.

Demo to play with: http://codepen.io/EightArmsHQ/pen/EgGbgR?editors=1010

Please check this jsFiddle: https://jsfiddle.net/mani04/r4mbh6nu/

EDIT: updated with dynamic text: https://jsfiddle.net/mani04/r4mbh6nu/1/

In the above example, I am able to write into canvas, and also ensure that the input text goes into span as you would expect, all using Vue.js

Coming back to your example, the HTML is:

<div id="app">
    <canvas id="canvas" width="800" height="600"></canvas>
    <input type="text" v-model="exampleContent" />
    <span>{{ exampleContent }}</span>
</div>

Vue.js reads the elements inside #app and keeps it as the template for your Vue app . So your canvas , input and span elements will now be re-rendered by Vue.js when it constructs the DOM.

During this process, your original canvas text - the one you set before initiating the Vue application gets lost.

There is no clear way to solve this issue other than using a directive , as in my jsFiddle example. A Vue directive helps you capture the DOM element.

In my example, I define a Vue directive called insert-message which I use on canvas as v-insert-message . This allows me to capture the DOM element and then do the other steps: getContext and fillText . There is no id required, or no javascript code to getElementById .

One more thing - your canvas is just too large, and therefore you were not able to see your input and span elements. They were working normally in your example also!

EDIT: example for directive bindings

I just found a way to use directives and still write dynamic stuff into canvas.

Check the updated jsFiddle: https://jsfiddle.net/mani04/r4mbh6nu/1/

Here's an easy way that works with Vuejs 2:

  1. Add the canvas to you template with a reference attribute:

    ref="myCanvas"

  2. You can then access it anywhere inside your component using

    var canvas = this.$refs.myCanvas

As detailed in @Mani's Answer , Vue removes and re-renders the DOM element that a Vue instance is invoked upon.

If you abstract your canvas updating into a Vue instance method , you can trigger it at the mounted lifecycle hook (to get the initial value of exampleContent ), the re-invoke it in your watch when exampleContent changes.

 var v = new Vue({ el: '#app', data: { 'exampleContent': 'This is TEXT' }, methods: { updateCanvas: function (){ var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.fillStyle = "black"; ctx.font="20px Georgia"; ctx.fillText(this.exampleContent,10,50); } }, watch: { exampleContent: function(val, oldVal) { this.updateCanvas(); } }, mounted: function (){ this.updateCanvas(); } }); 
 canvas{ background:red; width:800px; height:600px; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.min.js"></script> <div id="app"> <canvas id="canvas" width="800" height="600"></canvas> <input type="text" v-model="exampleContent" /> <span>{{ exampleContent }}</span> </div> 

The preceding SO Code Snippet was created from forked version of OP's CodePen

This question has been viewed a few times, and I've used canvases and Vue thousands of times since I first asked it. This is the best option I have found in practise.

Give the canvas a ref as mentioned by a few people:

<div id="app">
  <canvas width="800" height="600" ref="nameOfCanvas"></canvas>
</div>

Set the context of the canvas in the mounted function:

mounted(){
  this.canvas = this.$refs.nameOfCanvas;
  this.ctx = this.canvas.getContext("2d");
  this.render();
}

There is no need to put ctx in the data object unless you want to watch it.

That's it. From there, a render method can be created and reference data in your component...

render(){
  this.ctx.clearRect(0,0,this.canvas.width, this.canvas.height);
  this.ctx.fillRect(0, 0, 20, 20);
}

You can also set up a requestAnimationFrame loop within this method like so:

render(){
  this.ctx.clearRect(0,0,this.canvas.width, this.canvas.height);
  this.ctx.fillRect(0, 0, 20, 20);
  requestAnimationFrame(this.render);
}

Full demo / starter: https://codepen.io/EightArmsHQ/pen/gOROpqQ

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM