debian-mirror-gitlab/app/assets/javascripts/lazy_loader.js

175 lines
5.5 KiB
JavaScript
Raw Normal View History

2020-04-22 19:07:51 +05:30
import { debounce, throttle } from 'lodash';
2017-09-10 17:25:29 +05:30
2018-11-18 11:00:15 +05:30
export const placeholderImage =
'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
2018-12-05 23:21:45 +05:30
const SCROLL_THRESHOLD = 500;
2017-09-10 17:25:29 +05:30
export default class LazyLoader {
constructor(options = {}) {
2018-12-05 23:21:45 +05:30
this.intersectionObserver = null;
2017-09-10 17:25:29 +05:30
this.lazyImages = [];
this.observerNode = options.observerNode || '#content-body';
const scrollContainer = options.scrollContainer || window;
2018-12-05 23:21:45 +05:30
scrollContainer.addEventListener('load', () => this.register());
}
2020-07-28 23:09:34 +05:30
static supportsNativeLazyLoading() {
return 'loading' in HTMLImageElement.prototype;
}
2018-12-05 23:21:45 +05:30
static supportsIntersectionObserver() {
2020-06-23 00:09:42 +05:30
return Boolean(window.IntersectionObserver);
2017-09-10 17:25:29 +05:30
}
2018-12-05 23:21:45 +05:30
2017-09-10 17:25:29 +05:30
searchLazyImages() {
2019-02-15 15:39:39 +05:30
window.requestIdleCallback(
2018-11-18 11:00:15 +05:30
() => {
2018-12-05 23:21:45 +05:30
const lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
2018-03-17 18:26:18 +05:30
2020-07-28 23:09:34 +05:30
if (LazyLoader.supportsNativeLazyLoading()) {
2021-03-08 18:12:59 +05:30
lazyImages.forEach((img) => LazyLoader.loadImage(img));
2020-07-28 23:09:34 +05:30
} else if (LazyLoader.supportsIntersectionObserver()) {
2018-12-05 23:21:45 +05:30
if (this.intersectionObserver) {
2021-03-08 18:12:59 +05:30
lazyImages.forEach((img) => this.intersectionObserver.observe(img));
2018-12-05 23:21:45 +05:30
}
} else if (lazyImages.length) {
this.lazyImages = lazyImages;
this.checkElementsInView();
2018-11-18 11:00:15 +05:30
}
},
{ timeout: 500 },
);
2017-09-10 17:25:29 +05:30
}
2018-12-05 23:21:45 +05:30
2017-09-10 17:25:29 +05:30
startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
if (contentNode) {
2018-12-05 23:21:45 +05:30
this.mutationObserver = new MutationObserver(() => this.searchLazyImages());
2017-09-10 17:25:29 +05:30
2018-12-05 23:21:45 +05:30
this.mutationObserver.observe(contentNode, {
2017-09-10 17:25:29 +05:30
childList: true,
subtree: true,
});
}
}
2018-12-05 23:21:45 +05:30
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() {
2020-07-28 23:09:34 +05:30
if (!LazyLoader.supportsNativeLazyLoading()) {
if (LazyLoader.supportsIntersectionObserver()) {
this.startIntersectionObserver();
} else {
this.startLegacyObserver();
}
2018-12-05 23:21:45 +05:30
}
2020-07-28 23:09:34 +05:30
2017-09-10 17:25:29 +05:30
this.startContentObserver();
2018-12-05 23:21:45 +05:30
this.searchLazyImages();
2017-09-10 17:25:29 +05:30
}
2018-12-05 23:21:45 +05:30
startIntersectionObserver = () => {
2020-04-22 19:07:51 +05:30
this.throttledElementsInView = throttle(() => this.checkElementsInView(), 300);
2018-12-05 23:21:45 +05:30
this.intersectionObserver = new IntersectionObserver(this.onIntersection, {
rootMargin: `${SCROLL_THRESHOLD}px 0px`,
thresholds: 0.1,
});
};
2021-03-08 18:12:59 +05:30
onIntersection = (entries) => {
entries.forEach((entry) => {
2019-02-15 15:39:39 +05:30
// We are using `intersectionRatio > 0` over `isIntersecting`, as some browsers did not ship the latter
2019-12-04 20:38:33 +05:30
// See: https://gitlab.com/gitlab-org/gitlab-foss/issues/54407
2019-02-15 15:39:39 +05:30
if (entry.intersectionRatio > 0) {
2018-12-05 23:21:45 +05:30
this.intersectionObserver.unobserve(entry.target);
this.lazyImages.push(entry.target);
}
});
this.throttledElementsInView();
};
startLegacyObserver() {
2020-04-22 19:07:51 +05:30
this.throttledScrollCheck = throttle(() => this.scrollCheck(), 300);
this.debouncedElementsInView = debounce(() => this.checkElementsInView(), 300);
2018-12-05 23:21:45 +05:30
window.addEventListener('scroll', this.throttledScrollCheck);
window.addEventListener('resize', this.debouncedElementsInView);
}
2017-09-10 17:25:29 +05:30
scrollCheck() {
2019-02-15 15:39:39 +05:30
window.requestAnimationFrame(() => this.checkElementsInView());
2017-09-10 17:25:29 +05:30
}
2018-12-05 23:21:45 +05:30
2017-09-10 17:25:29 +05:30
checkElementsInView() {
2018-11-08 19:23:39 +05:30
const scrollTop = window.pageYOffset;
const visHeight = scrollTop + window.innerHeight + SCROLL_THRESHOLD;
2017-09-10 17:25:29 +05:30
// Loading Images which are in the current viewport or close to them
2021-03-08 18:12:59 +05:30
this.lazyImages = this.lazyImages.filter((selectedImage) => {
2017-09-10 17:25:29 +05:30
if (selectedImage.getAttribute('data-src')) {
2018-03-17 18:26:18 +05:30
const imgBoundRect = selectedImage.getBoundingClientRect();
const imgTop = scrollTop + imgBoundRect.top;
const imgBound = imgTop + imgBoundRect.height;
2017-09-10 17:25:29 +05:30
2018-12-05 23:21:45 +05:30
if (scrollTop <= imgBound && visHeight >= imgTop) {
2019-02-15 15:39:39 +05:30
window.requestAnimationFrame(() => {
2018-11-18 11:00:15 +05:30
LazyLoader.loadImage(selectedImage);
});
2017-09-10 17:25:29 +05:30
return false;
}
2018-12-05 23:21:45 +05:30
/*
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;
}
2017-09-10 17:25:29 +05:30
return true;
}
return false;
});
}
2018-12-05 23:21:45 +05:30
2017-09-10 17:25:29 +05:30
static loadImage(img) {
if (img.getAttribute('data-src')) {
2020-07-28 23:09:34 +05:30
img.setAttribute('loading', 'lazy');
2018-11-18 11:00:15 +05:30
let imgUrl = img.getAttribute('data-src');
// Only adding width + height for avatars for now
if (imgUrl.indexOf('/avatar/') > -1 && imgUrl.indexOf('?') === -1) {
2020-07-28 23:09:34 +05:30
const targetWidth = img.getAttribute('width') || img.width;
imgUrl += `?width=${targetWidth}`;
2018-11-18 11:00:15 +05:30
}
img.setAttribute('src', imgUrl);
2017-09-10 17:25:29 +05:30
img.removeAttribute('data-src');
img.classList.remove('lazy');
img.classList.add('js-lazy-loaded');
2019-03-02 22:35:43 +05:30
img.classList.add('qa-js-lazy-loaded');
2017-09-10 17:25:29 +05:30
}
}
}