0

javascript コンテキスト オブジェクトの拡張機能を作成しています。

私が達成したいこと:

最後に、canvas という引数を 1 つだけ取る関数が必要です。そこから、自分で作成するあらゆる種類の追加機能を使用してそのキャンバス要素を拡張する必要があります。

したがって、基本的には、次のようにキャンバスを特別なキャンバスに変換します。

HTML

<canvas id='normal'></canvas>
<canvas id='special'></canvas>

Javascript

var normal = document.getElementById("normal"),
    special = document.getElementById("special");

var Extendedctx = function (canvas) {
    // Magic?
}

Extendedctx(special); // Extends the "special" canvas's context (ctx) with lots of extra functionality.

special.getContext('2d').nonBuiltinFunction(); // Should work
normal.getContext('2d').nonBuiltinFunction(); // Should throw an exception

ただし、通常のキャンバスはまったく同じままにする必要があります。したがって、組み込みの ctx-object のプロトタイプを拡張することは、実行可能な解決策ではありません。

私は次のことを行うことでこれを達成しようとしています:

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

    Extendedctx = function (canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.__proto__ = Object.create(this.ctx);
    return this
},  pad = new Extendedctx(canvas);

pad.fillRect(0, 0, 40, 40);

ただし、最終行で次の例外が発生します。

Uncaught TypeError: Illegal invocation 

次の SO 投稿の引用によると、これは理にかなっています。

コードでは、ネイティブ メソッドをカスタム オブジェクトのプロパティに割り当てています。support.animationFrame(function () {}) を呼び出すと、現在のオブジェクト (つまりサポート) のコンテキストで実行されます。ネイティブの requestAnimationFrame 関数が正しく機能するには、window のコンテキストで実行する必要があります。

ソース

したがって、それを修正するには、電話する必要がありますpad.fillRect.call(pad.ctx, 0, 0, 40, 40);

さて、これは本当にすぐに面倒で厄介になります。私が終わらせたいのは、次のことです。

pad.fillRect(0, 0, 40, 40);

私が使用したい他の継承された関数と同様に、単に機能します( eg ctx.arc)

ここで何をすべきか本当にわかりません。「javascriptで関数呼び出しを変更する」などの行に沿って何かをグーグルで検索してみました。本当に有用な結果はありません。

jsをいじる必要がある場合のjsfiddleは次のとおりです

4

2 に答える 2

4

あなたがやろうとしているのは、プロトタイプが 2D キャンバス コンテキストであるオブジェクトを作成することです。

まず、うまくいきませんが、汎用の Web ページ (たとえば、IE8 のためにまだ ES5 に依存できないページ) でこれを行う正しい方法は次のとおりです。

function getExtendedContext(canvas) {
    var ectx;

    // A one-off constructor function
    function ctor() { }

    // Set the object that will be assigned as a prototype
    // when using that ctor
    ctor.prototype = canvas.getContext("2d");

    // Create our object
    ectx = new ctor();

    // Add properties to it
    ectx.nonBuiltinFunction = function() { /* ... */ };

    // Return it
    return ectx;
}

// Usage
var ctx = getExtendedContext(document.getElementById("special"));
ctx.nonBuiltinFunction();
ctx.fillRect(0, 0, 40, 40);

[ES5 では を使用できますObject.createが、古いブラウザーでは正しくシムできません (半分シムしないことを強くお勧めします)。]

実例| ライブ ソース (ただし、これも機能しません)

問題は、キャンバス オブジェクトとそのコンテキスト オブジェクトが、JavaScript オブジェクトではなく DOM オブジェクトであることです。DOM オブジェクトは、ECMAScript 仕様で「ホスト」オブジェクトと呼ばれるものであり、JavaScript オブジェクトのように動作する必要はありません。ECMAScript 仕様には、これに関するさまざまな注意事項があります (ホストが提供する関数についても同様ですalert)。

