Merge pull request #3256 from tsauerwein/overlay-panIntoView

Add autoPan option to ol.Overlay
This commit is contained in:
Tobias Sauerwein
2015-02-23 17:23:00 +01:00
4 changed files with 181 additions and 5 deletions

View File

@@ -29,9 +29,12 @@ closer.onclick = function() {
/**
* Create an overlay to anchor the popup to the map.
*/
var overlay = new ol.Overlay({
element: container
});
var overlay = new ol.Overlay(/** @type {olx.OverlayOptions} */ ({
element: container,
autoPanAnimation: {
duration: 250
}
}));
/**
@@ -60,7 +63,7 @@ var map = new ol.Map({
/**
* Add a click handler to the map to render the popup.
*/
map.on('click', function(evt) {
map.on('singleclick', function(evt) {
var coordinate = evt.coordinate;
var hdms = ol.coordinate.toStringHDMS(ol.proj.transform(
coordinate, 'EPSG:3857', 'EPSG:4326'));

View File

@@ -310,7 +310,10 @@ olx.MapOptions.prototype.view;
* position: (ol.Coordinate|undefined),
* positioning: (ol.OverlayPositioning|string|undefined),
* stopEvent: (boolean|undefined),
* insertFirst: (boolean|undefined)}}
* insertFirst: (boolean|undefined),
* autoPan: (boolean|undefined),
* autoPanAnimation: (olx.animation.PanOptions|undefined),
* autoPanMargin: (number|undefined)}}
* @api stable
*/
olx.OverlayOptions;
@@ -376,6 +379,35 @@ olx.OverlayOptions.prototype.stopEvent;
olx.OverlayOptions.prototype.insertFirst;
/**
* If set to `true` the map is panned when calling `setPosition`, so that the
* overlay is entirely visible in the current viewport.
* The default is `true`.
* @type {boolean|undefined}
* @api
*/
olx.OverlayOptions.prototype.autoPan;
/**
* The options used to create a `ol.animation.pan` animation. This animation
* is only used when `autoPan` is enabled. By default the default options for
* `ol.animation.pan` are used. If set to `null` the panning is not animated.
* @type {olx.animation.PanOptions|undefined}
* @api
*/
olx.OverlayOptions.prototype.autoPanAnimation;
/**
* The margin (in pixels) between the overlay and the borders of the map when
* autopanning. The default is `20`.
* @type {number|undefined}
* @api
*/
olx.OverlayOptions.prototype.autoPanMargin;
/**
* Object literal with config options for the projection.
* @typedef {{code: string,

View File

@@ -298,3 +298,35 @@ ol.dom.transformElement2D =
// content size.
}
};
/**
* Get the current computed width for the given element including margin,
* padding and border.
* Equivalent to jQuery's `$(el).outerWidth(true)`.
* @param {!Element} element Element.
* @return {number}
*/
ol.dom.outerWidth = function(element) {
var width = element.offsetWidth;
var style = element.currentStyle || window.getComputedStyle(element);
width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);
return width;
};
/**
* Get the current computed height for the given element including margin,
* padding and border.
* Equivalent to jQuery's `$(el).outerHeight(true)`.
* @param {!Element} element Element.
* @return {number}
*/
ol.dom.outerHeight = function(element) {
var height = element.offsetHeight;
var style = element.currentStyle || window.getComputedStyle(element);
height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);
return height;
};

View File

@@ -11,6 +11,9 @@ goog.require('ol.Coordinate');
goog.require('ol.Map');
goog.require('ol.MapEventType');
goog.require('ol.Object');
goog.require('ol.animation');
goog.require('ol.dom');
goog.require('ol.extent');
/**
@@ -90,6 +93,26 @@ ol.Overlay = function(options) {
this.element_ = goog.dom.createElement(goog.dom.TagName.DIV);
this.element_.style.position = 'absolute';
/**
* @private
* @type {boolean}
*/
this.autoPan_ = goog.isDef(options.autoPan) ? options.autoPan : true;
/**
* @private
* @type {olx.animation.PanOptions}
*/
this.autoPanAnimation_ = goog.isDef(options.autoPanAnimation) ?
options.autoPanAnimation : /** @type {olx.animation.PanOptions} */ ({});
/**
* @private
* @type {number}
*/
this.autoPanMargin_ = goog.isDef(options.autoPanMargin) ?
options.autoPanMargin : 20;
/**
* @private
* @type {{bottom_: string,
@@ -291,6 +314,9 @@ ol.Overlay.prototype.handleOffsetChanged = function() {
*/
ol.Overlay.prototype.handlePositionChanged = function() {
this.updatePixelPosition_();
if (goog.isDef(this.get(ol.OverlayProperty.POSITION)) && this.autoPan_) {
this.panIntoView_();
}
};
@@ -364,6 +390,89 @@ goog.exportProperty(
ol.Overlay.prototype.setPosition);
/**
* Pan the map so that the overlay is entirely visible in the current viewport
* (if necessary).
* @private
*/
ol.Overlay.prototype.panIntoView_ = function() {
goog.asserts.assert(this.autoPan_);
var map = this.getMap();
if (!goog.isDef(map) || goog.isNull(map.getTargetElement())) {
return;
}
var mapRect = this.getRect_(map.getTargetElement(), map.getSize());
var element = this.getElement();
goog.asserts.assert(!goog.isNull(element) && goog.isDef(element));
var overlayRect = this.getRect_(element,
[ol.dom.outerWidth(element), ol.dom.outerHeight(element)]);
var margin = this.autoPanMargin_;
if (!ol.extent.containsExtent(mapRect, overlayRect)) {
// the overlay is not completely inside the viewport, so pan the map
var offsetLeft = overlayRect[0] - mapRect[0];
var offsetRight = mapRect[2] - overlayRect[2];
var offsetTop = overlayRect[1] - mapRect[1];
var offsetBottom = mapRect[3] - overlayRect[3];
var delta = [0, 0];
if (offsetLeft < 0) {
// move map to the left
delta[0] = offsetLeft - margin;
} else if (offsetRight < 0) {
// move map to the right
delta[0] = Math.abs(offsetRight) + margin;
}
if (offsetTop < 0) {
// move map up
delta[1] = offsetTop - margin;
} else if (offsetBottom < 0) {
// move map down
delta[1] = Math.abs(offsetBottom) + margin;
}
if (delta[0] !== 0 || delta[1] !== 0) {
var center = map.getView().getCenter();
goog.asserts.assert(goog.isDef(center));
var centerPx = map.getPixelFromCoordinate(center);
var newCenterPx = [
centerPx[0] + delta[0],
centerPx[1] + delta[1]
];
if (!goog.isNull(this.autoPanAnimation_)) {
this.autoPanAnimation_.source = center;
map.beforeRender(ol.animation.pan(this.autoPanAnimation_));
}
map.getView().setCenter(map.getCoordinateFromPixel(newCenterPx));
}
}
};
/**
* Get the extent of an element relative to the document
* @param {Element|undefined} element The element.
* @param {ol.Size|undefined} size The size of the element.
* @return {ol.Extent}
* @private
*/
ol.Overlay.prototype.getRect_ = function(element, size) {
goog.asserts.assert(!goog.isNull(element) && goog.isDef(element));
goog.asserts.assert(goog.isDef(size));
var offset = goog.style.getPageOffset(element);
return [
offset.x,
offset.y,
offset.x + size[0],
offset.y + size[1]
];
};
/**
* Set the positioning for this overlay.
* @param {ol.OverlayPositioning} positioning how the overlay is