S R T B H P N
Tests.html

Test PhotoSizer Element

Implements W3C/Google webcomponent

Click on image body to expand, click on title to contract.
Works with FireFox, Chrome, Safari, Edge, Opera.
Figure 1. ThreadPool
This is how this page uses the PhotoSizer Element:
  <photosizer-block src="Pictures/ThreadPool.JPG" width="200" class="photoSizerBlock left">
    <span style="font-family:'Comic Sans MS';">Figure 1. ThreadPool</span>
  </photosizer-block>
And this is how this pages styles the PhotoSizer Element:
    #github photosizer-block {
      --border:1px solid black;
      --box-shadow: 3px 3px 5px #999;
      --wrapperPadding: 5px 10px 10px 10px;
      --margin: 20px;
      --captionPadding: 0px 0px 5px 0px;
    }
    /* uncomment .photoBlock to use this class */
    /*#github .photoSizerBlock {
      --background-color: #efe;
      --font-family:Comic Sans MS;
      --font-weight: Bold;
      --border: 3px solid darkgreen;
      --captionPadding: 0px 0px 5px 0px;
      --wrapperPadding: 5px;
      --margin: 30px;
    }*/
View page source for all the details.

Here's the component code:
<script>
  /* custom webcomponent - PhotoSizerBlock*/

  class PhotoSizerBlock extends HTMLElement {
    constructor(...args) {
      const self = super(...args);
      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.innerHTML =
        `
          <style>
            #cont {
              display:flex;
              position:relative;
              padding: var(--padding);
            }
            #wrapper {
              width:min-content;
              padding: var(--wrapperPadding); //10px;
              margin: var(--margin);  //20px;
              background-color: var(--background-color);
              color: var(--color);
              box-shadow: var(--box-shadow);
              font-family: var(--font-family);
              font-size: var(--font-size);
              font-weight: var(--font-weight);
              border: var(--border);
              cursor: pointer;
              user-select:none;
              -webkit-user-select:none;
              -moz-user-select:none;
              -ms-user-select:none;
            }
            #closer {
              position:absolute;
              top:20px; right:8px;
              border:2px solid red;
              background-color:black;
              color:white;
              cursor:pointer;
              width:0.5em; height:1em;
            }
            #caption {
              text-align:center;
              padding-bottom:10px;
              background-color:white;
              padding: var(--captionPadding); //0px 0px 10px 0px;
            }
            img {
              user-select:none;
              -webkit-user-select:none;
              -moz-user-select:none;
              -ms-user-select:none;
            }
          </style>
          <div id="cont">
            <slot></slot>
            <div id="closer"></div>
            <div id="wrapper">
              <div id="caption"></div>
              <img />
            </div>
          </div>
        `;
      this.bigger = this.bigger.bind(this);
      this.smaller = this.smaller.bind(this);
      this.toggle = this.toggle.bind(this);
      return self;
    }
    connectedCallback() {
      this.url = this.getAttribute('src');
      this.imgElem = this.shadowRoot.querySelector('img');
      this.imgElem.setAttribute('src', this.url);
      this.width = this.getAttribute('width');
      if (isDefined(this.width))
        this.imgElem.setAttribute('width', this.width);
      this.height = this.getAttribute('height');
      if (isDefined(this.height))
        this.imgElem.setAttribute(this.height);
      this.caption = this.shadowRoot.querySelector('#caption');
      if (isDefined(this.caption)) {
        this.captionText = this.innerHTML;
        this.innerHTML = "";
        this.caption.innerHTML = this.captionText;
      }
      this.imgElem.addEventListener('click', this.bigger);
      this.caption.addEventListener('click', this.smaller);
      this.closer = this.shadowRoot.querySelector('#closer');
      this.closer.addEventListener('click', this.toggle);
    }
    bigger() {
      this.imgElem.width = Number(this.imgElem.width) * 1.25;
    }
    smaller() {
      this.imgElem.width = Number(this.imgElem.width) / 1.25;
    }
    toggle() {
      let wrpr = this.shadowRoot.getElementById("wrapper");
      if (isDefined(wrpr)) {
        if (wrpr.style.display === "none") {
          wrpr.style.display = "block";
          this.closer.style.right = "8px";
        }
        else {
          wrpr.style.display = "none";
          this.closer.style.right = "7px";
        }
      }
    }
  }
  /* register with DOM - don't need to use script block to execute */

  /*---------------------------------------------------------------------------
    * This listener is added because of a component lifecycle issue with chrome
    * See first reference, below.
    */
  document.addEventListener('DOMContentLoaded', function () {
    window.customElements.define('photosizer-block', PhotoSizerBlock);
  });
  /*
  * The listener, above, will be removed and replaced with this when chrome
  * component lifecycle is fixed.
  *
  * window.customElements.define('photosizer-block', PhotoSizerBlock);
  */
 </script>

References:

  1. Issue with Chrome loading innerHTML
  2. polyfills CDN
  3. customelements - google.com
  4. shadowdom - google.com
  5. webcomponents - htm5rocks.com
  6. attributes & properties - alligator.io
  7. styling webcomponent - css-tricks.com
  8. encapsulating style and structure - css-tricks.com
  9. creating custom component from scratch - css-tricks.com
  10. Using custom elements - mozilla.org
  11. introduction to webcomponents - webcomponents.org
  12. build web components - dev.to
  13. tutorial - robinwieruch.de
  14. introduction - grapecity.com