Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorTim Zallmann <tzallmann@gitlab.com>2018-10-02 10:29:57 +0300
committerTim Zallmann <tzallmann@gitlab.com>2018-10-02 10:29:57 +0300
commit1883e97e7c8645f4305e7c9094050e10930a900c (patch)
treedcf78d407c3c98279d33b3284eea3c4829c4ed91 /app
parent2bb8c1caa583e1efbc207c4093ea2159bfcc1db1 (diff)
parent4552a9f9fb14b82ae9c0b6b8cf971b0517721a75 (diff)
Merge branch '35476-lazy-image-intersectionobserver' into 'master'
Improve Lazy Image Loading by using IntersectionObserver Closes #49511 and #35476 See merge request gitlab-org/gitlab-ce!21565
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/lazy_loader.js107
1 files changed, 89 insertions, 18 deletions
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
index bd2212edec7..61b4862b4e3 100644
--- a/app/assets/javascripts/lazy_loader.js
+++ b/app/assets/javascripts/lazy_loader.js
@@ -2,54 +2,114 @@ import _ from 'underscore';
export const placeholderImage =
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
-const SCROLL_THRESHOLD = 300;
+const SCROLL_THRESHOLD = 500;
export default class LazyLoader {
constructor(options = {}) {
+ this.intersectionObserver = null;
this.lazyImages = [];
this.observerNode = options.observerNode || '#content-body';
- const throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
- const debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
-
- window.addEventListener('scroll', throttledScrollCheck);
- window.addEventListener('resize', debouncedElementsInView);
-
const scrollContainer = options.scrollContainer || window;
- scrollContainer.addEventListener('load', () => this.loadCheck());
+ scrollContainer.addEventListener('load', () => this.register());
+ }
+
+ static supportsIntersectionObserver() {
+ return 'IntersectionObserver' in window;
}
+
searchLazyImages() {
- const that = this;
requestIdleCallback(
() => {
- that.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
+ const lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
- if (that.lazyImages.length) {
- that.checkElementsInView();
+ if (LazyLoader.supportsIntersectionObserver()) {
+ if (this.intersectionObserver) {
+ lazyImages.forEach(img => this.intersectionObserver.observe(img));
+ }
+ } else if (lazyImages.length) {
+ this.lazyImages = lazyImages;
+ this.checkElementsInView();
}
},
{ timeout: 500 },
);
}
+
startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
-
if (contentNode) {
- const observer = new MutationObserver(() => this.searchLazyImages());
+ this.mutationObserver = new MutationObserver(() => this.searchLazyImages());
- observer.observe(contentNode, {
+ this.mutationObserver.observe(contentNode, {
childList: true,
subtree: true,
});
}
}
- loadCheck() {
- this.searchLazyImages();
+
+ stopContentObserver() {
+ if (this.mutationObserver) {
+ this.mutationObserver.takeRecords();
+ this.mutationObserver.disconnect();
+ this.mutationObserver = null;
+ }
+ }
+
+ unregister() {
+ this.stopContentObserver();
+ if (this.intersectionObserver) {
+ this.intersectionObserver.takeRecords();
+ this.intersectionObserver.disconnect();
+ this.intersectionObserver = null;
+ }
+ if (this.throttledScrollCheck) {
+ window.removeEventListener('scroll', this.throttledScrollCheck);
+ }
+ if (this.debouncedElementsInView) {
+ window.removeEventListener('resize', this.debouncedElementsInView);
+ }
+ }
+
+ register() {
+ if (LazyLoader.supportsIntersectionObserver()) {
+ this.startIntersectionObserver();
+ } else {
+ this.startLegacyObserver();
+ }
this.startContentObserver();
+ this.searchLazyImages();
}
+
+ startIntersectionObserver = () => {
+ this.throttledElementsInView = _.throttle(() => this.checkElementsInView(), 300);
+ this.intersectionObserver = new IntersectionObserver(this.onIntersection, {
+ rootMargin: `${SCROLL_THRESHOLD}px 0px`,
+ thresholds: 0.1,
+ });
+ };
+
+ onIntersection = entries => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ this.intersectionObserver.unobserve(entry.target);
+ this.lazyImages.push(entry.target);
+ }
+ });
+ this.throttledElementsInView();
+ };
+
+ startLegacyObserver() {
+ this.throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
+ this.debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
+ window.addEventListener('scroll', this.throttledScrollCheck);
+ window.addEventListener('resize', this.debouncedElementsInView);
+ }
+
scrollCheck() {
requestAnimationFrame(() => this.checkElementsInView());
}
+
checkElementsInView() {
const scrollTop = window.pageYOffset;
const visHeight = scrollTop + window.innerHeight + SCROLL_THRESHOLD;
@@ -61,18 +121,29 @@ export default class LazyLoader {
const imgTop = scrollTop + imgBoundRect.top;
const imgBound = imgTop + imgBoundRect.height;
- if (scrollTop < imgBound && visHeight > imgTop) {
+ if (scrollTop <= imgBound && visHeight >= imgTop) {
requestAnimationFrame(() => {
LazyLoader.loadImage(selectedImage);
});
return false;
}
+ /*
+ If we are scrolling fast, the img we watched intersecting could have left the view port.
+ So we are going watch for new intersections.
+ */
+ if (LazyLoader.supportsIntersectionObserver()) {
+ if (this.intersectionObserver) {
+ this.intersectionObserver.observe(selectedImage);
+ }
+ return false;
+ }
return true;
}
return false;
});
}
+
static loadImage(img) {
if (img.getAttribute('data-src')) {
let imgUrl = img.getAttribute('data-src');