Initial commit
[ghost-theme-lbbg.git] / assets / js / infinite-scroll.js
1 /* eslint-env browser */
2
3 /**
4 * Infinite Scroll
5 * Used on all pages where there is a list of posts (homepage, tag index, etc).
6 *
7 * When the page is scrolled to 300px from the bottom, the next page of posts
8 * is fetched by following the the <link rel="next" href="..."> that is output
9 * by {{ghost_head}}.
10 *
11 * The individual post items are extracted from the fetched pages by looking for
12 * a wrapper element with the class "post-card". Any found elements are appended
13 * to the element with the class "post-feed" in the currently viewed page.
14 */
15
16 (function (window, document) {
17 // next link element
18 var nextElement = document.querySelector('link[rel=next]');
19 if (!nextElement) {
20 return;
21 }
22
23 // post feed element
24 var feedElement = document.querySelector('.post-feed');
25 if (!feedElement) {
26 return;
27 }
28
29 var buffer = 300;
30
31 var ticking = false;
32 var loading = false;
33
34 var lastScrollY = window.scrollY;
35 var lastWindowHeight = window.innerHeight;
36 var lastDocumentHeight = document.documentElement.scrollHeight;
37
38 function onPageLoad() {
39 if (this.status === 404) {
40 window.removeEventListener('scroll', onScroll);
41 window.removeEventListener('resize', onResize);
42 return;
43 }
44
45 // append contents
46 var postElements = this.response.querySelectorAll('article.post-card');
47 postElements.forEach(function (item) {
48 // document.importNode is important, without it the item's owner
49 // document will be different which can break resizing of
50 // `object-fit: cover` images in Safari
51 feedElement.appendChild(document.importNode(item, true));
52 });
53
54 // set next link
55 var resNextElement = this.response.querySelector('link[rel=next]');
56 if (resNextElement) {
57 nextElement.href = resNextElement.href;
58 } else {
59 window.removeEventListener('scroll', onScroll);
60 window.removeEventListener('resize', onResize);
61 }
62
63 // sync status
64 lastDocumentHeight = document.documentElement.scrollHeight;
65 ticking = false;
66 loading = false;
67 }
68
69 function onUpdate() {
70 // return if already loading
71 if (loading) {
72 return;
73 }
74
75 // return if not scroll to the bottom
76 if (lastScrollY + lastWindowHeight <= lastDocumentHeight - buffer) {
77 ticking = false;
78 return;
79 }
80
81 loading = true;
82
83 var xhr = new window.XMLHttpRequest();
84 xhr.responseType = 'document';
85
86 xhr.addEventListener('load', onPageLoad);
87
88 xhr.open('GET', nextElement.href);
89 xhr.send(null);
90 }
91
92 function requestTick() {
93 ticking || window.requestAnimationFrame(onUpdate);
94 ticking = true;
95 }
96
97 function onScroll() {
98 lastScrollY = window.scrollY;
99 requestTick();
100 }
101
102 function onResize() {
103 lastWindowHeight = window.innerHeight;
104 lastDocumentHeight = document.documentElement.scrollHeight;
105 requestTick();
106 }
107
108 window.addEventListener('scroll', onScroll, {passive: true});
109 window.addEventListener('resize', onResize);
110
111 requestTick();
112 })(window, document);