Change infinite scroll to use window scroll instead of container
- Remove overflow-y:auto and max-height constraints from property list - Use viewport-based IntersectionObserver (root: null) instead of container - Track and maintain max grid height to prevent layout shift on scroll up - Clear max height only on filter/map change (not on normal scroll) - Update scroll anchor methods to use window.scrollY/scrollBy - Mobile continues to use pagination (desktop only infinite scroll)
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1143,6 +1143,7 @@
|
||||
totalPosts: 0,
|
||||
pendingRequests: {}, // Object: pageNum -> jqXHR
|
||||
isEnabled: false,
|
||||
maxGridHeight: 0, // Track max height to prevent layout shift
|
||||
config: {
|
||||
maxPagesInDOM: 5,
|
||||
bufferPages: 2,
|
||||
@@ -1154,6 +1155,7 @@
|
||||
/**
|
||||
* Infinite Scroll Module
|
||||
* Bidirectional infinite scroll for property list (desktop map view only)
|
||||
* Uses window scroll - page extends naturally as content loads
|
||||
*/
|
||||
var InfiniteScroll = {
|
||||
$container: null,
|
||||
@@ -1164,7 +1166,6 @@
|
||||
$bottomLoader: null,
|
||||
topObserver: null,
|
||||
bottomObserver: null,
|
||||
scrollContainer: null,
|
||||
|
||||
/**
|
||||
* Initialize infinite scroll
|
||||
@@ -1208,6 +1209,9 @@
|
||||
// Mark as enabled
|
||||
InfiniteScrollState.isEnabled = true;
|
||||
this.$container.addClass('infinite-scroll-enabled');
|
||||
|
||||
// Track initial height
|
||||
this.updateMaxHeight();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1217,6 +1221,7 @@
|
||||
InfiniteScrollState.pages = {};
|
||||
InfiniteScrollState.visibleRange = { first: 1, last: 1 };
|
||||
InfiniteScrollState.pendingRequests = {};
|
||||
// Note: maxGridHeight is NOT reset here - only on full reset/filter change
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1237,9 +1242,6 @@
|
||||
// Insert elements
|
||||
this.$grid.before(this.$topSentinel).before(this.$topLoader);
|
||||
this.$grid.after(this.$bottomLoader).after(this.$bottomSentinel);
|
||||
|
||||
// Set scroll container reference
|
||||
this.scrollContainer = this.$container[0];
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1265,13 +1267,15 @@
|
||||
|
||||
/**
|
||||
* Setup IntersectionObservers for top and bottom sentinels
|
||||
* Uses viewport (root: null) since we're using window scroll
|
||||
*/
|
||||
setupObservers: function() {
|
||||
var self = this;
|
||||
|
||||
// Use viewport as root (null) with margin for pre-loading
|
||||
var observerOptions = {
|
||||
root: this.scrollContainer,
|
||||
rootMargin: '400px 0px',
|
||||
root: null, // Use viewport, not container
|
||||
rootMargin: '600px 0px', // Load when within 600px of viewport
|
||||
threshold: 0
|
||||
};
|
||||
|
||||
@@ -1298,6 +1302,29 @@
|
||||
this.topObserver.observe(this.$topSentinel[0]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Track and maintain maximum grid height to prevent layout shift
|
||||
*/
|
||||
updateMaxHeight: function() {
|
||||
if (!this.$grid) return;
|
||||
|
||||
var currentHeight = this.$grid.outerHeight();
|
||||
if (currentHeight > InfiniteScrollState.maxGridHeight) {
|
||||
InfiniteScrollState.maxGridHeight = currentHeight;
|
||||
this.$grid.css('min-height', currentHeight + 'px');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear max height tracking (called on filter change)
|
||||
*/
|
||||
clearMaxHeight: function() {
|
||||
InfiniteScrollState.maxGridHeight = 0;
|
||||
if (this.$grid) {
|
||||
this.$grid.css('min-height', '');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the next page (append)
|
||||
*/
|
||||
@@ -1442,6 +1469,9 @@
|
||||
// Restore scroll anchor
|
||||
this.restoreScrollAnchor(anchor);
|
||||
|
||||
// Update max height tracking
|
||||
this.updateMaxHeight();
|
||||
|
||||
// Trigger image lazy loading
|
||||
if (typeof CardImageLoader !== 'undefined') {
|
||||
CardImageLoader.loadVisibleImages();
|
||||
@@ -1555,22 +1585,27 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Save scroll anchor (first visible card)
|
||||
* Save scroll anchor (first visible card in viewport)
|
||||
* Uses window scroll position
|
||||
*/
|
||||
saveScrollAnchor: function() {
|
||||
if (!this.scrollContainer) return null;
|
||||
if (!this.$grid) return null;
|
||||
|
||||
var containerRect = this.scrollContainer.getBoundingClientRect();
|
||||
var viewportTop = window.scrollY || window.pageYOffset;
|
||||
var viewportBottom = viewportTop + window.innerHeight;
|
||||
var $cards = this.$grid.find('.property-card');
|
||||
var anchor = null;
|
||||
|
||||
$cards.each(function() {
|
||||
var rect = this.getBoundingClientRect();
|
||||
// Find first card that is at least partially visible
|
||||
if (rect.bottom > containerRect.top && rect.top < containerRect.bottom) {
|
||||
var elementTop = rect.top + viewportTop;
|
||||
var elementBottom = rect.bottom + viewportTop;
|
||||
|
||||
// Find first card that is at least partially visible in viewport
|
||||
if (elementBottom > viewportTop && elementTop < viewportBottom) {
|
||||
anchor = {
|
||||
element: this,
|
||||
offsetFromTop: rect.top - containerRect.top
|
||||
offsetFromViewport: rect.top // Distance from top of viewport
|
||||
};
|
||||
return false; // break
|
||||
}
|
||||
@@ -1580,23 +1615,24 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore scroll position to maintain anchor
|
||||
* Restore scroll position to maintain anchor element's viewport position
|
||||
* Uses window scroll
|
||||
*/
|
||||
restoreScrollAnchor: function(anchor) {
|
||||
if (!anchor || !anchor.element || !this.scrollContainer) return;
|
||||
if (!anchor || !anchor.element) return;
|
||||
|
||||
var containerRect = this.scrollContainer.getBoundingClientRect();
|
||||
var elementRect = anchor.element.getBoundingClientRect();
|
||||
var currentOffset = elementRect.top - containerRect.top;
|
||||
var diff = currentOffset - anchor.offsetFromTop;
|
||||
var currentOffset = elementRect.top;
|
||||
var diff = currentOffset - anchor.offsetFromViewport;
|
||||
|
||||
if (Math.abs(diff) > 1) {
|
||||
this.scrollContainer.scrollTop += diff;
|
||||
window.scrollBy(0, diff);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset infinite scroll (called on filter/map change)
|
||||
* Clears max height since filters changed
|
||||
*/
|
||||
reset: function() {
|
||||
// Cancel all pending infinite scroll requests
|
||||
@@ -1609,13 +1645,14 @@
|
||||
// Also cancel pending property and cluster requests via RequestQueue
|
||||
RequestQueue.cancel();
|
||||
|
||||
// Clear max height since filters are changing
|
||||
this.clearMaxHeight();
|
||||
|
||||
// Clear state
|
||||
this.resetState();
|
||||
|
||||
// Scroll to top
|
||||
if (this.scrollContainer) {
|
||||
this.scrollContainer.scrollTop = 0;
|
||||
}
|
||||
// Scroll to top of page
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1653,6 +1690,9 @@
|
||||
this.$grid.find('.infinite-scroll-placeholder').remove();
|
||||
}
|
||||
|
||||
// Clear max height
|
||||
this.clearMaxHeight();
|
||||
|
||||
// Reset state
|
||||
this.resetState();
|
||||
InfiniteScrollState.isEnabled = false;
|
||||
|
||||
@@ -572,30 +572,11 @@
|
||||
}
|
||||
|
||||
// Infinite Scroll (desktop map view only)
|
||||
// Uses window scroll, not container scroll - page extends naturally
|
||||
@media (min-width: 1024px) {
|
||||
.property-list-container.infinite-scroll-enabled {
|
||||
max-height: calc(100vh - 180px);
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
// Custom scrollbar
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-dark);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-text-muted);
|
||||
}
|
||||
}
|
||||
// No overflow constraints - content grows the page naturally
|
||||
// Min-height maintained by JS to prevent layout shift when pages unloaded
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user