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,
|
totalPosts: 0,
|
||||||
pendingRequests: {}, // Object: pageNum -> jqXHR
|
pendingRequests: {}, // Object: pageNum -> jqXHR
|
||||||
isEnabled: false,
|
isEnabled: false,
|
||||||
|
maxGridHeight: 0, // Track max height to prevent layout shift
|
||||||
config: {
|
config: {
|
||||||
maxPagesInDOM: 5,
|
maxPagesInDOM: 5,
|
||||||
bufferPages: 2,
|
bufferPages: 2,
|
||||||
@@ -1154,6 +1155,7 @@
|
|||||||
/**
|
/**
|
||||||
* Infinite Scroll Module
|
* Infinite Scroll Module
|
||||||
* Bidirectional infinite scroll for property list (desktop map view only)
|
* Bidirectional infinite scroll for property list (desktop map view only)
|
||||||
|
* Uses window scroll - page extends naturally as content loads
|
||||||
*/
|
*/
|
||||||
var InfiniteScroll = {
|
var InfiniteScroll = {
|
||||||
$container: null,
|
$container: null,
|
||||||
@@ -1164,7 +1166,6 @@
|
|||||||
$bottomLoader: null,
|
$bottomLoader: null,
|
||||||
topObserver: null,
|
topObserver: null,
|
||||||
bottomObserver: null,
|
bottomObserver: null,
|
||||||
scrollContainer: null,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize infinite scroll
|
* Initialize infinite scroll
|
||||||
@@ -1208,6 +1209,9 @@
|
|||||||
// Mark as enabled
|
// Mark as enabled
|
||||||
InfiniteScrollState.isEnabled = true;
|
InfiniteScrollState.isEnabled = true;
|
||||||
this.$container.addClass('infinite-scroll-enabled');
|
this.$container.addClass('infinite-scroll-enabled');
|
||||||
|
|
||||||
|
// Track initial height
|
||||||
|
this.updateMaxHeight();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1217,6 +1221,7 @@
|
|||||||
InfiniteScrollState.pages = {};
|
InfiniteScrollState.pages = {};
|
||||||
InfiniteScrollState.visibleRange = { first: 1, last: 1 };
|
InfiniteScrollState.visibleRange = { first: 1, last: 1 };
|
||||||
InfiniteScrollState.pendingRequests = {};
|
InfiniteScrollState.pendingRequests = {};
|
||||||
|
// Note: maxGridHeight is NOT reset here - only on full reset/filter change
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1237,9 +1242,6 @@
|
|||||||
// Insert elements
|
// Insert elements
|
||||||
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.$bottomLoader).after(this.$bottomSentinel);
|
||||||
|
|
||||||
// Set scroll container reference
|
|
||||||
this.scrollContainer = this.$container[0];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1265,13 +1267,15 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup IntersectionObservers for top and bottom sentinels
|
* Setup IntersectionObservers for top and bottom sentinels
|
||||||
|
* Uses viewport (root: null) since we're using window scroll
|
||||||
*/
|
*/
|
||||||
setupObservers: function() {
|
setupObservers: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Use viewport as root (null) with margin for pre-loading
|
||||||
var observerOptions = {
|
var observerOptions = {
|
||||||
root: this.scrollContainer,
|
root: null, // Use viewport, not container
|
||||||
rootMargin: '400px 0px',
|
rootMargin: '600px 0px', // Load when within 600px of viewport
|
||||||
threshold: 0
|
threshold: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1298,6 +1302,29 @@
|
|||||||
this.topObserver.observe(this.$topSentinel[0]);
|
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)
|
* Load the next page (append)
|
||||||
*/
|
*/
|
||||||
@@ -1442,6 +1469,9 @@
|
|||||||
// Restore scroll anchor
|
// Restore scroll anchor
|
||||||
this.restoreScrollAnchor(anchor);
|
this.restoreScrollAnchor(anchor);
|
||||||
|
|
||||||
|
// Update max height tracking
|
||||||
|
this.updateMaxHeight();
|
||||||
|
|
||||||
// Trigger image lazy loading
|
// Trigger image lazy loading
|
||||||
if (typeof CardImageLoader !== 'undefined') {
|
if (typeof CardImageLoader !== 'undefined') {
|
||||||
CardImageLoader.loadVisibleImages();
|
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() {
|
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 $cards = this.$grid.find('.property-card');
|
||||||
var anchor = null;
|
var anchor = null;
|
||||||
|
|
||||||
$cards.each(function() {
|
$cards.each(function() {
|
||||||
var rect = this.getBoundingClientRect();
|
var rect = this.getBoundingClientRect();
|
||||||
// Find first card that is at least partially visible
|
var elementTop = rect.top + viewportTop;
|
||||||
if (rect.bottom > containerRect.top && rect.top < containerRect.bottom) {
|
var elementBottom = rect.bottom + viewportTop;
|
||||||
|
|
||||||
|
// Find first card that is at least partially visible in viewport
|
||||||
|
if (elementBottom > viewportTop && elementTop < viewportBottom) {
|
||||||
anchor = {
|
anchor = {
|
||||||
element: this,
|
element: this,
|
||||||
offsetFromTop: rect.top - containerRect.top
|
offsetFromViewport: rect.top // Distance from top of viewport
|
||||||
};
|
};
|
||||||
return false; // break
|
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) {
|
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 elementRect = anchor.element.getBoundingClientRect();
|
||||||
var currentOffset = elementRect.top - containerRect.top;
|
var currentOffset = elementRect.top;
|
||||||
var diff = currentOffset - anchor.offsetFromTop;
|
var diff = currentOffset - anchor.offsetFromViewport;
|
||||||
|
|
||||||
if (Math.abs(diff) > 1) {
|
if (Math.abs(diff) > 1) {
|
||||||
this.scrollContainer.scrollTop += diff;
|
window.scrollBy(0, diff);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset infinite scroll (called on filter/map change)
|
* Reset infinite scroll (called on filter/map change)
|
||||||
|
* Clears max height since filters changed
|
||||||
*/
|
*/
|
||||||
reset: function() {
|
reset: function() {
|
||||||
// Cancel all pending infinite scroll requests
|
// Cancel all pending infinite scroll requests
|
||||||
@@ -1609,13 +1645,14 @@
|
|||||||
// Also cancel pending property and cluster requests via RequestQueue
|
// Also cancel pending property and cluster requests via RequestQueue
|
||||||
RequestQueue.cancel();
|
RequestQueue.cancel();
|
||||||
|
|
||||||
|
// Clear max height since filters are changing
|
||||||
|
this.clearMaxHeight();
|
||||||
|
|
||||||
// Clear state
|
// Clear state
|
||||||
this.resetState();
|
this.resetState();
|
||||||
|
|
||||||
// Scroll to top
|
// Scroll to top of page
|
||||||
if (this.scrollContainer) {
|
window.scrollTo(0, 0);
|
||||||
this.scrollContainer.scrollTop = 0;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1653,6 +1690,9 @@
|
|||||||
this.$grid.find('.infinite-scroll-placeholder').remove();
|
this.$grid.find('.infinite-scroll-placeholder').remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear max height
|
||||||
|
this.clearMaxHeight();
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
this.resetState();
|
this.resetState();
|
||||||
InfiniteScrollState.isEnabled = false;
|
InfiniteScrollState.isEnabled = false;
|
||||||
|
|||||||
@@ -572,30 +572,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Infinite Scroll (desktop map view only)
|
// Infinite Scroll (desktop map view only)
|
||||||
|
// Uses window scroll, not container scroll - page extends naturally
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.property-list-container.infinite-scroll-enabled {
|
.property-list-container.infinite-scroll-enabled {
|
||||||
max-height: calc(100vh - 180px);
|
// No overflow constraints - content grows the page naturally
|
||||||
overflow-y: auto;
|
// Min-height maintained by JS to prevent layout shift when pages unloaded
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user