Canvas state lost after changing size

The accepted answer no longer works because some properties are deprecated and hence give runtime error when trying to set deprecated properties.
Here's a fix for Khauri MacClain's answer.

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);
}

I also found this somewhat annoying and I'm very interested to see if there are other solutions to this. I'd also like to see a reason as to why the state has to be reset and the state stack cleared when the canvas is resized. Really it's unintuitive behavior that you wouldn't expect and even MDN doesn't mention it, so it's probably pretty easy to make this mistake.

The absolute best way is to restructure your code in such a manner that all draw operations can be redone after the canvas is resized since you're gonna have to redraw everything anyway, you might as well be setting all the ctx states after you resize anyway. (i.e. make and utilize a 'draw' function that can be called after you resize the canvas)

If you really want to keep the state then I'd recommend making your own save/restore state stack and perhaps a resize function while you're at it. I'm not gonna judge you and say it's a bad idea...

Let's say I wanted to resize the canvas to the exact width and height of some text before drawing the text.

Normally I would have to set the font for the text first, then measure the text, then resize the canvas, but since resizing the canvas resets the state, I'd then have to set the font again like so:

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

As an (admittedly over-complicated) workaround, I'd save and restore the state using my custom save-restore functions before and after resizing, respectively.

And yes, I do see the irony in replacing one extra line of code with around 30 or so extra lines of code in this particular example.

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>