Enhance mobile library loading: implement dynamic item rendering and lazy loading for improved performance on mobile devices
This commit is contained in:
+131
-1
@@ -208,12 +208,21 @@
|
||||
<script>
|
||||
// View mode persistence
|
||||
const LIBRARY_VIEW_MODE_KEY = 'inventarLibraryViewMode';
|
||||
const MOBILE_LIBRARY_MAX_WIDTH = 900;
|
||||
|
||||
function isMobileViewport() {
|
||||
return window.matchMedia(`(max-width: ${MOBILE_LIBRARY_MAX_WIDTH}px)`).matches;
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
localStorage.setItem(LIBRARY_VIEW_MODE_KEY, mode);
|
||||
const container = document.getElementById('itemsContainer');
|
||||
const tableHeader = document.getElementById('tableHeader');
|
||||
const items = container.querySelectorAll('.item-card');
|
||||
const renderedItems = Array.from(container.querySelectorAll('.item-card'));
|
||||
const detachedItems = Array.isArray(window.mobileDetachedLibraryItems)
|
||||
? window.mobileDetachedLibraryItems
|
||||
: [];
|
||||
const items = [...renderedItems, ...detachedItems.filter(item => !container.contains(item))];
|
||||
|
||||
items.forEach(item => {
|
||||
const cardContent = item.querySelector('.item-content.card-mode');
|
||||
@@ -236,6 +245,125 @@ function setViewMode(mode) {
|
||||
document.getElementById('viewModeToggle').classList.toggle('open', mode === 'table');
|
||||
}
|
||||
|
||||
function initMobileWindowedLibraryLoading() {
|
||||
const container = document.getElementById('itemsContainer');
|
||||
if (!container || !isMobileViewport()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allItems = Array.from(container.querySelectorAll('.library-item'));
|
||||
if (allItems.length <= 24) {
|
||||
return;
|
||||
}
|
||||
|
||||
const topSpacer = document.createElement('div');
|
||||
topSpacer.className = 'mobile-window-spacer top';
|
||||
const bottomSpacer = document.createElement('div');
|
||||
bottomSpacer.className = 'mobile-window-spacer bottom';
|
||||
|
||||
window.mobileDetachedLibraryItems = allItems;
|
||||
allItems.forEach(item => item.remove());
|
||||
container.appendChild(topSpacer);
|
||||
container.appendChild(bottomSpacer);
|
||||
|
||||
let averageItemHeight = Math.max(Math.round(window.innerHeight * 0.35), 230);
|
||||
let lastStart = -1;
|
||||
let lastEnd = -1;
|
||||
let framePending = false;
|
||||
|
||||
function updateImageMemory(startIndex, endIndex) {
|
||||
const imageBuffer = 4;
|
||||
allItems.forEach((item, index) => {
|
||||
const image = item.querySelector('img.item-image');
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!image.dataset.mobileSrc) {
|
||||
image.dataset.mobileSrc = image.getAttribute('src') || '';
|
||||
image.loading = 'lazy';
|
||||
image.decoding = 'async';
|
||||
}
|
||||
|
||||
const shouldKeepLoaded = index >= startIndex - imageBuffer && index < endIndex + imageBuffer;
|
||||
if (shouldKeepLoaded) {
|
||||
if (!image.getAttribute('src') && image.dataset.mobileSrc) {
|
||||
image.setAttribute('src', image.dataset.mobileSrc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (image.getAttribute('src')) {
|
||||
image.removeAttribute('src');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderVisibleWindow() {
|
||||
const viewportHeight = window.innerHeight || 800;
|
||||
const scrollTop = window.scrollY || window.pageYOffset || 0;
|
||||
const renderBuffer = viewportHeight * 1.5;
|
||||
|
||||
let startIndex = Math.max(0, Math.floor((scrollTop - renderBuffer) / averageItemHeight));
|
||||
let endIndex = Math.min(allItems.length, Math.ceil((scrollTop + viewportHeight + renderBuffer) / averageItemHeight));
|
||||
|
||||
if (endIndex - startIndex < 14) {
|
||||
endIndex = Math.min(allItems.length, startIndex + 14);
|
||||
}
|
||||
|
||||
if (startIndex === lastStart && endIndex === lastEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastStart = startIndex;
|
||||
lastEnd = endIndex;
|
||||
|
||||
while (topSpacer.nextSibling && topSpacer.nextSibling !== bottomSpacer) {
|
||||
topSpacer.nextSibling.remove();
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
for (let index = startIndex; index < endIndex; index += 1) {
|
||||
fragment.appendChild(allItems[index]);
|
||||
}
|
||||
container.insertBefore(fragment, bottomSpacer);
|
||||
|
||||
let measuredHeightSum = 0;
|
||||
let measuredCount = 0;
|
||||
for (let index = startIndex; index < endIndex; index += 1) {
|
||||
const height = allItems[index].getBoundingClientRect().height;
|
||||
if (height > 0) {
|
||||
measuredHeightSum += height;
|
||||
measuredCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (measuredCount > 0) {
|
||||
const measuredAverage = measuredHeightSum / measuredCount;
|
||||
averageItemHeight = Math.round((averageItemHeight * 3 + measuredAverage) / 4);
|
||||
}
|
||||
|
||||
topSpacer.style.height = `${Math.max(0, startIndex * averageItemHeight)}px`;
|
||||
bottomSpacer.style.height = `${Math.max(0, (allItems.length - endIndex) * averageItemHeight)}px`;
|
||||
updateImageMemory(startIndex, endIndex);
|
||||
}
|
||||
|
||||
function scheduleWindowRender() {
|
||||
if (framePending) {
|
||||
return;
|
||||
}
|
||||
framePending = true;
|
||||
requestAnimationFrame(() => {
|
||||
framePending = false;
|
||||
renderVisibleWindow();
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', scheduleWindowRender, { passive: true });
|
||||
window.addEventListener('resize', scheduleWindowRender, { passive: true });
|
||||
scheduleWindowRender();
|
||||
}
|
||||
|
||||
// Initialize view mode
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const savedMode = localStorage.getItem(LIBRARY_VIEW_MODE_KEY) || 'card';
|
||||
@@ -311,6 +439,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (e.target === this) this.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
initMobileWindowedLibraryLoading();
|
||||
});
|
||||
|
||||
function displayLibraryItemDetail(item) {
|
||||
|
||||
Reference in New Issue
Block a user