Make Infobubble Always Visible After Rendering

Ensure InfoBubble Remains Fully Visible After Rendering in HERE Maps JS API When Content Overflows Map Bounds

Problem Description:

When an end user taps on a marker or another map object in a HERE Maps JS API (Web SDK) application, an InfoBubble is rendered to display contextual information. However, if the content inside the InfoBubble is large or positioned near the edge of the map, part of the bubble may be rendered outside the visible map area. As a result, the user must manually pan the map to view the full content of the InfoBubble, which negatively affects usability and user experience.

This issue typically occurs when:

The marker is close to the map’s borders.
The InfoBubble contains rich or lengthy content.
The map viewport is small or zoomed in.

Solution:

The following code demonstrates a mechanism that automatically adjusts the map’s center after an InfoBubble is rendered in the HERE Maps JavaScript API (Web SDK). This ensures that the entire bubble remains visible within the map bounds, even when its content would otherwise overflow beyond the viewport. By calculating the bubble’s position and dimensions relative to the map container, the map is programmatically repositioned to avoid requiring manual panning by the user.

This approach improves usability by guaranteeing that contextual information is always fully accessible immediately after interaction with a map object.

Pay attention to the variables intersectionObserver and onStChg in the code.

URL to this example: https://jsfiddle.net/Lgaz79kh/1

```
window.apikey = "your apikey";/**
Creates a new marker and adds it to a group @param {H.map.Group} group The group holding the new marker @param {H.geo.Point} coordinate The location of the marker @param {String} html Data associated with the marker /function addMarkerToGroup(group, coordinate, html) { var marker = new H.map.Marker(coordinate); // add custom data to the marker marker.setData(html); group.addObject(marker);}/ Add two markers showing the position of Liverpool and Manchester City football clubs. Clicking on a marker opens an infobubble which holds HTML content related to the marker. @param {H.Map} map A HERE Map instance within the application /function addInfoBubble(map) { var group = new H.map.Group(); map.addObject(group); // add 'tap' event listener, that opens info bubble, to the group group.addEventListener('tap', function (evt) { // event target is the marker itself, group is a parent event target // for all objects that it contains const intersectionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { const containerRect = map.getElement().getBoundingClientRect(); const innerRect = entry.boundingClientRect const offsetRight = containerRect.right - innerRect.right; const offsetTop = innerRect.top - containerRect.top; const offsetLeft = innerRect.left - containerRect.left; //console.log('Bounding rect:', entry.boundingClientRect, containerRect); //console.log("offsetRight, offsetTop:", offsetRight, offsetTop, offsetLeft); if(innerRect.left != -90 && (offsetTop < 0 || offsetLeft < 0 || offsetRight < 0)) { intersectionObserver.disconnect(); let cs = map.geoToScreen(map.getCenter()); cs.y += offsetTop < 0 ? offsetTop - 1 : 0; cs.x += offsetLeft < 0 ? offsetLeft - 1 : 0; cs.x -= offsetRight < 0 ? offsetRight - 1 : 0; map.setCenter(map.screenToGeo(cs.x, cs.y), false); //console.log("cs:", cs); }else if(offsetTop >= 0){ intersectionObserver.disconnect(); } }); }); const onStChg = (e) => { const bubble = e.target; if(bubble.getState() == H.ui.InfoBubble.State.CLOSED){ return; } const content = bubble.getElement().querySelector(".H_ib_body"); intersectionObserver.observe(content); }; var bubble = new H.ui.InfoBubble(evt.target.getGeometry(), { // read custom data content: evt.target.getData(), onStateChange: onStChg }); //bubble.addEventListener('statechange', onStChg); // show info bubble ui.addBubble(bubble); bubble.setState(H.ui.InfoBubble.State.CLOSED); bubble.setState(H.ui.InfoBubble.State.OPEN); }, false); addMarkerToGroup(group, {lat: 53.439, lng: -2.221}, '

Manchester City

' + '

City of Manchester Stadium
Capacity: 55,097

'); addMarkerToGroup(group, {lat: 53.2, lng: -2.961}, '

Liverpool

' + '

Anfield
Capacity: 54,074

');}const engineType = H.Map.EngineType['HARP'];/
Boilerplate map initialization code starts below: /// initialize communication with the platform// In your own code, replace variable window.apikey with your own apikeyvar platform = new H.service.Platform({ apikey: window.apikey});var defaultLayers = platform.createDefaultLayers({ engineType: engineType});// initialize a map - this map is centered over Europevar map = new H.Map(document.getElementById('map'), defaultLayers.vector.normal.map, { engineType: engineType, center: {lat: 53.430, lng: -2.961}, zoom: 7, pixelRatio: window.devicePixelRatio || 1});// add a resize listener to make sure that the map occupies the whole containerwindow.addEventListener('resize', () => map.getViewPort().resize());// MapEvents enables the event system// Behavior implements default interactions for pan/zoom (also on mobile touch environments)var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));// create default UI with layers provided by the platformvar ui = H.ui.UI.createDefault(map, defaultLayers);// Now use the map as required...addInfoBubble(map);
```