4

I'd like to create an object which I can pass around in place of a built-in object (specifically, an instance of CanvasRenderingContext2D). I'd like most of the methods to forward to the built-in equivalents, but I'd also like to add some methods of my own & wrap some of the built-ins.

Here's what I've come up with:

function MyContext(ctx) {
  for (var k in ctx) {
    (function(k) {
      if (typeof(ctx[k]) == 'function') {
        this[k] =  ctx[k].bind(ctx);
      } else {
        Object.defineProperty(this, k, {
          get: function() { return ctx[k]; },
          set: function(x) { ctx[k] = x; }
        });
      }
    }).bind(this)(k);
  }

  this.stroke = function() {
    console.log('called stroke');
    ctx.stroke();
  };
}

A MyContext object can be used exactly as the built-in CanvasRenderingContext2D is:

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

var mtx = new MyContext(ctx);
mtx.beginPath();
mtx.moveTo(100, 50);
mtx.lineTo(200, 50);
mtx.lineTo(200, 75);
mtx.closePath();
mtx.strokeStyle = 'red';
mtx.stroke();

with the exception that the stroke call logs.

Is this the best way to achieve this? Is it possible to do this more efficiently using prototype trickery?

danvk
  • 15,863
  • 5
  • 72
  • 116
  • Looks like maybe a dup of [Can't inherit from context object](http://stackoverflow.com/questions/23838914/cant-inherit-from-context-object). – jfriend00 Sep 22 '15 at 02:40
  • 1
    To me it would be better as `var mtx = new MyContext('canvas')` and do the *querySelector* and *getContext* calls inside the constructor too. – RobG Sep 22 '15 at 02:41
  • @jfriend00—not sure it's a dupe, the OP is wrapping the context rather than trying to subclass it. All the methods are still called with the original context object bound as *this*. One issue is that non–enumerable properties won't be copied, though I don't know if that's an issue or not. – RobG Sep 22 '15 at 02:49
  • @RobG - The OP is asking if there's any trick with the prototype which is what the other question tried and failed with. The other question also ended up wrapping. Seems like pretty much the same ground being covered there as is covered here. Yeah, questions aren't worded identically, but I can't envision an answer here that would end up being different from the answer there. – jfriend00 Sep 22 '15 at 02:55
  • @jfriend00—fair enough, though I think the OP's attempt here is better than the accepted answer there—`arguments.callee.caller` is not a good idea. – RobG Sep 22 '15 at 03:06
  • Yeah, the OP's proxy here is a cleaner implementation than that other proxy. – jfriend00 Sep 22 '15 at 03:08
  • Regardless of whether this is a dupe, it sounds like the answer to my question about prototype trickery is "no"? – danvk Sep 22 '15 at 03:14
  • and as to performance: at least using [this page](http://www.themaninblue.com/experiment/AnimationBenchmark/canvas/) in Mac OS X/Chrome as a benchmark, I see no measurable effect on performance when I use `MyContext` as a wrapper around the built-in context. – danvk Sep 22 '15 at 03:26
  • The answer is "no" if you want to modify the *canvas* or *context* constructor prototype (there's no guarantee that there is one and not good design if there is), or to create a constructor whose prototype is an instance of a context (won't work, per [*jfriend00's reference*](http://stackoverflow.com/questions/23838914/cant-inherit-from-context-object)). I expect the biggest performance issue with canvas objects is drawing, an extra function call is likely irrelevant, testing will confirm (or not!). ;-) – RobG Sep 22 '15 at 04:36
  • I use similar code to automatically serialize drawings done through the context. If you want a complete proxy for the context there are edge cases to consider: gradients, patterns, the differing kinds of arguments to .fillStyle, pixel manipulation, etc. In my experience (business apps, no gaming apps to speak of), there are no significant performance issues except with pixel manipulation (which increases memory load to the point of being impractical). – markE Sep 22 '15 at 05:19
  • @markE, can you clarify what you mean by edge cases? At the end of the day, whatever value you assign to `fillStyle`, it will be passed through to the underlying `CanvasRenderingContext2D` by the setter defined in the code above. – danvk Sep 22 '15 at 12:09
  • @danvk. If you're just creating an immediate-mode context proxy, these edges may not apply to you. But for my logging/serializing purposes, serializing a gradient requires saving the gradient arguments "outside the context" because they are not exposed after setting them. The drawImage and createPattern commands require that the image src be saved "outside the context". :-) – markE Sep 22 '15 at 16:21

0 Answers0