JavaScript prototype inheritance and html canvas

若如初见. 提交于 2020-01-15 02:36:46

问题


I'm a Ruby developer who finally decided to learn JavaScript seriously. So I purchased some books and I started to dive in, but I got stuck quickly when I tried to understand prototypal inheritance...

One of the examples of the book is the following. Given a Shape which prototype has a draw method, and two child shapes: a Triangle and a Rectangle which prototype inherit from Shape;

  • when I call the draw function on Triangle and Rectangle instances the method will draw them properly.
  • when I add a second method to show their name, every instance will log it properly.

Everything was understandable perfectly until I added a third method to fill the shapes... And only the last one get filled. no matter which one I call. Why? Is there something special in canvas?

Here is the code of the exercise:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

function Shape() {
  this.points = [];
  this.init();
}

Shape.prototype = {
  constructor: Shape,
  init: function() {
    if (this.context === undefined) {
      Shape.prototype.context = document.getElementById('canvas').getContext('2d');
    };
    if (this.name === undefined) {
      Shape.prototype.name = 'generic shape'
    }
  },
  draw: function() {
    var i, ctx = this.context;
    ctx.strokeStyle = 'rgb(0,0,255)';
    ctx.beginPath();
    ctx.moveTo(this.points[0].x, this.points[0].y);
    for (i = 1; i < this.points.length; i++) {
      ctx.lineTo(this.points[i].x, this.points[i].y);
    }
    ctx.closePath();
    ctx.stroke();
  },
  fill: function(color) {
    var ctx = this.context;
    ctx.fillStyle = color;
    ctx.fill();
  },
  say_name: function() {
    console.log('Hello my name is ' + this.name)
  }
};

function Triangle(a, b, c) {
  this.points = [a, b, c];
  this.name = 'Triangle'
  this.context = document.getElementById('canvas').getContext('2d');
}

function Rectangle(side_a, side_b) {
  var p = new Point(200, 200);
  this.points = [
    p,
    new Point(p.x + side_a, p.y), // top right
    new Point(p.x + side_a, p.y + side_b), // bottom right
    new Point(p.x, p.y + side_b) // bottom left
  ];
  this.name = 'Rectangle'
  this.context = document.getElementById('canvas').getContext('2d');
}

(function() {
  var s = new Shape();
  Triangle.prototype = s;
  Rectangle.prototype = s;
})();

function testTriangle() {
  var p1 = new Point(100, 100);
  var p2 = new Point(300, 100);
  var p3 = new Point(200, 0);
  return new Triangle(p1, p2, p3);
}

function testRectangle() {
  return new Rectangle(100, 100);
}

function make_me_crazy() {
  var t = testTriangle();
  var r = testRectangle();
  t.draw();
  r.draw();
  t.say_name();
  r.say_name();
  t.fill('red');
}
make_me_crazy();
<canvas height='600' width='800' id='canvas' />

Thank you!

More details:

  • Why the function say_name is working exactly I expect saying: 'I am a triangle' or 'I am a rectangle' and never 'I am a generic shape', but the fill function fills the rectangle despite I'm calling it on a triangle instance? As people rightly answered to flip the two draw functions calls, I would specify better the following. The problem is not about the color of a shape, but the context pointer. why only the last shape is filled? If I add more shapes before calling fill only the last one get filled. This means I'm doing something wrong referring to the canvas. I supposed it was "the place where I draw shapes" but it seems more like "the last active shape"
  • How can I fix that code to make it working correctly filling the shape I want whenever I want? I mean. what if I want to have a function which receive an instance of a particular shape and fills it?
  • Is there any way to access a the draws contained into a canvas?

回答1:


The core of the problem is the context - your shapes are sharing the single context of the canvas, and therefore it is not straight-forward to flip back and forth between objects. Instead, think of your order-of-operations as handling a single shape at a time and only moving on to the next one when you are done with the former.

Note the order of calls in the make_me_crazy function:

function Point(x, y) {
    this.x = x;
    this.y = y;
  }

  function Shape() {
    this.points = [];
    this.init();
  }

  Shape.prototype = {
    constructor: Shape,
    init: function(){
      if (this.context === undefined) {
        Shape.prototype.context = document.getElementById('canvas').getContext('2d');
      };
      if(this.name === undefined){
        Shape.prototype.name = 'generic shape'
      }
    },
    draw: function(){
      var i, ctx = this.context;
      ctx.strokeStyle = 'rgb(0,0,255)';
      ctx.beginPath();
      ctx.moveTo(this.points[0].x, this.points[0].y);
      for (i = 1; i<this.points.length; i++) {
        ctx.lineTo(this.points[i].x, this.points[i].y);
      }
      ctx.closePath();
      ctx.stroke();
    },
    fill: function(color){
      var ctx = this.context;
      ctx.fillStyle = color;
      ctx.fill();
    },
    say_name: function(){console.log('Hello my name is '+ this.name)}
  };

  function Triangle(a,b,c){
    this.points = [a, b, c];
    this.name = 'Triangle'
    this.context = document.getElementById('canvas').getContext('2d');
  }

  function Rectangle(side_a, side_b){
    var p = new Point(200, 200);
    this.points = [
      p,
      new Point(p.x + side_a, p.y),// top right
      new Point(p.x + side_a, p.y + side_b), // bottom right
      new Point(p.x, p.y + side_b)// bottom left
    ];
    this.name = 'Rectangle'
    this.context = document.getElementById('canvas').getContext('2d');
  }

  (function(){
    var s = new Shape();
    Triangle.prototype = s;
    Rectangle.prototype = s;
  })();

  function testTriangle(){
    var p1 = new Point(100, 100);
    var p2 = new Point(300, 100);
    var p3 = new Point(200, 0);
    return new Triangle(p1, p2, p3);
  }

  function testRectangle(){
    return new Rectangle(100, 100);
  }

  function make_me_crazy(){
    var t = testTriangle();
    t.say_name();
    t.draw();
    t.fill('red');
    
    var r = testRectangle();
    r.draw();
    r.say_name();
  }
  make_me_crazy();
<canvas height='600' width='800' id='canvas'></canvas>



回答2:


About the points of your question.

For the first one: the key is this line of code

if(this.name === undefined){
    Shape.prototype.name = 'generic shape'
}

When you instantiate Rectangle and Triangle, both of them set name. In the other hand, the render method is only available in the Shape prototype.

About the second point (and the third one): Maybe are you painting the Rectangle over the Triangle. Try to switch the order of the draw calls to check it.



来源:https://stackoverflow.com/questions/42161164/javascript-prototype-inheritance-and-html-canvas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!