What makes a CanvasRenderingContext2D a CanvasRenderingContext2D?

会有一股神秘感。 提交于 2021-02-08 07:57:02

问题


Consider the following web page.

<html>
    <body>
        <canvas id="canvas" width="300" height="300" style="border:1px solid #000000;">
        </canvas>
    </body>
</html>

I open this page in Firefox, open the JS console and type the following.

> document.getElementById("canvas").getContext("2d")

The output is as follows:

CanvasRenderingContext2D { canvas: canvas#canvas, mozCurrentTransform: (6) […], mozCurrentTransformInverse: (6) […], mozTextStyle: "10px sans-serif", mozImageSmoothingEnabled: true, globalAlpha: 1, globalCompositeOperation: "source-over", strokeStyle: "#000000", fillStyle: "#000000", filter: "none" }

On the other hand, if I create an object myself and copy all of the guts of the CanvasRenderingContext2D, it's still just a plain object.

var realContext = document.getElementById("canvas").getContext("2d")
var myContext = new Object()
for (var property in realContext) {
    myContext[property] = realContext[property];
}

myContext
Object { drawImage: drawImage(), beginPath: beginPath(), fill: fill(), stroke: stroke(), clip: clip(), isPointInPath: isPointInPath(), isPointInStroke: isPointInStroke(), createLinearGradient: createLinearGradient(), createRadialGradient: createRadialGradient(), createPattern: createPattern(), … }

What makes a CanvasRenderingContext2D a CanvasRenderingContext2D?

As a corollary, how can I turn my plain old object into a CanvasRenderingContext2D?


Edit: I don't care about what the JS console says. I do care that I cannot use my new context the same way I use the original one.

myContext.save()
TypeError: 'save' called on an object that does not implement interface CanvasRenderingContext2D

The goal is to be able to use the new object exactly like the old one and draw on the original canvas.

Edit: I've looked for solutions which do not require modification of the source code of the website using canvas.


回答1:


Here's a snippet that will start logging all accesses to any CanvasRenderingContext2D. I disabled the stack snippet console because it throws some errors trying to serialize the console.log() output, so just check the developer console for the output using F12.

function intercept(value) {
  switch (typeof value) {
    case 'function':
    case 'object':
      return new Proxy(value, {
        get(target, key) {
          const value = Reflect.get(target, key);
          console.log('handler.get', target, key, value);
          return intercept(value);
        },
        set(target, key, value) {
          const result = Reflect.set(target, key, value);
          console.log('handler.set', target, key, value);
          return result;
        },
        apply(target, thisArg, args) {
          const value = Reflect.apply(target, thisArg, args);
          console.log('handler.apply', thisArg, target, ...args, value);
          return intercept(value);
        }
      });
    default:
      return value;
  }
}

const ownPropertyDescriptorEntries = Object.entries(
  Object.getOwnPropertyDescriptors(
    CanvasRenderingContext2D.prototype
  )
);
const interceptedPropertyDescriptorEntries = ownPropertyDescriptorEntries.map(
  ([key, { get, set, value, ...descriptor }]) => [
    key, get || set ? {
      ...descriptor,
      get() {
        console.log('descriptor.get', this, key);
        return intercept(Reflect.apply(get, this, []));
      },
      set(value) {
        console.log('descriptor.set', this, key, value);
        return intercept(Reflect.apply(set, this, [value]));
      }
    } : {
      ...descriptor,
      value: intercept(value)
    }
  ]
);

Object.defineProperties(
  CanvasRenderingContext2D.prototype,
  Object.fromEntries(
    interceptedPropertyDescriptorEntries
  )
);

const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');

console.log(context.lineWidth);
context.fillStyle = 'red';
context.getImageData(0, 0, 100, 100).data[0] = 255;
<canvas></canvas>
descriptor.get CanvasRenderingContext2D lineWidth
1
descriptor.set CanvasRenderingContext2D fillStyle red
handler.apply CanvasRenderingContext2D ƒ getImageData() { [native code] } 0 0 100 100 ImageData
handler.get ImageData data Uint8ClampedArray(40000)
handler.set Uint8ClampedArray(40000) 0 255

References

  • Proxy
  • handler.get
  • handler.set
  • handler.apply
  • Reflect.get()
  • Reflect.set()
  • Reflect.apply()
  • Object.getOwnPropertyDescriptors()
  • Object.defineProperty()


来源:https://stackoverflow.com/questions/52303490/what-makes-a-canvasrenderingcontext2d-a-canvasrenderingcontext2d

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