Skip to content

Commit 67b1fa8

Browse files
committed
refactor to use a 1x1 pixel & display: contents the host
1 parent c49d7a3 commit 67b1fa8

File tree

3 files changed

+69
-66
lines changed

3 files changed

+69
-66
lines changed

examples/with.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
<h1>With lightning-image</h1>
1818
<p>DOMComplete: <span class="dom-complete-time">N/A</span></p>
1919

20-
<lightning-image src="https://placehold.it/399x399" width="399" height="399" alt="399 Placeholder"></lightning-image>
20+
<lightning-image src="https://placehold.it/399x399" alt="Placeholder image"></lightning-image>
2121

2222
<!-- Generate 100 images to enable scrolling down the page & showcase the lazy loading functionality -->
2323
<script>
2424
for (let i = 400; i < 500; i++) {
2525
const img = document.createElement('lightning-image');
26-
img.width = i;
27-
img.height = i;
28-
img.src = `https://placehold.it/${i}`;
26+
img.setAttribute('width', `${i}`);
27+
img.setAttribute('height', `${i}`);
28+
img.setAttribute('src', `https://placehold.it/${i}`);
2929

3030
document.body.appendChild(img);
3131
}

examples/without.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
<h1>Without lightning-image</h1>
1717
<p>DOMComplete: <span class="dom-complete-time">N/A</span></p>
1818

19-
<img src="https://placehold.it/399x399" width="399" height="399" alt="" />
19+
<img src="https://placehold.it/399x399" alt="Placeholder image" />
2020

2121
<!-- Generate 100 images to enable scrolling down the page & showcase the lazy loading functionality -->
2222
<script>
2323
for (let i = 400; i < 500; i++) {
2424
const img = document.createElement('img');
25-
img.width = i;
26-
img.height = i;
27-
img.src = `https://placehold.it/${i}x${i}`;
25+
img.setAttribute('width', `${i}`);
26+
img.setAttribute('height', `${i}`);
27+
img.setAttribute('src', `https://placehold.it/${i}`);
2828

2929
document.body.appendChild(img);
3030
}

lightning-image.js

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
template.innerHTML = `
1010
1111
<style>
12-
.lightning-image {
13-
display: inline-block;
12+
:host(lightning-image) {
13+
display: inline; /* if display: contents is not supported, then use the same default display style as an img tag */
14+
display: contents; /* treat the lightning-image container as if it isn't there */
1415
}
1516
</style>
1617
17-
<div class="lightning-image">
18-
</div>
19-
2018
`;
2119

2220

