VulpesOS/gaia/shared/js/contacts/utilities/image_loader.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

207 lines
5.7 KiB
JavaScript
Executable file

/* exported ImageLoader */
'use strict';
if (!window.ImageLoader) {
var ImageLoader = function ImageLoader(pContainer, pItems) {
var container, items, itemsSelector, lastScrollTime, scrollLatency = 100,
scrollTimer, itemHeight, total, imgsLoading = 0,
loadImage = defaultLoadImage, self = this;
init(pContainer, pItems);
/**
* Initializer
*
*/
function init(pContainer, pItems) {
itemsSelector = pItems;
container = document.querySelector(pContainer);
attachHandlers();
window.addEventListener('image-loader-resume', resuming);
window.addEventListener('image-loader-pause', unload);
load();
}
function resuming() {
window.clearTimeout(scrollTimer);
attachHandlers();
update();
}
function onUpdate(evt) {
evt.stopPropagation();
onScroll();
}
function load() {
window.clearTimeout(scrollTimer);
scrollTimer = null;
items = container.querySelectorAll(itemsSelector);
// All items have the same height
itemHeight = items[0] ? items[0].offsetHeight : 1;
total = items.length;
// Initial check if items should appear
window.setTimeout(update, 0);
}
function attachHandlers() {
container.addEventListener('scroll', onScroll);
document.addEventListener('onupdate', onUpdate);
}
function unload() {
container.removeEventListener('scroll', onScroll);
document.removeEventListener('onupdate', onUpdate);
window.clearTimeout(scrollTimer);
scrollTimer = null;
}
function setResolver(pResolver) {
loadImage = pResolver;
}
function onScroll() {
if (imgsLoading > 0) {
// Stop the pending images load
window.stop();
imgsLoading = 0;
}
// Clearing and setting a timer on every scroll event is too slow on
// some mobile devices. Therefore, set a timer once here and then
// check how long it has been since the last scroll event in the
// timer handler to determine what to do.
lastScrollTime = Date.now();
if (!scrollTimer) {
scrollTimer = window.setTimeout(updateFromScroll, scrollLatency);
}
}
function updateFromScroll() {
scrollTimer = null;
// If we scrolled more since the timer was set, then we need to
// delay again. Otherwise, go ahead and update now.
var deltaLatency = lastScrollTime - Date.now() + scrollLatency;
if (deltaLatency > 0) {
scrollTimer = window.setTimeout(updateFromScroll, deltaLatency);
} else {
update();
}
}
/**
* Loads the image contained in a DOM Element.
*/
function defaultLoadImage(item) {
var image = item.querySelector('span[data-type=img][data-src]');
if (!image) {
image = item.querySelector('img[data-src]');
if (!image) {
// Image by default
image = item.querySelector('span[data-type=img][data-group]');
if (image) {
item.dataset.visited = 'true';
}
return;
}
}
++imgsLoading;
var tmp = new Image();
var src = tmp.src = image.dataset.src;
tmp.onload = function onload() {
--imgsLoading;
image.style.backgroundImage = 'url(' + src + ')';
if (tmp.complete) {
item.dataset.visited = 'true';
}
tmp = null;
};
tmp.onabort = tmp.onerror = function onerror() {
--imgsLoading;
item.dataset.visited = 'false';
tmp = null;
};
}
/**
* Calculates the set of items are in the current viewport
*
*/
function update() {
if (total === 0) {
return;
}
var viewTop = container.scrollTop;
// Index is always inside or below viewport
var index = Math.floor(viewTop / itemHeight);
var containerHeight = container.offsetHeight;
// Goes backward
for (var i = index; i >= 0; i--) {
var item = items[i];
if (item) {
if (item.offsetTop + itemHeight < viewTop) {
break; // Over
}
if (item.dataset.visited !== 'true' &&
item.offsetTop <= viewTop + containerHeight) {
loadImage(item, self); // Inside
}
}
}
// Goes forward
for (var j = index + 1; j < total; j++) {
var theItem = items[j];
if (!theItem) {
// Returning because of index out of bound
return;
}
if (theItem.offsetTop > viewTop + containerHeight) {
return; // Below
}
if (theItem.dataset.visited !== 'true') {
loadImage(theItem, self);
}
}
} // update
function releaseImage(item) {
var image = item.querySelector('span[data-type=img][data-src]');
if (!image) {
// Image by default
image = item.querySelector('span[data-type=img][data-group]');
if (image) {
item.dataset.visited = 'false';
}
return null;
}
image.style.backgroundImage = 'none';
item.dataset.visited = 'false';
return image;
}
// It's necessary to prevent unit test errors
function destroy() {
unload();
window.removeEventListener('image-loader-pause', unload);
window.removeEventListener('image-loader-resume', resuming);
container = items = itemsSelector = lastScrollTime = scrollLatency = null;
scrollTimer = itemHeight = total = imgsLoading = loadImage = null;
}
this.reload = load;
this.unload = unload;
this.setResolver = setResolver;
this.defaultLoad = defaultLoadImage;
this.releaseImage = releaseImage;
this.destroy = destroy;
};
}