Component Code
The tag <image-viewer> creates an instance of the
ImageViewer class. The class definition extends from lines 1-94.
Component attributes are defined in lines 7-14. Attributes allow user
applications to modify encapsulated component values.
shadowDOM is defined in lines 17-80. It includes styles, lines 18-70 and
HTML element structure in lines 72-79.
Event listeners are defined in lines 83-87, that support expanding the view
by clicking on img body and contracting view by clicking on title.
The Helper function resizeImage(scaleFactor) is
defined in lines 90-93.
Shadow DOM
A W3C web component is defined entirely in JavaScript as illustrated in the right
panel.
The shadowDOM provides styling and HTML markup for the component using a JavaScript
expression:
this.shadowRoot.innerHTML = `[markup goes here]`;
.
Note that a style block is placed near the top of the component which
lies in the component element of the body, not the head. This is done
to encapsulate the component styles so that they are not corrupted by styles
used in the application, perhaps in an included style library.
Structure
This markup defines the structure of the image-viewer component. The outer div
provides padding to keep content from touching the image-viewer when it is
floated to the left or right.
The next div provides the visible part of the component, e.g., its title and
image. Note that the img src and width are provided by attributes the
application supplies in the <image-viewer>
declaration.
Listeners
The primary purpose of this component is to enlarge or diminish the size of
an image using button clicks on image (enlarge) or title (diminish).
The first listener awaits clicks on the title which contracts the image.
The second awaits clicks on the image then enlarges it.
The final piece registers the <image-viewer> with
this component class. That causes the browser's rendering engine to create
an instance of the ImageViewer class for each occurance of the tag.
1 class ImageViewer extends HTMLElement {
2 constructor() {
3 super();
4 this.attachShadow({ mode: 'open' });
5
6 // Get attributes
7 this.imgSrc = this.getAttribute('img-src') || '';
8 this.imgWidth = this.getAttribute('img-width') || '400';
9 const hasBgAttr = this.hasAttribute('bg-color');
10 const componentBg = hasBgAttr ? this.getAttribute('bg-color') : 'white';
11 const titleBg = this.getAttribute('title-bg-color') || 'transparent';
12
13 // Wrapper background uses --light fallback to white
14 const wrapperBg = 'var(--light, white)';
15
16 // Shadow DOM markup
17 this.shadowRoot.innerHTML = `
18 <style>
19 :host {
20 display: inline-block; /* allow float and shrink to content */
21 }
22
23 .wrapper {
24 padding: 1rem; /* outer padding to keep text off the border when floated */
25 box-sizing: border-box;
26 background-color: ${wrapperBg};
27 }
28
29 .component {
30 border: 2px solid var(--dark, #333); /* use client's --dark or fallback */
31 padding: 0.5rem;
32 display: flex;
33 flex-direction: column;
34 user-select: none;
35 width: min-content;
36 box-shadow: 5px 5px 5px #999;
37 box-sizing: border-box;
38 background-color: ${componentBg};
39 }
40
41 .title {
42 display: flex;
43 font-family: "Comic Sans MS", cursive, sans-serif;
44 font-weight: bold;
45 cursor: pointer;
46 max-width: 100%;
47 margin-bottom: 8px;
48 line-height: 1.0rem;
49 flex-wrap: wrap;
50 word-wrap: break-word;
51 overflow-wrap: break-word;
52 white-space: wrap;
53 color: var(--dark, #333); /* title text uses --dark or fallback */
54 background-color: ${titleBg};
55 padding: 0.25rem 0.5rem;
56 }
57
58 .image {
59 display: block;
60 flex: 0 0 auto;
61 cursor: pointer;
62 transition: transform 0.2s ease-in-out;
63 }
64
65 img {
66 display: block;
67 height: auto;
68 /* removed max-width so explicit width adjustments take effect */
69 }
70 </style>
71
72 <div class="wrapper">
73 <div class="component">
74 <div class="title" part="title"><slot></slot></div>
75 <div class="image">
76 <img id="img" src="${this.imgSrc}" width="${this.imgWidth}">
77 </div>
78 </div>
79 </div>
80 `;
81
82 // Event listeners
83 this.titleElement = this.shadowRoot.querySelector('.title');
84 this.imageElement = this.shadowRoot.querySelector('#img');
85
86 this.titleElement.addEventListener('click', () => this.resizeImage(1 / 1.2));
87 this.imageElement.addEventListener('click', () => this.resizeImage(1.2));
88 }
89
90 resizeImage(scaleFactor) {
91 const currentWidth = parseFloat(window.getComputedStyle(this.imageElement).width);
92 this.imageElement.style.width = `${currentWidth * scaleFactor}px`;
93 }
94 }
95
96 customElements.define('image-viewer', ImageViewer);
16 // Shadow DOM markup
17 this.shadowRoot.innerHTML = `
18 <style>
19 :host {
20 display: inline-block; /* allow float and shrink content */
21 }
22
23 .wrapper {
24 padding: 1rem; /* outer padding to keep text off the border when floated */
25 box-sizing: border-box;
26 background-color: ${wrapperBg};
27 }
28
29 .component {
30 border: 2px solid var(--dark, #333); /* use client's --dark or fallback */
31 padding: 0.5rem;
32 display: flex;
33 flex-direction: column;
34 user-select: none;
35 width: min-content;
36 box-shadow: 5px 5px 5px #999;
37 box-sizing: border-box;
38 background-color: ${componentBg};
39 }
40
41 .title {
42 display: flex;
43 font-family: "Comic Sans MS", cursive, sans-serif;
44 font-weight: bold;
45 cursor: pointer;
46 max-width: 100%;
47 margin-bottom: 8px;
48 line-height: 1.0rem;
49 flex-wrap: wrap;
50 word-wrap: break-word;
51 overflow-wrap: break-word;
52 white-space: wrap;
53 color: var(--dark, #333); /* title text uses --dark or fallback */
54 background-color: ${titleBg};
55 padding: 0.25rem 0.5rem;
56 }
57
58 .image {
59 display: block;
60 flex: 0 0 auto;
61 cursor: pointer;
62 transition: transform 0.2s ease-in-out;
63 }
64
65 img {
66 display: block;
67 height: auto;
68 /* removed max-width so explicit width adjustments take effect */
69 }
70 </style>
71
72 <div class="wrapper">
73 <div class="component">
74 <div class="title" part="title"><slot></slot></div>
75 <div class="image">
76 <img id="img" src="${this.imgSrc}" width="${this.imgWidth}">
77 </div>
78 </div>
79 </div>
80 `;
81
82 // Event listeners
83 this.titleElement = this.shadowRoot.querySelector('.title');
84 this.imageElement = this.shadowRoot.querySelector('#img');
85
86 this.titleElement.addEventListener('click', () => this.resizeImage(1 / 1.2));
87 this.imageElement.addEventListener('click', () => this.resizeImage(1.2));
88 }
89
90 resizeImage(scaleFactor) {
91 const currentWidth = parseFloat(window.getComputedStyle(this.imageElement).width);
92 this.imageElement.style.width = `${currentWidth * scaleFactor}px`;
93 }
94 }
95
96 customElements.define('image-viewer', ImageViewer);