Sticky Map for FacetWP and Results — with Viewport Height
When using FacetWP to display a filterable map and results layout, you often want the map to stay visible while users scroll through the results. In this post, we’ll show you how to make the left sidebar (map) stick to the viewport height and remain fixed during scroll, only on desktop screens.
We’ll do this with clean vanilla JavaScript and responsive design principles — no external libraries or frameworks required.
Use Case
Imagine this layout:
- Left column: contains a map (
#facetwp-map
) with filters, wrapped in.buzy__left-inner
. - Right column: contains FacetWP-powered search results.
- As you scroll the results, the map stays in place — but only when the container is fully in view and you're on desktop.
HTML Structure
Here’s a basic version of the layout you might use on a FacetWP-powered results page:
<div class="buzy__results_container">
<div class="buzy__left">
<div class="buzy__left-inner">
<div id="facetwp-map">
<!-- Your interactive map or filter UI -->
</div>
</div>
</div>
<div class="buzy__right">
<!-- FacetWP results go here -->
<div class="facetwp-template">
<div class="result">Result 1</div>
<div class="result">Result 2</div>
<div class="result">Result 3</div>
<!-- ... -->
</div>
</div>
</div>
JavaScript for Sticky Behavior
Save this in a separate file or include it in your theme’s JS bundle:
(function () {
const left = document.querySelector('.buzy__left-inner');
const right = document.querySelector('.buzy__right');
const container = document.querySelector('.buzy__results_container');
if (!left || !right || !container) return;
const spacer = document.createElement('div');
spacer.style.height = `${left.offsetHeight}px`;
spacer.style.display = 'none';
left.parentNode.insertBefore(spacer, left);
function isDesktop() {
return window.innerWidth >= 992; // match CSS breakpoint
}
function resetPositioning() {
left.style.position = 'static';
left.style.top = '';
left.style.width = '';
left.style.height = '';
spacer.style.display = 'none';
}
function updateLeftHeight() {
if (isDesktop()) {
left.style.height = `${window.innerHeight}px`;
} else {
left.style.height = ''; // Reset for mobile
}
}
function onScroll() {
if (!isDesktop()) {
resetPositioning();
return;
}
const containerRect = container.getBoundingClientRect();
const leftHeight = left.offsetHeight;
const rightHeight = right.offsetHeight;
if (rightHeight <= leftHeight) {
resetPositioning();
return;
}
if (containerRect.top <= 0 && containerRect.bottom > leftHeight) {
// Stick to top
left.style.position = 'fixed';
left.style.top = '0';
left.style.width = `${left.parentElement.offsetWidth}px`;
spacer.style.display = 'block';
} else if (containerRect.bottom <= leftHeight) {
// Stick to bottom of container
const offsetTop = container.offsetTop;
const bottomOffset = container.offsetHeight - leftHeight;
left.style.position = 'absolute';
left.style.top = `${bottomOffset}px`;
left.style.width = `${left.parentElement.offsetWidth}px`;
spacer.style.display = 'block';
} else {
resetPositioning();
}
}
window.addEventListener('scroll', () => {
onScroll();
updateLeftHeight(); // Optional: if height can change on scroll
});
window.addEventListener('resize', () => {
spacer.style.height = `${left.offsetHeight}px`;
onScroll();
updateLeftHeight();
});
onScroll();
updateLeftHeight();
})();
Key Features of the Script
- Applies only on desktop (
≥ 992px
). - Keeps the
.buzy__left-inner
(map and filters) visible as you scroll. - Prevents sticky behavior from starting too early.
- Calculates exact bottom limit based on scroll position.
- Adjusts dynamically on
resize
andscroll
.
Why We Did This for FacetWP
When using FacetWP with a map and result list, users expect the map (and possibly filters) to remain visible. Instead of relying on position: sticky
(which lacks control over container boundaries), we:
- Used JavaScript to detect container position.
- Ensured the map sticks only within the container.
- Set the height of the sticky map column to match
window.innerHeight
.
This provides a smooth user experience, especially for long result lists where users want to refer back to the map while scrolling.
Final Tip
If you use a fixed header, you may want to offset the sticky position by adjusting this line:
if (containerRect.top <= 0 && scrollBottom < containerBottom)
To:
if (containerRect.top <= headerHeight && scrollBottom < containerBottom)
Then define:
const headerHeight = 80; // or whatever your fixed header height is