I try to find a way to adjust markers popup window position based on following condition: if popup is near map containers edge.
Reason: right now popup is being hidden under container if marker (and popup, ofc) is near edge.
Working example: I am not sure do I have rights to link you to a different website except sandboxes, but Zillow popups does everything I've described - popup adjust position a bit to the left if initial position is going to be under the right edge of container (accept this for each edge of container)
Simple approaches: tried to add zIndex to popup component to place popup on top of container, but it doesn't work 'cause of GoogleMap's canvas
My Markers component clusterer render function:
const renderClusterer = (clusterer, locations): any => {
return (
<OverlayViewF
mapPaneName={'overlayMouseTarget'}
position={{ lat: 0, lng: 0 }}
>
{locations?.map((location: IEstate, index) => (
<OverlayViewF
zIndex={999999}
key={location.id}
mapPaneName={'overlayMouseTarget'}
position={{ lat: location.latitude, lng: location.longitude }}
>
<MarkerF
position={{ lat: location.latitude, lng: location.longitude }}
icon={{
url: getMarkerIcon(location.isViewed, location.isFavorite),
scaledSize: new window.google.maps.Size(17, 17),
}}
onClick={(e) => navigate(`/estate/${location.id}`)}
onMouseOver={(e) => {
setTooltip(location)
globalStore.setHoveredEstateId(location.id)
}}
onMouseOut={(e) => {
setTooltip(null)
globalStore.setHoveredEstateId(null)
}}
clusterer={clusterer}
>
</MarkerF>
</OverlayViewF>
))}
{tooltip && tooltip.id && <StyledPopup marker={tooltip} />}
</OverlayViewF>
)
}
Actual implementation in the component jsx body:
return (
locations?.length > 0 && (
<MarkerClustererF
gridSize={11}
minimumClusterSize={10}
options={{
enableRetinaIcons: true,
}}
>
{(clusterer) => renderClusterer(clusterer, locations)}
</MarkerClustererF>
)
)
StyledPopup component jsx body:
<OverlayViewF
mapPaneName={'overlayMouseTarget'}
position={{ lat: marker.latitude, lng: marker.longitude }}
>
//...regular jsx here, does nothing with @react-google-maps/api
</OverlayViewF>
UPD:
Tried to use following function to calculate adjustments:
const adjustPopupPosition = useCallback(
(position) => {
const mapContainer = mapRef.current
if (!mapContainer) return position
console.log(!!mapContainer, mapContainer)
const mapContainerRect = mapContainer.getBoundingClientRect()
const popupWidth = 400
const maxRightPosition = mapContainerRect.width - popupWidth
const maxBottomPosition = mapContainerRect.height
const adjustedPosition = {
x: Math.min(position.x, maxRightPosition),
y: Math.min(position.y, maxBottomPosition),
}
return adjustedPosition
},
[mapRef],
)
And then used it in my StyledPopup like this:
<OverlayViewF
mapPaneName={'overlayMouseTarget'}
position={{ lat: adjustedPosition.x, lng: adjustedPosition.y }}
>
//rest of regular jsx
</OverlayViewF>
But still no results. Popup width is 400px.
In my StyledPopup.tsx:
IPositionMarkerState:
interface IPositionMarkerState {
lng: number
lat: number
}
FC:
function adjustPosition(
location: IPositionMarkerState,
map: google.maps.Map,
): { x: number; y: number } {
const overlay = new google.maps.OverlayView()
// eslint-disable-next-line @typescript-eslint/no-empty-function
overlay.draw = function () {} // empty function required
overlay.setMap(map)
let offsetX = 0
let offsetY = 0
const projection = overlay.getProjection()
if (!projection) return { x: offsetX, y: offsetY } // make sure projection exists
// Convert the LatLng location to pixel coordinates
const point = projection.fromLatLngToContainerPixel(
new google.maps.LatLng(location.lat, location.lng),
)
// Check if the location is within 350 pixels of the right edge
if (point!.x + 350 > map.getDiv().offsetWidth) {
offsetX = -350
}
// Check if the location is within 120 pixels of the bottom edge
if (point!.y + 120 > map.getDiv().offsetHeight) {
offsetY = -140
}
return { x: offsetX, y: offsetY } // Default no shift
}
const handlePosition = () => {
if (!mapStore.mapRef.current) return { x: 0, y: 0 }
return adjustPosition(
{ lat: marker.latitude, lng: marker.longitude },
mapStore.mapRef.current,
)
}
JSX:
return (
<OverlayViewF
mapPaneName={'floatPane'}
position={{ lat: marker.latitude, lng: marker.longitude }}
getPixelPositionOffset={() => handlePosition()}
>
//rest of the code
</OverlayViewF>
)
Basically I'm providing mapRef.current to adjustPosition(...) and returning the { x: number; y: number } that applies inside of OverlayViewF getPixelPositionOffset prop
Should be tweaked based on the popup width and height (offsetX -350 turns popup to the right, my popup width is 300, and offsetY -140 turns popup up, popup height is 120)