Skip to content
This repository was archived by the owner on Feb 5, 2022. It is now read-only.

Commit b01c132

Browse files
authored
Merge pull request #290 from openforge/sam-lazy-loading
perf: add component for lazy loading of images
2 parents 4758291 + 142481a commit b01c132

File tree

22 files changed

+440
-228
lines changed

22 files changed

+440
-228
lines changed

CHANGELOG.md

Lines changed: 196 additions & 125 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@openforge/main-website",
33
"private": true,
4-
"version": "2.0.3",
4+
"version": "2.0.4",
55
"description": "The official website for OpenForge",
66
"files": [
77
"dist/"

sass-lint.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ rules:
3535
nesting-depth:
3636
- 1
3737
-
38-
max-depth: 2
38+
max-depth: 5
3939
no-duplicate-properties: 1
4040
no-empty-rulesets: 2
4141
no-debug: 1
@@ -46,8 +46,6 @@ rules:
4646
- 2
4747
-
4848
style: lowercase
49-
indentation:
50-
- 2
5149
-
5250
size: 2
5351
single-line-per-selector: 1
@@ -57,4 +55,4 @@ rules:
5755
space-before-colon: 2
5856
space-between-parens: 2
5957
zero-unit:
60-
- 2
58+
- 2

src/assets/i18n/en.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ export const translations = {
6363
action: 'LEARN MORE',
6464
},
6565
mission: {
66-
statement: 'Our mission is to forge a bond between people and technology through digital experiences.'
67-
},
66+
statement:
67+
'Our mission is to forge a bond between people and technology through digital experiences.',
68+
},
6869
values: {
6970
title: 'Our Values',
7071
text:
@@ -97,13 +98,13 @@ export const translations = {
9798
container: {
9899
title1: 'What',
99100
title2: 'Does',
100-
skills: 'Skills'
101+
skills: 'Skills',
101102
},
102103
cta: {
103104
title: 'Meet The Team',
104-
button: `Let's Go`
105-
}
106-
}
105+
button: `Let's Go`,
106+
},
107+
},
107108
},
108109

