Add 500px prefetch zone for earlier infinite scroll loading

- Add 500px bottom padding when more pages exist (hidden on last page)
- Trigger next page load when viewport enters last 500px of content or padding
- Add checkImmediateLoad() to chain page loads if viewport still in zone
- Runs immediately after page 1 and after each subsequent page loads
- Enables rapid sequential loading when user scrolls far or has tall viewport
This commit is contained in:
Hanson.xyz Dev
2025-12-16 14:27:17 -06:00
parent 53d3c41917
commit bc39aa19dc
4 changed files with 82 additions and 9 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1162,10 +1162,12 @@
$grid: null, $grid: null,
$topSentinel: null, $topSentinel: null,
$bottomSentinel: null, $bottomSentinel: null,
$bottomPadding: null, // 500px padding when more pages exist
$topLoader: null, $topLoader: null,
$bottomLoader: null, $bottomLoader: null,
topObserver: null, topObserver: null,
bottomObserver: null, bottomObserver: null,
prefetchZone: 500, // Pixels from bottom to trigger load
/** /**
* Initialize infinite scroll * Initialize infinite scroll
@@ -1210,8 +1212,12 @@
InfiniteScrollState.isEnabled = true; InfiniteScrollState.isEnabled = true;
this.$container.addClass('infinite-scroll-enabled'); this.$container.addClass('infinite-scroll-enabled');
// Track initial height // Track initial height and update bottom padding
this.updateMaxHeight(); this.updateMaxHeight();
this.updateBottomPadding();
// Check if we should immediately load next page (viewport already in trigger zone)
this.checkImmediateLoad();
}, },
/** /**
@@ -1225,23 +1231,27 @@
}, },
/** /**
* Setup DOM elements (sentinels and loaders) * Setup DOM elements (sentinels, loaders, and padding)
*/ */
setupDOM: function() { setupDOM: function() {
// Remove any existing infinite scroll elements // Remove any existing infinite scroll elements
this.$container.find('.infinite-scroll-sentinel, .infinite-scroll-loader').remove(); this.$container.find('.infinite-scroll-sentinel, .infinite-scroll-loader, .infinite-scroll-padding').remove();
// Create top sentinel and loader (before grid) // Create top sentinel and loader (before grid)
this.$topSentinel = $('<div class="infinite-scroll-sentinel" data-direction="top"></div>'); this.$topSentinel = $('<div class="infinite-scroll-sentinel" data-direction="top"></div>');
this.$topLoader = $('<div class="infinite-scroll-loader infinite-scroll-loader--top"><div class="spinner"></div></div>'); this.$topLoader = $('<div class="infinite-scroll-loader infinite-scroll-loader--top"><div class="spinner"></div></div>');
// Create bottom sentinel and loader (after grid) // Create bottom padding (500px zone when more pages exist)
this.$bottomPadding = $('<div class="infinite-scroll-padding"></div>');
// Create bottom sentinel and loader (after grid and padding)
this.$bottomSentinel = $('<div class="infinite-scroll-sentinel" data-direction="bottom"></div>'); this.$bottomSentinel = $('<div class="infinite-scroll-sentinel" data-direction="bottom"></div>');
this.$bottomLoader = $('<div class="infinite-scroll-loader infinite-scroll-loader--bottom"><div class="spinner"></div></div>'); this.$bottomLoader = $('<div class="infinite-scroll-loader infinite-scroll-loader--bottom"><div class="spinner"></div></div>');
// Insert elements // Insert elements: grid -> padding -> loader -> sentinel
this.$grid.before(this.$topSentinel).before(this.$topLoader); this.$grid.before(this.$topSentinel).before(this.$topLoader);
this.$grid.after(this.$bottomLoader).after(this.$bottomSentinel); this.$grid.after(this.$bottomPadding);
this.$bottomPadding.after(this.$bottomLoader).after(this.$bottomSentinel);
}, },
/** /**
@@ -1325,6 +1335,57 @@
} }
}, },
/**
* Update bottom padding visibility based on whether more pages exist
*/
updateBottomPadding: function() {
if (!this.$bottomPadding) return;
var currentPage = InfiniteScrollState.visibleRange.last;
var hasMorePages = currentPage < InfiniteScrollState.totalPages;
if (hasMorePages) {
this.$bottomPadding.css('height', this.prefetchZone + 'px');
} else {
this.$bottomPadding.css('height', '0');
}
},
/**
* Check if viewport is already in the prefetch zone and trigger immediate load
* Called after any page load completes
*/
checkImmediateLoad: function() {
var self = this;
// Small delay to let DOM settle after render
setTimeout(function() {
if (!self.$grid || !InfiniteScrollState.isEnabled) return;
var currentPage = InfiniteScrollState.visibleRange.last;
var hasMorePages = currentPage < InfiniteScrollState.totalPages;
if (!hasMorePages) return;
// Already loading?
var nextPage = currentPage + 1;
if (InfiniteScrollState.pendingRequests[nextPage]) return;
// Check if viewport intersects the prefetch zone
// Prefetch zone = last 500px of grid content + 500px padding
var gridRect = self.$grid[0].getBoundingClientRect();
var viewportBottom = window.innerHeight;
// Trigger zone starts 500px from bottom of grid content
var triggerZoneTop = gridRect.bottom - self.prefetchZone;
// If viewport bottom is past the trigger zone top, load next page
if (viewportBottom >= triggerZoneTop) {
self.loadNextPage();
}
}, 50);
},
/** /**
* Load the next page (append) * Load the next page (append)
*/ */
@@ -1472,6 +1533,9 @@
// Update max height tracking // Update max height tracking
this.updateMaxHeight(); this.updateMaxHeight();
// Update bottom padding based on whether more pages exist
this.updateBottomPadding();
// Trigger image lazy loading // Trigger image lazy loading
if (typeof CardImageLoader !== 'undefined') { if (typeof CardImageLoader !== 'undefined') {
CardImageLoader.loadVisibleImages(); CardImageLoader.loadVisibleImages();
@@ -1479,6 +1543,9 @@
// Cleanup excess pages // Cleanup excess pages
this.cleanupExcessPages(); this.cleanupExcessPages();
// Check if we should immediately load next page (viewport still in trigger zone)
this.checkImmediateLoad();
}, },
/** /**
@@ -1680,7 +1747,7 @@
// Remove DOM elements // Remove DOM elements
if (this.$container) { if (this.$container) {
this.$container.find('.infinite-scroll-sentinel, .infinite-scroll-loader').remove(); this.$container.find('.infinite-scroll-sentinel, .infinite-scroll-loader, .infinite-scroll-padding').remove();
this.$container.removeClass('infinite-scroll-enabled'); this.$container.removeClass('infinite-scroll-enabled');
} }
@@ -598,6 +598,12 @@
pointer-events: none; pointer-events: none;
} }
// Bottom padding zone (500px when more pages exist)
.infinite-scroll-padding {
height: 0;
pointer-events: none;
}
// Loading indicators // Loading indicators
.infinite-scroll-loader { .infinite-scroll-loader {
display: none; display: none;