SO:<full-size-canvas>

by WebComponents

HTML

<full-size-canvas width="200"></full-size-canvas>

<script>
  customElements.define("full-size-canvas", class extends HTMLElement {
  	static observedAttributes = ["width","height"];
    attributeChangedCallback(name,oldValue,newValue){
    		if ( this.canvas && oldValue !== newValue ) {
        	this[name] = newValue;
          this.updateCanvas();
        }
    }
    get width(){
    	return ~~(this.getAttribute("width") || 100);
    }
    set width(v=this.width){
    	this.setAttribute("width",(this.canvas.width = v || 100));
    }
    get height(){
    	return ~~(this.getAttribute("height") || 100);
    }
    set height(v=this.height){
    	this.setAttribute("height",(this.canvas.height = v || 100));
    }
		connectedCallback() {
      //this.width = 200, this.height = 80;
      const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props);
      Object.assign(this.style, {
        display: "inline-block",
        resize: "both",
        border: "1px solid black",
        overflow: "auto"
      });
      this.attachShadow({
          mode: "open"
        })
        .append(
          createElement("style", {
            innerHTML: `canvas {vertical-align:top}`,
          }),
          this.canvas = createElement("canvas")
        );
      this.updateCanvas(this.width, this.height, "grey");
      let observer = new ResizeObserver((mutations) => {
        let {
          width,
          height
        } = mutations[0].contentRect;
        if ((this.canvas.width !== width || this.canvas.height !== height)) {
          observer.unobserve(this); // do not observe on repaint
          this.updateCanvas(width,height);
          // Re-enable the observer after the current call stack is clear
          // also prevents Observer undelivered notifications warning
          setTimeout(() => observer.observe(this));
        } // if
      }); // observer
      observer.observe(this);
    } // connectedCallback
    updateCanvas(width =...