简体   繁体   中英

Hook changes to lineWidth on HTML5 canvas context

I want to enforce a miterLimit in actual pixels rather than as a ratio of the lineWidth. To do this, I'd like to hook any changes to lineWidth, and set the miterLimit simultaneously and automatically. I've used custom setters on objects before, but if I replace the lineWidth setter, I don't know of any way to actually pass the value to set on through to the actual canvas context.

Is there some way (compatible on IE9+) that I can listen to changes to a given key on an object without changing the behavior of setting that value?

Your getter/setter idea is a good one...

How about just adding a property definition to your context object?

Add a myLineWidth property to your context object and then set the linewidth using context.myLineWidth instead of context.lineWidth .

Some example code:

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

Object.defineProperty(ctx, 'myLineWidth', {
    get: function() {
        return(this.lineWidth);
    },
    set: function(newWidth) {
        this.lineWidth=newWidth;
        console.log("Executed myLineWidth setter: ",this.lineWidth);        
    }
});

ctx.myLineWidth=5;
ctx.strokeRect(100,100,50,50);

Alternate Method using Encapsulation:

JavaScript does have true inheritance so it's not possible to inherit & override lineWidth .

The next best thing would be encapsulating the context object. Then all coders can use the encapsulated version of the context using the standard property and method syntax (no need for myLineWidth). If needed, here's a how-to: http://aboutcode.net/2011/10/04/efficient-encapsulation-of-javascript-objects.html .

I did a similar encapsulation in order to log the context drawings. Below, I've tried to snip the encapsulation code from one of my projects. You can ignore my special handling of drawImage and gradients as I needed to grab values from these that you won't need to grab--just add those methods to the returnMethods[] array.

Some example code for you to start with:

// Log all context drawings
// Creates a proxy class wrapping canvas context
function LoggedContext(canvas,context) {
    var self = this;
    this.canvas=canvas;
    this.context=context;
    this.imageURLs=[];
    this.log=[];
    this.gradients=[];
    this.patterns=[];
    this.init(self);

}

// maintain urls of images used
LoggedContext.prototype.imageIndex=function(url){
    var i=a.indexOf(url);
    // found
    if(i>-1){ return(i); }
    // not found -- added
    a.push(url);
    return(a.length-1);
}
///////////////////////////////////////////
// These methods require special handling
// (drawImage:need image.src, gradients:need gradDefs & colors)
//
LoggedContext.prototype.drawImage=function(){
    this.context.drawImage.apply(this.context,arguments);
    var args = Array.prototype.slice.call(arguments);
    args[0]=arguments[0].src;
    args.unshift(2,"drawImage");
    var sArgs=JSON.stringify(args);
    this.log.push(sArgs);
    return(this);
}
//
LoggedContext.prototype.createLinearGradient =function(x1,y1,x2,y2){
    var gradient=this.context.createLinearGradient(x1,y1,x2,y2);
    gradient.context=this;
    gradient.gradientID=this.gradients.length;
    this.gradients.push({line:{x1:x1,y1:y1,x2:x2,y2:y2},stops:[]});
    gradient.baseAddColorStop=gradient.addColorStop;
    gradient.addColorStop=function(stop,color){
        this.context.gradients[this.gradientID].stops.push({stop:stop,color:color});
        this.baseAddColorStop(stop,color);
    }
    return(gradient);
}
//
LoggedContext.prototype.createPattern =function(i,r){
    var pattern=this.context.createPattern(i,r);
    pattern.patternID=this.patterns.length;
    this.patterns.push({src:i.src,repeat:r});
    return(pattern);
}
//
LoggedContext.prototype.createRadialGradient =function(sx,sy,sr,ex,ey,er){
    var gradient=this.context.createRadialGradient(sx,sy,sr,ex,ey,er);
    gradient.context=this;
    gradient.gradientID=this.gradients.length;
    this.gradients.push({circles:{sx:sx,sy:sy,sr:sr,ex:ex,ey:ey,er:er},stops:[]});
    gradient.baseAddColorStop=gradient.addColorStop;
    gradient.addColorStop=function(stop,color){
        this.context.gradients[this.gradientID].stops.push({stop:stop,color:color});
        this.baseAddColorStop(stop,color);
    }
    return(gradient);
}

// load the proxy object with all properties & methods of the context
LoggedContext.prototype.init=function(self){

    // define public context properties
    var properties={
        //
        fillStyle:"black",
        strokeStyle:"black",
        lineWidth:1,
        font:"10px sans-serif",
        //
        globalAlpha:1.00,
        globalCompositeOperation:"source-over",
        //
        shadowColor:"black",
        shadowBlur:0,
        shadowOffsetX:0,
        shadowOffsetY:0,
        //
        lineCap:"butt",   // butt,round,square
        lineJoin:"miter", // miter,round,miter
        miterLimit:10,
        //
        textAlign:"start",
        textBaseLine:"alphabetic",
    };

    // encapsulate public properties
    for (var i in properties) {
        (function(i) {
            if(!(i=="fillStyle")){
                Object.defineProperty(self, i, {
                    get: function () {
                        return properties[i];
                    },
                    set: function (val) {
                        this.log.push(JSON.stringify([1,i,val]));
                        properties[i] = val;
                        this.context[i]=val;
                    }
                })
            }else{
                Object.defineProperty(self, i, {
                    get: function () {
                        return properties[i];
                    },
                    set: function (val) {
                        if(typeof val ==="object"){
                            if(val.gradientID>=0){
                                this.log.push(JSON.stringify([1,i,"gradient",val.gradientID]));
                            }else if(val.patternID>=0){
                                this.log.push(JSON.stringify([1,i,"pattern",val.patternID]));
                            }
                        }else{
                            this.log.push(JSON.stringify([1,i,val]));
                        }
                        properties[i] = val;
                        this.context[i]=val;
                    }
                })
            }
        })(i);
    }

    // define public context methods
    var methods = ['arc','beginPath','bezierCurveTo','clearRect','clip',
      'closePath','fill','fillRect','fillText','lineTo','moveTo',
      'quadraticCurveTo','rect','restore','rotate','save','scale','setTransform',
      'stroke','strokeRect','strokeText','transform','translate','putImageData'];

    // encapsulate public methods
    for (var i=0;i<methods.length;i++){   
        var m = methods[i];
        this[m] = (function(m){
            return function () {
                this.context[m].apply(this.context, arguments);
                // "arguments" is not a real array--so convert it
                var args = Array.prototype.slice.call(arguments);
                args.unshift(2,m);
                var sArgs=JSON.stringify(args);
                this.log.push(sArgs);
                return(this);
        };}(m));
    }

    // define context methods that return values
    var returnMethods = ['measureText','getImageData','toDataURL',
      'isPointInPath','isPointInStroke'];

    // encapsulate return methods
    for (var i=0;i<returnMethods.length;i++){   
        var m = returnMethods[i];
        this[m] = (function(m){
            return function () {
                return(this.context[m].apply(this.context, arguments));
        };}(m));
    }

}  // end init()

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