更改尺寸后,画布状态丢失

时间:2017-12-31 18:44:31

标签: javascript html5-canvas

我想调整画布的大小,但是当我这样做时,上下文会重置,丢失当前的fillStyle,转换矩阵等。ctx.save()ctx.restore()函数没有按照我最初的预期工作:

function resizeCanvas(newWidth, newHeight) {
    ctx.save();
    canvas.width = newWidth; // Resets context, including the save state
    canvas.height = newHeight;
    ctx.restore(); // context save stack is empty, this does nothing
}

经过一段时间的研究,我似乎无法在调整大小后找到保存和恢复画布上下文的好方法。我能想到的唯一方法是手动保存每个属性,这看起来既凌乱又缓慢。 ctx.save()也没有返回保存状态,我也不知道访问上下文堆栈的方法。

有没有更好的方法,或者我注定要使用这样的东西:

function resizeCanvas(newWidth, newHeight) {
    let fillStyle = ctx.fillStyle;
    let strokeStyle = ctx.strokeStyle;
    let globalAlpha= ctx.globalAlpha;
    let lineWidth = ctx.lineWidth;
    // ...

    canvas.width = newWidth;
    canvas.height = newHeight;

    ctx.fillStyle = fillStyle;
    ctx.strokeStyle = strokeStyle;
    ctx.globalAlpha= globalAlpha;
    ctx.lineWidth = lineWidth;
    // ...
}

3 个答案:

答案 0 :(得分:1)

我也觉得这有点烦人,我很想知道是否有其他解决方案。我还希望看到为什么状态必须被重置并且在调整画布大小时清除状态堆栈的原因。真的是你不会期望的那种不直观的行为,甚至MDN都没有提到它,所以犯这个错误可能很容易。

绝对最好的方法是重新调整代码,以便在调整画布大小后重绘所有绘制操作,因为无论如何你都需要重绘所有内容,你可能会以及在你调整大小后设置所有的ctx状态。 (即制作并使用可在调整画布大小后调用的“绘制”功能)

如果你真的想要保持状态,那么我建议你自己创建自己的保存/恢复状态堆栈,也许还有一个调整大小的功能。我不会评判你,并说这是一个坏主意......

假设我想在绘制文本之前将画布调整为某些文本的确切宽度和高度。

通常我必须首先设置文本的字体,然后测量文本,然后调整画布大小,但是由于调整画布的大小会重置状态,所以我必须再次设置字体,如下所示:

ctx.font = "48px serif"
width = ctx.measureText('Hello World').width
canvas.width = width
ctx.font = "48px serif"

作为一种(不可否认的过于复杂)的解决方法,我分别在调整大小之前和之后使用我的自定义保存 - 恢复功能保存和恢复状态。

是的,我确实看到在这个特定的例子中用大约30个额外的代码行替换一行额外代码时具有讽刺意味。



let canvas = document.querySelector('canvas')
  , ctx = canvas.getContext('2d')
  , stack = []

function save(ctx){
  let state = {}
  for(let property in ctx){
    if(property == 'canvas')
      continue
    if(typeof ctx[property] == 'function')
      continue
    state[property] = ctx[property]
  }
  stack.push(state)
}

function restore(ctx){
  let state = stack.pop() || {}
  for(let property in state){
    ctx[property] = state[property]
  }
}

function resize(ctx, width, height){
  save(ctx)
  ctx.canvas.width = width || canvas.width;
  ctx.canvas.height = height || canvas.height;
  restore(ctx)
}


//////////////    EXAMPLE    ////////////////


let index = 0
  , words = ["Our", "Words", "Are", "Dynamic"];

(function change(){
  let font_size = ~~(Math.random() * 150 + 16)
  let word = words[index]
  index = (index + 1) % words.length
  
  ctx.font = font_size+"px serif"
  ctx.textBaseline = "hanging"
  ctx.fillStyle = "white"
  resize(ctx, ctx.measureText(word).width, font_size)
  ctx.fillText(word, 0, 0)
  
  setTimeout(change, 750)
})()

canvas{
  background-color : orange
}

<canvas></canvas>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

我正在尝试获取由库生成的部分绘制的画布,因此在发出所有绘制命令之前实际上无法调整大小,并对其进行扩展以在其旁边添加一些文本。 @markE在this question上删除了一个答案,这对我有所帮助。瞧,您可以制作一个任意大小的新canvas元素,然后在其上“绘制”旧的(较小的)画布:

const libCanvas = myLib.getCanvas();
const newCanvas = document.createElement("canvas");
const ctx = newCanvas.getContext("2d");
ctx.font = MY_FONT;
newCanvas.width = libCanvas.width + ctx.measureText(myStr).width;
ctx.drawImage(libImage, 0, 0);
ctx.font = MY_FONT; // Changing width resets the context font
ctx.fillText(myStr, libCanvas.width, libCanvas.height / 2);

现在newCanvas的宽度与旧画布一样,加上我要绘制的文本的宽度,并且在左侧保留了库生成的画布的内容,而在右侧。

答案 2 :(得分:0)

已接受的答案不再起作用,因为某些属性已被弃用,因此在尝试设置已弃用的属性时会给出运行时错误。
这是Khauri MacClain答案的解决方案。

function save(ctx){
    let props = ['strokeStyle', 'fillStyle', 'globalAlpha', 'lineWidth', 
    'lineCap', 'lineJoin', 'miterLimit', 'lineDashOffset', 'shadowOffsetX',
    'shadowOffsetY', 'shadowBlur', 'shadowColor', 'globalCompositeOperation', 
    'font', 'textAlign', 'textBaseline', 'direction', 'imageSmoothingEnabled'];
    let state = {}
    for(let prop of props){
      state[prop] = ctx[prop];
    }
    return state;
}

function restore(ctx, state){
    for(let prop in state){
      ctx[prop] = state[prop];
    }
}

function resize(ctx, width, height){
    let state = save(ctx);
    ctx.canvas.width = width || canvas.width;
    ctx.canvas.height = height || canvas.height;
    restore(ctx, state);
}