109110
contact: {

src/components.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import '@stencil/router';
1010
import '@stencil/state-tunnel';
1111

1212

13+
import {
14+
EventEmitter,
15+
} from '@stencil/core';
1316
import {
1417
MatchResults,
1518
RouterHistory,
@@ -50,6 +53,12 @@ declare global {
5053
'src': string;
5154
}
5255

56+
interface LazyImg {
57+
'alt': string;
58+
'src': string;
59+
'width': number;
60+
}
61+
5362
interface AppInput {
5463
'id': string;
5564
'label': string;
@@ -193,6 +202,14 @@ declare global {
193202
};
194203

195204

205+
interface HTMLLazyImgElement extends StencilComponents.LazyImg, HTMLStencilElement {}
206+
207+
var HTMLLazyImgElement: {
208+
prototype: HTMLLazyImgElement;
209+
new (): HTMLLazyImgElement;
210+
};
211+
212+
196213
interface HTMLAppInputElement extends StencilComponents.AppInput, HTMLStencilElement {}
197214

198215
var HTMLAppInputElement: {
@@ -368,6 +385,7 @@ declare global {
368385
'app-cta': JSXElements.AppCtaAttributes;
369386
'app-footer': JSXElements.AppFooterAttributes;
370387
'app-img': JSXElements.AppImgAttributes;
388+
'lazy-img': JSXElements.LazyImgAttributes;
371389
'app-input': JSXElements.AppInputAttributes;
372390
'app-members': JSXElements.AppMembersAttributes;
373391
'app-nav-header': JSXElements.AppNavHeaderAttributes;
@@ -413,6 +431,13 @@ declare global {
413431
'src'?: string;
414432
}
415433

434+
export interface LazyImgAttributes extends HTMLAttributes {
435+
'alt'?: string;
436+
'onLazyImgloaded'?: (event: CustomEvent<HTMLImageElement>) => void;
437+
'src'?: string;
438+
'width'?: number;
439+
}
440+
416441
export interface AppInputAttributes extends HTMLAttributes {
417442
'id'?: string;
418443
'label'?: string;
@@ -532,6 +557,7 @@ declare global {
532557
'app-cta': HTMLAppCtaElement
533558
'app-footer': HTMLAppFooterElement
534559
'app-img': HTMLAppImgElement
560+
'lazy-img': HTMLLazyImgElement
535561
'app-input': HTMLAppInputElement
536562
'app-members': HTMLAppMembersElement
537563
'app-nav-header': HTMLAppNavHeaderElement
@@ -560,6 +586,7 @@ declare global {
560586
'app-cta': HTMLAppCtaElement;
561587
'app-footer': HTMLAppFooterElement;
562588
'app-img': HTMLAppImgElement;
589+
'lazy-img': HTMLLazyImgElement;
563590
'app-input': HTMLAppInputElement;
564591
'app-members': HTMLAppMembersElement;
565592
'app-nav-header': HTMLAppNavHeaderElement;

src/components/app-cta/app-cta.scss

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
.cta {
22
height: 344px;
3-
3+
44
@include media-breakpoint-down(sm) {
55
height: 780px;
66
}
77

88
.row {
99
height: 100%;
10-
background: linear-gradient(
11-
to bottom,
12-
rgba(108, 144, 152, 0.99),
13-
#3f5a60
14-
);
10+
background: linear-gradient(to bottom, rgba(108, 144, 152, 0.99), #3f5a60);
1511
}
1612

1713
.cta-image {

src/components/app-cta/app-cta.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { Component } from '@stencil/core';
55
styleUrl: 'app-cta.scss',
66
})
77
export class AppCta {
8-
98
render() {
109
return (
1110
<section id="cta" class="cta">

src/components/app-footer/app-footer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class AppFooter {
9090
</li>
9191
<li>
9292
<stencil-route-link url="/service-level-agreement">
93-
{translate('footer.social.sla')}
93+
{translate('footer.social.sla')}
9494
</stencil-route-link>
9595
</li>
9696
</ul>

src/components/app-img/app-img.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,7 @@ export class Img {
5757

5858
render() {
5959
return (
60-
<img
61-
class={{ fit: this.fit }}
62-
src={this.loadSrc}
63-
alt={this.alt}
64-
decoding="async"
65-
/>
60+
<lazy-img class={{ fit: this.fit }} src={this.loadSrc} alt={this.alt} />
6661
);
6762
}
6863
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
Component,
3+
Element,
4+
Event,
5+
EventEmitter,
6+
Prop,
7+
State,
8+
} from '@stencil/core';
9+
10+
/*
11+
You can use this component to lazy load below the fold images to improve load time.
12+
Below the fold images are images that are not seen by the user until they have started to scroll.
13+
Instead of loading all these images up front before the user even sees them, which will eat up
14+
network bandwith, we can use this component to put off loading these images until the user has
15+
scrolled to where that image is in your PWA.
16+
To use this component use the following markup: <lazy-img src={/path/to/img} alt={alt text for img}></lazy-img>
17+
*/
18+
19+
@Component({
20+
tag: 'lazy-img',
21+
// styleUrl: 'lazy-img.scss'
22+
})
23+
export class LazyImg {
24+
@Element() el: HTMLElement;
25+
26+
@Prop() src: string;
27+
@Prop() alt: string;
28+
@Prop() width: number;
29+
30+
@State() oldSrc: string;
31+
32+
@Event() lazyImgloaded: EventEmitter<HTMLImageElement>;
33+
34+
image: HTMLImageElement;
35+
io: IntersectionObserver | null;
36+
37+
componentDidLoad() {
38+
this.addIntersectionObserver();
39+
}
40+
41+
componentDidUnload() {
42+
this.removeIntersectionObserver();
43+
}
44+
45+
componentWillUpdate() {
46+
if (this.src !== this.oldSrc) {
47+
this.addIntersectionObserver();
48+
}
49+
this.oldSrc = this.src;
50+
}
51+
52+
handleImage() {
53+
console.log('a');
54+
const image = this.image;
55+
image.setAttribute('src', this.src || '');
56+
image.onload = () => {
57+
image.removeAttribute('data-src');
58+
this.lazyImgloaded.emit(image);
59+
};
60+
}
61+
62+
addIntersectionObserver() {
63+
if (!this.src) {
64+
return;
65+
}
66+
if ('IntersectionObserver' in window) {
67+
this.io = new IntersectionObserver(data => {
68+
// because there will only ever be one instance
69+
// of the element we are observing
70+
// we can just use data[0]
71+
if (data[0].isIntersecting) {
72+
this.handleImage();
73+
this.removeIntersectionObserver();
74+
}
75+
});
76+
this.io.observe(this.image);
77+
} else {
78+
// fall back to just loading the image for Safari and IE
79+
this.handleImage();
80+
}
81+
}
82+
83+
removeIntersectionObserver() {
84+
if (this.io) {
85+
this.io.disconnect();
86+
this.io = null;
87+
}
88+
}
89+
90+
render() {
91+
return (
92+
<img
93+
ref={el => (this.image = el as HTMLImageElement)}
94+
data-src={this.src}
95+
alt={this.alt}
96+
width={this.width}
97+
/>
98+
);
99+
}
100+
}

0 commit comments

Comments
 (0)