少なくとも Chrome では、2d コンテキスト オブジェクトは JavaScript の継承に参加することを拒否しています。上記を呼び出すとctx.fillRect(0, 0, 40, 40);、質問で言及した例外が発生します。

Uncaught TypeError: Illegal invocation

その理由は、(呼び出し内で) が 2d コンテキスト オブジェクトであるfillRectかどうかを確認するためであり、そうではありません — これは、プロトタイプとしてコンテキストを持つ、作成したオブジェクトです。thisはコンテキストではないため、fillRectをスローします。TypeErrorthis

これは、やろうとしていることを実行するのがずっと難しくなるため、残念なことです。キャンバス要素の真のファサードを作成するには、または 2D コンテキストだけでも、膨大な量のコードを作成する必要があります: 2D コンテキストのすべてのメソッドを持つオブジェクトを作成する必要があります。が呼び出されると、ラップする実際の 2D コンテキストでそれらを呼び出します。さらに悪いことに、2d コンテキストには書き込み可能なプロパティがあるため、ES5 環境が当てにならない場合は、呼び出しを行う前に、基礎となるコンテキストでこれらすべてのプロパティを設定する必要があります。本当に醜い。

したがって、継承を使用することはできず、ファサードを使用するのは非常に見苦しいため、拡張機能を検討する必要があります。プロトタイプとして使用したり、ファサードにラップしたりするのではなく、2d コンテキスト オブジェクト自体を実際に拡張します。

少なくとも、Chrome は 2d コンテキスト オブジェクトにプロパティを追加することを許容するので、次のようにすることができます。

function getExtendedContext(canvas) {
    var ectx;

    ectx = canvas.getContext("2d");
    ectx.nonBuiltinFunction = function() { /* ... */ };

    return ectx;
}

実例| ライブソース

それが他のブラウザーで機能するかどうかは、テストする必要があるものです。各ホストは、ホスト オブジェクトを「封印」するなど、必要なことを自由に実行できるからです。

getContextあなたの例では、HTMLCanvasElementオブジェクト自体をオーバーライドしたいようです。Chrome では、次のことができます。

function extendCanvas(canvas) {
    var realGetContext;

    // Save a reference to the real `getContext` function        
    realGetContext = canvas.getContext;

    // Replace it with our own
    canvas.getContext = function(type) {
        // Call the real one
        var ctx = realGetContext.apply(this, arguments);

        // If the request was for a 2d canvas, extend it
        if (type === "2d") {
            extend2dContext(ctx);
        }

        // Return it
        return ctx;
    };

    return canvas;
}

function extend2dContext(ctx) {
    ctx.nonBuiltinFunction = function() {
        this.fillStyle = "blue";
        this.fillRect(41, 41, 30, 30);
    };

    return ctx;
}

// Usage
var canvas = extendCanvas(document.getElementById("someCanvas"));
var ctx = canvas.getContext("2d");
ctx.nonBuiltinFunction();

実例| ライブソース

...しかし、他のブラウザがそうするかどうかは、やはりテストする必要があります。このようにオブジェクトを拡張できない環境に遭遇した場合、選択肢は完全なファサードを実行するか (これは非常に見苦しくなります)、手続き型プログラミング (キャンバスを渡す関数) にフォールバックします。

于 2013-09-19T09:53:45.230 に答える
2

コンテキストに追加するプロパティをミックスインできます。

var canvas = document.getElementById('testcanvas');

var ExtendedCtx = function(canvas) {
    var ctx = canvas.getContext('2d');
    ctx.newProp = 20;
    ctx.newMethod = function(){
        return this.newProp/2;
    }
    return ctx;
}

var pad = ExtendedCtx(canvas);

pad.fillRect(0, 0, 40, 40);
console.log(pad.newProp, pad.newMethod(), pad.canvas); // 20, 10, canvas element
于 2013-09-18T22:09:35.247 に答える