Skip to content

Commit febe87c

Browse files
author
Keith Daulton
committed
Adds once visibility detection
1 parent 9a8a723 commit febe87c

File tree

7 files changed

+298
-210
lines changed

7 files changed

+298
-210
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ indent_size = 2
1111

1212
[*.md]
1313
trim_trailing_whitespace = false
14+
indent_size = 4

assets/media/portfolio/sparx-logo.png

44 KB
Loading

assets/scripts/site.js

Lines changed: 148 additions & 104 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
<section class="c-section c-section--fixed-hero c-section--light c-section--rough" id="intro">
2828
<div class="l-row l-row--narrow l-row--halves">
2929
<div class="c-section__content">
30-
<div class="a-fade-in-down h-vp-in-once">
30+
<div class="a-fade-in-down h-vp-in">
3131
<h1>Hello!</h1>
3232
<p>I'm a <strong>UI Developer &amp; Designer</strong> with a passion for creating awesome user experiences for web and mobile.</p>
33-
<p>I make pixels & divs pretty!</p>
33+
<p>I make pixels &amp; divs pretty!</p>
3434
</div>
3535
</div>
3636
<div class="c-section__media"></div>

src/scripts/site.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { on } from './utils/events';
2-
import { observeVisibility, monitorVisibility } from './utils/visibility';
2+
import { monitorVisibility } from './utils/visibility'; // observeVisibility,
33

44
on(document, 'DOMContentLoaded', () => {
55
monitorVisibility('.h-vp-in');
66
monitorVisibility('.h-vp-in-once', true);
77

88
// observeVisibility(document.querySelectorAll('.h-vp-in'));
99
// observeVisibility(document.querySelectorAll('.h-vp-in-once'), true);
10-
});
10+
});

