Complete Guide: Fixing Mouse Wheel Scroll in Overlay Panels
Problem
When you have a scrollable panel (like a filter list, modal, or dropdown) overlaid on a Google Map, the mouse wheel often doesn’t work as expected:
- You scroll inside the panel, but nothing happens
- Or the map zooms instead of the panel scrolling
- Or scroll works intermittently
Why This Happens
There are two separate systems fighting for wheel events:
- CSS Scroll Context - The browser’s native scrolling behavior
- JavaScript Event Listeners - Google Maps registers wheel listeners for zoom functionality
CSS properties alone can’t solve this because the map’s JavaScript listener captures the event before your CSS scroll can use it.
The Complete Solution
You need both CSS and JavaScript working together.
Step 1: CSS Foundation
Set up proper scroll containment on your scrollable container:
.scrollable-panel {
// Core scrolling
overflow-y: auto;
overflow-x: hidden;
max-height: 400px; // Or calc() based on viewport
// iOS smooth scrolling
-webkit-overflow-scrolling: touch;
// Prevent scroll chaining to parent elements
overscroll-behavior: contain;
}
What Each Property Does
-
overflow-y: auto
Enable vertical scrolling when content overflows -
overflow-x: hidden
Prevent horizontal scroll (cleaner UX) -
max-height
Define the scrollable boundary - required for overflow to work -
-webkit-overflow-scrolling: touch
Momentum scrolling on iOS Safari -
overscroll-behavior: contain
Prevents scroll from “chaining” to parent when you hit top/bottom
Important: Without max-height, the container will expand to fit content and never scroll.
Step 2: JavaScript Event Handler (The Key Fix)
CSS alone won’t work when a map is involved. You must stop the wheel event from propagating to the map’s listener:
wheelListenerAttached = false
componentDidMount() {
this.attachWheelListener()
}
componentDidUpdate(prevProps) {
// Re-attach when component becomes visible
if (this.props.show && !prevProps.show) {
this.attachWheelListener()
}
}
componentWillUnmount() {
this.detachWheelListener()
}
attachWheelListener = () => {
const element = document.getElementById('my-scrollable-panel')
if (element && !this.wheelListenerAttached) {
element.addEventListener('wheel', this.handleWheel, { passive: false })
this.wheelListenerAttached = true
}
}
detachWheelListener = () => {
const element = document.getElementById('my-scrollable-panel')
if (element && this.wheelListenerAttached) {
element.removeEventListener('wheel', this.handleWheel)
this.wheelListenerAttached = false
}
}
handleWheel = (e) => {
e.stopPropagation() // This is the magic line
}
}
Why { passive: false }?
By default, browsers use passive event listeners for wheel events (for performance). Setting passive: false allows us to call stopPropagation(). Without this, the browser may ignore your event handler.
Step 3: Parent Container Setup
If your scrollable element is inside a positioned container (like an overlay), ensure the parent doesn’t interfere:
position: absolute;
max-height: calc(100vh - 80px); // Leave room for header/margins
// Do NOT add overflow-y here if child handles scrolling
// Having both parent and child with overflow-y causes conflicts
}
.scrollable-content {
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
max-height: calc(100vh - 120px); // Slightly smaller than parent
}
Debugging Checklist
If scroll still doesn’t work:
- Does the scrollbar appear?
If no, check max-height and overflow-y - Does clicking and dragging the scrollbar work?
If yes, it’s a wheel event issue (use JS fix) - Does the map zoom when you scroll over the panel?
If yes, events are propagating (use JS fix) - Does nothing happen at all?
Events are being swallowed - check for conflicting listeners
Check DevTools: Inspect the element’s computed styles for overflow properties
Browser Support
-
overflow-y: auto
All browsers -
-webkit-overflow-scrolling
Safari/iOS (ignored elsewhere) -
overscroll-behavior
Chrome 63+, Firefox 59+, Safari 16+, Edge 18+ -
{ passive: false }
All modern browsers