Apply existing reference to CanvasRendingContext2D

2019-02-20 23:10发布

I am trying to store a canvas reference in a global object and then apply that reference to an element instead of regenerating the canvas. here is my existing code. I hope that makes sense. thanks in advance!

waveformCache is assumed to be a global

var cL = document.getElementById('track' + trackId + 'WaveformL');
var cR = document.getElementById('track' + trackId + 'WaveformR');

if (waveformCache.hasOwnProperty(track.path))
{
    var waveformCacheItem = waveformCache[track.path];

    if (waveformCacheItem.hasOwnProperty('left'))
    {
        // restore canvas data here to cL element
    }
}
else
{
    waveformCache[track.path] = {};

    var left = track.data.getChannelData(0);

    var ctx1 = cL.getContext('2d');
    ctx1.save();
    ctx1.strokeStyle = 'rgb(49,73,11)';
    ctx1.translate(0, 55/2); //centers where the line drawing starts horizontally

    for(var i = 0; i < left.length; i += 200) {
        var x1 = Math.floor(track.waveformLength * i / left.length); //first parameter affects the length of the drawn waveform #ZOOM
        var y1 = left[i] * 55/2;

        ctx1.beginPath();
        ctx1.moveTo(x1, 0);
        ctx1.lineTo(x1 + 1, y1);
        ctx1.stroke();
    }
    ctx1.restore();

    waveformCache[track.path].left = ctx1;
}

2条回答
手持菜刀,她持情操
2楼-- · 2019-02-20 23:56

You can use a Path2D object to store your paths commands at. Then store the path in your global object. When you need to re-apply the path, simply stroke or fill using the stored path object.

For example:

var path = new Path2D();

...
path.moveTo(.. , ..);
path.lineTo(.. , ..);
etc.

Later when you need to recall the path:

ctx.stroke(path);

(A bonus is that you can initialize it using SVG paths. This means you can just define your path using SVG commands and store that as a single string. Reapply using the route of Path2D at a slight cost of performance when initializing.)

Path2D can be polyfilled for browsers which do not yet support it (see notes for special cases).

查看更多
唯我独甜
3楼-- · 2019-02-21 00:01

An outline of how to serialize an html5 canvas CanvasRendingContext2D

The canvas context (CanvasRendingContext2D ) holds the canvas' properties (styling, current transformation, etc).

Important! The context does not hold all the executed drawing commands that created the canvas content. Context Properties:

  • Coloring: strokeStyle, fillStyle(1), globalAlpha,

  • Line styles: lineWidth, lineCap, lineJoin, miterLimit,

  • Text Styles: font, textAlign, textBaseline,

  • Compositing: globalCompositeOperation,

  • Shadowing: shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY

(1) fillStyle is usually a string ('#ff0000'), but it can alternatively hold a reference to a gradient object or pattern object. To save the context's fillStyle, you will have to either ignore gradients / patterns or also serialize the gradient / pattern properties.

Here's how to save context properties into an object

var properties=['strokeStyle','lineWidth','font','globalAlpha',
    'globalCompositeOperation','shadowColor','shadowBlur',
    'shadowOffsetX','shadowOffsetY','lineCap','lineJoin',
    'miterLimit','textAlign','textBaseline'];

var serializedContext={}

for(var i=0;i<properties.length;i++){
    var prop=properties[i];
    serializedContext[prop]=context[prop];
}

// fillStyle can be a headache
if(typeof context.fillStyle === 'string'){
    serializedContext['fillStyle']=context.fillStyle;
}else{
    // do lots more work to serialize gradient or pattern :-O
}

Here's how to copy saved context properties into a new context:

var context=myOtherCanvas.getContext('2d');

for(var i=0;i<properties.length;i++){
    var prop=properties[i];
    context[prop]=serializedContext[prop];
}

// fillStyle can be a headache
if(typeof context.fillStyle === 'string'){
    serializedContext['fillStyle']=context.fillStyle;
}else{
    // do lots more work to re-establish gradient or pattern :-O
}

Re-executing the drawings

If you want to re-execute all the drawings commands, you must save the commands and their arguments.

From your example code, it looks like your drawings involve line segments(moveTo & lineTo) so you can save each segment as a segment-object in an array of segment-objects.

var segments=[];

segments.push({moveX:10, moveY:20, lineX:100, lineY:35});

... and push all the other line segments

And then you can "replay" the line-segment drawing commands after you've reset all the context properties:

// redraw every line segment
ctx.beginPath()
for(var i=0;i<segments.length;i++){
    var s=segments[i];
    ctx.moveTo(s.moveX,s.moveY);
    ctx.lineTo(s.lineX,s.lineY);
}
ctx.stroke();

You can also serialize and replay all the common drawing commands (arc, beginPath, bezierCurveTo , clearRect, clip, closePath, fill, fillRect, fillText, lineTo, moveTo, quadraticCurveTo, rect, restore, rotate, save, scale, setTransform, stroke, strokeRect, strokeText, transform, translate). Save each command name & associated arguments in an object and save all those command-objects in an array.

These commands return values so you will need to do more work to handle them: measureText, getImageData (putImageData), toDataURL, isPointInPath, isPointInStroke, createImageData, createLinearGradient, createRadialGradient, createPattern. Luckily, these commands are used are used less often than the more common (simpler) commands.

About portability

If you use this method of saving all properties & drawing commands into object arrays, you can easily serialize them all into JSON strings with JSON.stringify and you can easily deserialize them back into object arrays with JSON.parse.

Having your canvas properties & drawing commands serialized to strings means that you can easily transport them to a server for storage and then fetch them for replaying.

查看更多
登录 后发表回答