@@ -29,71 +27,68 @@
2927
*
3028
*/
3129
class LightningImage extends HTMLElement {
30+
/**
31+
* This is a simple 1x1 white pixel encoded as base64.
32+
*/
33+
static PIXEL_BASE_64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQIHWP4DwABAQEANl9ngAAAAABJRU5ErkJggg==';
34+
3235
constructor() {
3336
super();
3437

35-
// this is obviously not correct, I'm just not sure of the proper way to keep properties
36-
// and attributes in sync right now.
37-
if (!this.src && this.getAttribute('src')) {
38-
this.src = this.getAttribute('src');
39-
}
40-
41-
if (!this.getAttribute('src') && this.src) {
42-
this.setAttribute('src', this.src);
43-
}
44-
45-
if (!this.width && this.getAttribute('width')) {
46-
this.width = this.getAttribute('width');
47-
}
48-
49-
if (!this.getAttribute('width') && this.width) {
50-
this.setAttribute('width', this.width);
51-
}
38+
// https://developers.google.com/web/fundamentals/web-components/best-practices#create-your-shadow-root-in-the-constructor
39+
this.attachShadow({mode: 'open'});
40+
this.shadowRoot.appendChild(this.template());
41+
}
5242

53-
if (!this.height && this.getAttribute('height')) {
54-
this.height = this.getAttribute('height');
55-
}
43+
get src() {
44+
return this.getAttribute('src');
45+
}
5646

57-
if (!this.getAttribute('height') && this.height) {
58-
this.setAttribute('height', this.height);
47+
/**
48+
* Ensure src property is reflected to an attribute.
49+
* https://developers.google.com/web/fundamentals/web-components/customelements#properties_and_attributes
50+
*/
51+
set src(value) {
52+
if (!value) {
53+
return this.removeAttribute('src');
5954
}
6055

61-
this.img = this.getImageToBeInserted();
62-
63-
this.attachShadow({mode: 'open'});
64-
this.shadowRoot.appendChild(this.template());
56+
this.setAttribute('src', value);
6557
}
6658

6759
connectedCallback() {
68-
if (this.supportsNativeLazyLoading()) {
69-
this.img.loading = 'lazy';
70-
this.insertImage();
71-
72-
return;
60+
// if the browser supports native lazy loading (only Chrome at time of writing), then
61+
// simply use that instead of using our own lazy loading implementation.
62+
if (!this.supportsNativeLazyLoading()) {
63+
this.setupObserver();
7364
}
74-
75-
// no native lazy loading for the browser
76-
this.setupObserver();
7765
}
7866

7967
template() {
68+
// See the note that indicates cloning is better than setting innerHTML
69+
// https://developers.google.com/web/fundamentals/web-components/customelements#shadowdom
8070
const cloned = template.content.cloneNode(true);
8171

82-
const container = cloned.querySelector('.lightning-image')
83-
const width = this.getAttribute('width') || 0;
84-
const height = this.getAttribute('height') || 0;
85-
container.style.width = width + 'px';
86-
container.style.height = height + 'px';
72+
// we want to create an img tag element that is the same as the lightning-image
73+
// component, except it has a src that will not cause any additional network requests
74+
const tpl = document.createElement('template');
75+
tpl.innerHTML = replaceTag(this);
76+
const imgTag = tpl.content.firstChild;
8777

88-
return cloned;
89-
}
78+
if (this.supportsNativeLazyLoading()) {
79+
// for browsers that support native lazy loading, return the true img tag
80+
// with the real src but ensure it has the loading=lazy attribute
81+
imgTag.loading = 'lazy';
82+
imgTag.src = this.src;
83+
} else {
84+
// for browsers without native lazy loading support, replace the src with a very simple pixel image.
85+
imgTag.src = LightningImage.PIXEL_BASE_64;
86+
}
9087

91-
getImageToBeInserted() {
92-
const template = document.createElement('template');
93-
template.innerHTML = replaceTag(this);
94-
const img = template.content.firstChild;
88+
// add the dynamic img into our static template
89+
cloned.appendChild(imgTag);
9590

96-
return img;
91+
return cloned;
9792
}
9893

9994
supportsNativeLazyLoading() {
@@ -104,18 +99,22 @@
10499
const observer = new IntersectionObserver(entries => {
105100
entries.forEach(entry => {
106101
if (entry.isIntersecting) {
107-
this.insertImage();
102+
this.insertOriginalImage();
108103
observer.unobserve(this);
109104
}
110105
})
111106
}, { rootMargin: '0px' });
112107

113-
observer.observe(this);
108+
// observe our pixel image
109+
observer.observe(this.getImgElement());
114110
}
115111

116-
insertImage() {
117-
const container = this.shadowRoot.querySelector('.lightning-image');
118-
container.appendChild(this.img);
112+
getImgElement() {
113+
return this.shadowRoot.querySelector('img');
114+
}
115+
116+
insertOriginalImage() {
117+
this.getImgElement().src = this.src;
119118
}
120119
}
121120

@@ -126,10 +125,14 @@
126125
* @returns {string}
127126
*/
128127
var replaceTag = function (element) {
129-
return element.outerHTML.replace(/lightning-image/g, 'img').trim();
128+
// custom elements are required to have a closing tag, but regular img tags do not use a closing tag.
129+
// remove our closing tag here so that we can do a normal search and replace in the next step
130+
const closingTagRemoved = element.outerHTML.replace(/<\/lightning-image>/g, '');
131+
132+
return closingTagRemoved.replace(/lightning-image/g, 'img').trim();
130133
};
131134

132-
// check if custom elements are not supported, and fall to showing the original iframe if so
135+
// check if custom elements are not supported, and fall to showing the original img tag if so
133136
if (!('customElements' in window)) {
134137
return [].forEach.call(document.querySelectorAll('lightning-image'), function (el) {
135138
el.outerHTML = replaceTag(el);

0 commit comments

Comments
 (0)