In the OpenLayers overlay example:
http://openlayers.org/en/v3.11.2/examples/overlay.html
If you click near the top of map most of the overlay is hidden. Is there a CSS trick, or an OpenLayers setting (I do not want to use the autoPan, which doesn't seem to work for popovers anyway), that will enable the entire popover to be shown even if it extends beyond the map view?
Here's a screenshot that illustrates the problem.
autoPan
does work for popups, see here: http://openlayers.org/en/v3.11.2/examples/popup.html
However, I also had some trouble with autoPan
so I didi it like this (Fiddle demo):
// move map if popop sticks out of map area:
var extent = map.getView().calculateExtent(map.getSize());
var center = map.getView().getCenter();
var pixelPosition = map.getPixelFromCoordinate([ coordinate[0], coordinate[1] ]);
var mapWidth = $("#map").width();
var mapHeight = $("#map").height();
var popoverHeight = $("#popup").height();
var popoverWidth = $("#popup").width();
var thresholdTop = popoverHeight+50;
var thresholdBottom = mapHeight;
var thresholdLeft = popoverWidth/2-80;
var thresholdRight = mapWidth-popoverWidth/2-130;
if(pixelPosition[0] < thresholdLeft || pixelPosition[0] > thresholdRight || pixelPosition[1]<thresholdTop || pixelPosition[1]>thresholdBottom) {
if(pixelPosition[0] < thresholdLeft) {
var newX = pixelPosition[0]+(thresholdLeft-pixelPosition[0]);
} else if(pixelPosition[0] > thresholdRight) {
var newX = pixelPosition[0]-(pixelPosition[0]-thresholdRight);
} else {
var newX = pixelPosition[0];
}
if(pixelPosition[1]<thresholdTop) {
var newY = pixelPosition[1]+(thresholdTop-pixelPosition[1]);
} else if(pixelPosition[1]>thresholdBottom) {
var newY = pixelPosition[1]-(pixelPosition[1]-thresholdBottom);
} else {
var newY = pixelPosition[1];
}
newCoordinate = map.getCoordinateFromPixel([newX, newY]);
newCenter = [(center[0]-(newCoordinate[0]-coordinate[0])), (center[1]-(newCoordinate[1]-coordinate[1])) ]
map.getView().setCenter(newCenter);
}
I added this code to the Popover Official Example in this fiddle demo:
// get DOM element generated by Bootstrap
var bs_element = document.getElementById(element.getAttribute('aria-describedby'));
var offset_height = 10;
// get computed popup height and add some offset
var popup_height = bs_element.offsetHeight + offset_height;
var clicked_pixel = evt.pixel;
// how much space (height) left between clicked pixel and top
var height_left = clicked_pixel[1] - popup_height;
var view = map.getView();
// get the actual center
var center = view.getCenter();
if (height_left < 0) {
var center_px = map.getPixelFromCoordinate(center);
var new_center_px = [
center_px[0],
center_px[1] + height_left
];
map.beforeRender(ol.animation.pan({
source: center,
start: Date.now(),
duration: 300
}));
view.setCenter(map.getCoordinateFromPixel(new_center_px));
}
To make the popup always appear inside the map view, I reversed the ol3 autopan function
So that it the popup is offset from the feature towards the view, instead of panning the view.
I am not sure why so many ol3 fiddles are not loading the map anymore.
http://jsfiddle.net/bunjil/L6rztwj8/48/
var getOverlayOffsets = function(mapInstance, overlay) {
const overlayRect = overlay.getElement().getBoundingClientRect();
const mapRect = mapInstance.getTargetElement().getBoundingClientRect();
const margin = 15;
// if (!ol.extent.containsExtent(mapRect, overlayRect)) //could use, but need to convert rect to extent
const offsetLeft = overlayRect.left - mapRect.left;
const offsetRight = mapRect.right - overlayRect.right;
const offsetTop = overlayRect.top - mapRect.top;
const offsetBottom = mapRect.bottom - overlayRect.bottom;
console.log('offsets', offsetLeft, offsetRight, offsetTop, offsetBottom);
const delta = [0, 0];
if (offsetLeft < 0) {
// move overlay to the right
delta[0] = margin - offsetLeft;
} else if (offsetRight < 0) {
// move overlay to the left
delta[0] = -(Math.abs(offsetRight) + margin);
}
if (offsetTop < 0) {
// will change the positioning instead of the offset to move overlay down.
delta[1] = margin - offsetTop;
} else if (offsetBottom < 0) {
// move overlay up - never happens if bottome-center is default.
delta[1] = -(Math.abs(offsetBottom) + margin);
}
return (delta);
};
/**
* Add a click handler to the map to render the popup.
*/
map.on('click', function(evt) {
var coordinate = evt.coordinate;
var hdms = ol.coordinate.toStringHDMS(ol.proj.transform(
coordinate, 'EPSG:3857', 'EPSG:4326'));
content.innerHTML = '<p>You clicked here:</p><code>' + hdms +
'</code>';
//overlay.setPosition(coordinate);
overlay.setOffset([0, 0]); // restore default
overlay.setPositioning('bottom-right'); // restore default
//overlay.set('autopan', true, false); //only need to do once.
overlay.setPosition(coordinate);
const delta = getOverlayOffsets(map, overlay);
if (delta[1] > 0) {
overlay.setPositioning('bottom-center');
}
overlay.setOffset(delta);
})
In this fiddle, the setPositioning() isn't working, so when you click near the top, the popup is under your mouse - it would be better to setPositioning('bottom-center');
automove would be a good feature to complement autopan.
In case of popover where "autoPan" option is not available you have to check extent's limits (top/bottom/right - left is skipped since popover is spawned on the center right of feature). So extending previous answer of Jonatas Walker a bit:
var bs_element = $('.popover');
var popup_height = bs_element.height();
var popup_width = bs_element.width();
var clicked_pixel = evt.pixel;
var view = map.getView();
var center = view.getCenter();
var height_left = clicked_pixel[1] - popup_height / 2; // from top
var height_left2 = clicked_pixel[1] + popup_height / 2; // from bottom
var width_left = clicked_pixel[0] + popup_width; // from right
var center_px = map.getPixelFromCoordinate(center);
var new_center_px = center_px;
var needs_recenter = false;
if (height_left2 > $("#map").height()) {
new_center_px[1] = height_left2 - center_px[1] + 30;
needs_recenter = true;
}
else if (height_left < 0) {
new_center_px[1] = center_px[1] + height_left;
needs_recenter = true;
}
if (width_left > $("#map").width()) {
new_center_px[0] = width_left - center_px[0] + 30;
needs_recenter = true;
}
if (needs_recenter)
view.setCenter(map.getCoordinateFromPixel(new_center_px));