src/scripts/utils/events.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
1-
export function on(element, type, listener, thisArg, capture) {
2-
capture || (capture = false);
1+
export function on(element, type, listener, thisArg, capture = false) {
2+
if (thisArg) {
3+
listener = listener.bind(thisArg);
4+
}
35

4-
if (thisArg) {
5-
listener = listener.bind(thisArg);
6+
element.addEventListener(type, listener, capture);
7+
8+
return {
9+
off: function () {
10+
element.removeEventListener(type, listener, capture);
611
}
12+
};
13+
}
14+
15+
export function once(element, type, listener, thisArg, capture) {
16+
if (thisArg) {
17+
listener = listener.bind(thisArg);
18+
}
719

8-
element.addEventListener(type, listener, capture);
20+
const inst = on(element, type, () => {
21+
listener.apply(thisArg, arguments);
22+
if (inst) {
23+
inst.off();
24+
}
25+
}, thisArg, capture);
926

10-
return {
11-
off: function() {
12-
element.removeEventListener(type, listener, capture);
13-
}
14-
};
15-
}
27+
return inst;
28+
}

src/scripts/utils/visibility.js

Lines changed: 121 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,156 @@
1-
import { on } from './events';
1+
import { on, once } from './events';
22
import { debounceFrame } from './delays';
33

4-
export function observeVisibility(items, once) {
5-
if (!items || !items.length) return { off: () => {} };
6-
7-
const observer = new IntersectionObserver((entries) => {
8-
console.log('IntersectionObserver 2', entries);
9-
entries.forEach(entry => {
10-
if (entry.isIntersecting) {
11-
console.log('is Intersecting', entry);
12-
entry.target.classList.add('is-in-view');
13-
if (once) {
14-
observer.unobserve(entry.target);
15-
}
16-
} else {
17-
console.log('not Intersecting', entry);
18-
entry.target.classList.remove('is-in-view');
19-
}
20-
});
21-
}, {
22-
threshold: 0.5
4+
/* global Promise */
5+
export function observeVisibility(items, doOnce) {
6+
if (!items || !items.length) return { off: () => { } };
7+
8+
const observer = new IntersectionObserver((entries) => {
9+
//console.log('IntersectionObserver 2', entries);
10+
entries.forEach(entry => {
11+
if (entry.isIntersecting) {
12+
//console.log('is Intersecting', entry);
13+
entry.target.classList.add('is-in-view');
14+
if (doOnce) {
15+
observer.unobserve(entry.target);
16+
}
17+
} else {
18+
//console.log('not Intersecting', entry);
19+
entry.target.classList.remove('is-in-view');
20+
}
2321
});
22+
}, { threshold: 0.5 });
2423

25-
if (items && items.length) {
26-
for (let i=0; i < items.length; i++) {
27-
observer.observe(items[i]);
28-
}
24+
if (items && items.length) {
25+
for (let i = 0; i < items.length; i++) {
26+
observer.observe(items[i]);
2927
}
28+
}
3029

31-
return {
32-
off: () => {
33-
observer.disconnect();
34-
}
35-
};
30+
return {
31+
off: () => {
32+
observer.disconnect();
33+
}
34+
};
3635
}
3736

3837
let vpW = window.innerWidth;
3938
let vpH = window.innerHeight;
4039
function elementIsVisible(el) {
41-
// FIXME: performance issue, cache this somewhere
42-
const rect = el.getBoundingClientRect();
43-
if (rect.top <= -rect.height || rect.top >= vpH) {
44-
return false;
45-
}
40+
// FIXME: performance issue, cache this somewhere
41+
const rect = el.getBoundingClientRect();
42+
if (rect.top <= -rect.height || rect.top >= vpH) {
43+
return false;
44+
}
4645

47-
if (rect.left <= -rect.width || rect.left >= vpW) {
48-
return false;
49-
}
46+
if (rect.left <= -rect.width || rect.left >= vpW) {
47+
return false;
48+
}
5049

51-
return true;
50+
return true;
5251
}
5352

54-
function detectVisibility(items, once) {
55-
items.forEach(el => {
56-
if (once && el.classList.contains('is-in-view')) {
57-
return;
58-
}
59-
60-
if (elementIsVisible(el)) {
61-
el.classList.add('is-in-view');
62-
} else {
63-
el.classList.remove('is-in-view');
64-
}
53+
const isInView = el => el.classList.contains('is-in-view');
54+
55+
function detectVisibility(items, doOnce) {
56+
return Promise.all(items.map(el => {
57+
return new Promise((resolve) => {
58+
if (doOnce && isInView(el)) {
59+
//console.log('This probably should not happen');
60+
resolve();
61+
return;
62+
}
63+
64+
if (el.classList.contains('is-animating')) {
65+
resolve();
66+
return;
67+
}
68+
69+
if (!elementIsVisible(el)) {
70+
el.classList.remove('is-in-view');
71+
resolve();
72+
} else if (!isInView(el)) {
73+
once(el, 'animationend', () => {
74+
el.classList.remove('is-animating');
75+
resolve();
76+
});
77+
el.classList.add('is-animating', 'is-in-view');
78+
} else {
79+
resolve();
80+
}
6581
});
82+
}));
6683
}
6784

6885
const listenerQueue = {};
6986
function detectAnimationVisibility() {
70-
//console.log('detectAnimationVisibility');
87+
//console.log('detectAnimationVisibility');
7188

72-
Object.keys(listenerQueue).map(key => listenerQueue[key]).forEach(_ => {
73-
detectVisibility(_.items, _.once);
74-
});
89+
Object.keys(listenerQueue).forEach(key => {
90+
const _ = listenerQueue[key];
91+
92+
const p = detectVisibility(_.items, _.once)
93+
94+
if (_.once) {
95+
p.then(() => {
96+
_.items = _.items.filter(el => !isInView(el));
97+
98+
if (!_.items.length && listenerQueue[key]) {
99+
//console.log(key, 'has none left');
100+
delete listenerQueue[key];
101+
}
102+
});
103+
}
104+
});
75105
}
76106

77107
const debouncedDetectAnimationVisibility = debounceFrame(() => {
78-
detectAnimationVisibility();
108+
detectAnimationVisibility();
79109
});
80110

81111
let activeVisibility;
82112
function listenOnChanges() {
83-
if (!activeVisibility) {
84-
let events = [
85-
on(window, 'resize', () => {
86-
//console.log('resize');
87-
vpW = window.innerWidth;
88-
vpH = window.innerHeight;
89-
debouncedDetectAnimationVisibility();
90-
}),
91-
on(window, 'scroll', () => {
92-
//console.log('scroll');
93-
debouncedDetectAnimationVisibility();
94-
})
95-
];
96-
113+
if (!activeVisibility) {
114+
const events = [
115+
on(window, 'resize', () => {
116+
//console.log('resize');
117+
vpW = window.innerWidth;
118+
vpH = window.innerHeight;
119+
debouncedDetectAnimationVisibility();
120+
}),
121+
on(window, 'scroll', () => {
122+
//console.log('scroll');
97123
debouncedDetectAnimationVisibility();
124+
})
125+
];
98126

99-
activeVisibility = {
100-
off: () => {
101-
events.forEach(_ => _.off());
102-
}
103-
};
104-
}
127+
debouncedDetectAnimationVisibility();
105128

106-
return activeVisibility;
129+
activeVisibility = {
130+
off: () => {
131+
events.forEach(_ => _.off());
132+
}
133+
};
134+
}
135+
136+
return activeVisibility;
107137
}
108138

109-
export function monitorVisibility(selector, once) {
110-
if (!listenerQueue.hasOwnProperty(selector)) {
111-
const els = document.querySelectorAll(selector);
112-
if (els && els.length) {
113-
listenerQueue[selector] = {
114-
items: els,
115-
once: once || false
116-
};
117-
listenOnChanges();
118-
}
139+
export function monitorVisibility(selector, doOnce) {
140+
if (!listenerQueue.hasOwnProperty(selector)) {
141+
const els = Array.prototype.slice.call(document.querySelectorAll(selector));
142+
if (els && els.length) {
143+
listenerQueue[selector] = {
144+
items: els,
145+
once: doOnce || false
146+
};
147+
listenOnChanges();
119148
}
149+
}
120150

121-
return {
122-
off: function() {
123-
activeVisibility && activeVisibility.off();
124-
}
125-
};
126-
}
151+
return {
152+
off: function() {
153+
activeVisibility && activeVisibility.off();
154+
}
155+
};
156+
}

0 commit comments

Comments
 (0)