View / implement a smooth rebound effect when a max extent is given
This is done by applying the center constraint differently when we're in the middle of an interaction/animation or not. When the view is moving, the center constraint will restrain the given value in an "elastic" way, using a logarithmic function. This can be disabled using the `smoothCenterConstrain` view parameter.
This commit is contained in:
+21
-2
@@ -97,6 +97,9 @@ import {easeOut} from './easing';
|
|||||||
* view, in other words, nothing outside of this extent can be visible on the map
|
* view, in other words, nothing outside of this extent can be visible on the map
|
||||||
* @property {boolean} [constrainOnlyCenter] If true, the extent
|
* @property {boolean} [constrainOnlyCenter] If true, the extent
|
||||||
* constraint will only apply to the center and not the whole view.
|
* constraint will only apply to the center and not the whole view.
|
||||||
|
* @property {boolean} [smoothExtentConstraint] If true, the extent
|
||||||
|
* constraint will be applied smoothly, i. e. allow the view to go slightly outside
|
||||||
|
* of the given `extent`. Default is true.
|
||||||
* @property {number} [maxResolution] The maximum resolution used to determine
|
* @property {number} [maxResolution] The maximum resolution used to determine
|
||||||
* the resolution constraint. It is used together with `minResolution` (or
|
* the resolution constraint. It is used together with `minResolution` (or
|
||||||
* `maxZoom`) and `zoomFactor`. If unspecified it is calculated in such a way
|
* `maxZoom`) and `zoomFactor`. If unspecified it is calculated in such a way
|
||||||
@@ -616,6 +619,10 @@ class View extends BaseObject {
|
|||||||
if (more && this.updateAnimationKey_ === undefined) {
|
if (more && this.updateAnimationKey_ === undefined) {
|
||||||
this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_);
|
this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.getAnimating()) {
|
||||||
|
this.resolveConstraints_();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1094,6 +1101,16 @@ class View extends BaseObject {
|
|||||||
return !!this.getCenter() && this.getResolution() !== undefined;
|
return !!this.getCenter() && this.getResolution() !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds relative coordinates to the center of the view.
|
||||||
|
* @param {import("./coordinate.js").Coordinate} deltaCoordinates Relative value to add.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
adjustCenter(deltaCoordinates) {
|
||||||
|
const center = this.targetCenter_;
|
||||||
|
this.setCenter([center[0] + deltaCoordinates[0], center[1] + deltaCoordinates[1]]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate the view around a given coordinate.
|
* Rotate the view around a given coordinate.
|
||||||
* @param {number} rotation New rotation value for the view.
|
* @param {number} rotation New rotation value for the view.
|
||||||
@@ -1196,7 +1213,7 @@ class View extends BaseObject {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
resolveConstraints_(opt_duration, opt_resolutionDirection) {
|
resolveConstraints_(opt_duration, opt_resolutionDirection) {
|
||||||
const duration = opt_duration || 250;
|
const duration = opt_duration || 200;
|
||||||
const direction = opt_resolutionDirection || 0;
|
const direction = opt_resolutionDirection || 0;
|
||||||
|
|
||||||
const newRotation = this.constraints_.rotation(this.targetRotation_);
|
const newRotation = this.constraints_.rotation(this.targetRotation_);
|
||||||
@@ -1206,6 +1223,7 @@ class View extends BaseObject {
|
|||||||
|
|
||||||
if (this.getResolution() !== newResolution ||
|
if (this.getResolution() !== newResolution ||
|
||||||
this.getRotation() !== newRotation ||
|
this.getRotation() !== newRotation ||
|
||||||
|
!this.getCenter() ||
|
||||||
!equals(this.getCenter(), newCenter)) {
|
!equals(this.getCenter(), newCenter)) {
|
||||||
|
|
||||||
if (this.getAnimating()) {
|
if (this.getAnimating()) {
|
||||||
@@ -1291,7 +1309,8 @@ function animationCallback(callback, returnValue) {
|
|||||||
*/
|
*/
|
||||||
export function createCenterConstraint(options) {
|
export function createCenterConstraint(options) {
|
||||||
if (options.extent !== undefined) {
|
if (options.extent !== undefined) {
|
||||||
return createExtent(options.extent, options.constrainOnlyCenter);
|
return createExtent(options.extent, options.constrainOnlyCenter,
|
||||||
|
options.smoothExtentConstraint !== undefined ? options.smoothExtentConstraint : true);
|
||||||
} else {
|
} else {
|
||||||
return centerNone;
|
return centerNone;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import {clamp} from './math.js';
|
|||||||
/**
|
/**
|
||||||
* @param {import("./extent.js").Extent} extent Extent.
|
* @param {import("./extent.js").Extent} extent Extent.
|
||||||
* @param {boolean} onlyCenter If true, the constraint will only apply to the view center.
|
* @param {boolean} onlyCenter If true, the constraint will only apply to the view center.
|
||||||
|
* @param {boolean} smooth If true, the view will be able to go slightly out of the given extent
|
||||||
|
* (only during interaction and animation).
|
||||||
* @return {Type} The constraint.
|
* @return {Type} The constraint.
|
||||||
*/
|
*/
|
||||||
export function createExtent(extent, onlyCenter) {
|
export function createExtent(extent, onlyCenter, smooth) {
|
||||||
return (
|
return (
|
||||||
/**
|
/**
|
||||||
* @param {import("./coordinate.js").Coordinate|undefined} center Center.
|
* @param {import("./coordinate.js").Coordinate|undefined} center Center.
|
||||||
@@ -27,11 +29,23 @@ export function createExtent(extent, onlyCenter) {
|
|||||||
if (center) {
|
if (center) {
|
||||||
let viewWidth = onlyCenter ? 0 : size[0] * resolution;
|
let viewWidth = onlyCenter ? 0 : size[0] * resolution;
|
||||||
let viewHeight = onlyCenter ? 0 : size[1] * resolution;
|
let viewHeight = onlyCenter ? 0 : size[1] * resolution;
|
||||||
|
const minX = extent[0] + viewWidth / 2;
|
||||||
|
const maxX = extent[2] - viewWidth / 2;
|
||||||
|
const minY = extent[1] + viewHeight / 2;
|
||||||
|
const maxY = extent[3] - viewHeight / 2;
|
||||||
|
let x = clamp(center[0], minX, maxX);
|
||||||
|
let y = clamp(center[1], minY, maxY);
|
||||||
|
let ratio = 30 * resolution;
|
||||||
|
|
||||||
return [
|
// during an interaction, allow some overscroll
|
||||||
clamp(center[0], extent[0] + viewWidth / 2, extent[2] - viewWidth / 2),
|
if (opt_isMoving && smooth) {
|
||||||
clamp(center[1], extent[1] + viewHeight / 2, extent[3] - viewHeight / 2)
|
x += -ratio * Math.log(1 + Math.max(0, minX - center[0]) / ratio) +
|
||||||
];
|
ratio * Math.log(1 + Math.max(0, center[0] - maxX) / ratio);
|
||||||
|
y += -ratio * Math.log(1 + Math.max(0, minY - center[1]) / ratio) +
|
||||||
|
ratio * Math.log(1 + Math.max(0, center[1] - maxY) / ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [x, y];
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,15 +81,15 @@ class DragPan extends PointerInteraction {
|
|||||||
this.kinetic_.update(centroid[0], centroid[1]);
|
this.kinetic_.update(centroid[0], centroid[1]);
|
||||||
}
|
}
|
||||||
if (this.lastCentroid) {
|
if (this.lastCentroid) {
|
||||||
const deltaX = this.lastCentroid[0] - centroid[0];
|
const delta = [
|
||||||
const deltaY = centroid[1] - this.lastCentroid[1];
|
this.lastCentroid[0] - centroid[0],
|
||||||
|
centroid[1] - this.lastCentroid[1]
|
||||||
|
];
|
||||||
const map = mapBrowserEvent.map;
|
const map = mapBrowserEvent.map;
|
||||||
const view = map.getView();
|
const view = map.getView();
|
||||||
let center = [deltaX, deltaY];
|
scaleCoordinate(delta, view.getResolution());
|
||||||
scaleCoordinate(center, view.getResolution());
|
rotateCoordinate(delta, view.getRotation());
|
||||||
rotateCoordinate(center, view.getRotation());
|
view.adjustCenter(delta);
|
||||||
addCoordinate(center, view.getCenter());
|
|
||||||
view.setCenter(center);
|
|
||||||
}
|
}
|
||||||
} else if (this.kinetic_) {
|
} else if (this.kinetic_) {
|
||||||
// reset so we don't overestimate the kinetic energy after
|
// reset so we don't overestimate the kinetic energy after
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('ol.View', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with extent option', function() {
|
describe('with extent option and center only', function() {
|
||||||
it('gives a correct center constraint function', function() {
|
it('gives a correct center constraint function', function() {
|
||||||
const options = {
|
const options = {
|
||||||
extent: [0, 0, 1, 1],
|
extent: [0, 0, 1, 1],
|
||||||
@@ -55,6 +55,26 @@ describe('ol.View', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('with extent option', function() {
|
||||||
|
it('gives a correct center constraint function', function() {
|
||||||
|
const options = {
|
||||||
|
extent: [0, 0, 1, 1]
|
||||||
|
};
|
||||||
|
const fn = createCenterConstraint(options);
|
||||||
|
const res = 1;
|
||||||
|
const size = [0.15, 0.1];
|
||||||
|
expect(fn([0, 0], res, size)).to.eql([0.075, 0.05]);
|
||||||
|
expect(fn([0.5, 0.5], res, size)).to.eql([0.5, 0.5]);
|
||||||
|
expect(fn([10, 10], res, size)).to.eql([0.925, 0.95]);
|
||||||
|
|
||||||
|
const overshootCenter = fn([10, 10], res, size, true);
|
||||||
|
expect(overshootCenter[0] > 0.925).to.eql(true);
|
||||||
|
expect(overshootCenter[1] > 0.95).to.eql(true);
|
||||||
|
expect(overshootCenter[0] < 9).to.eql(true);
|
||||||
|
expect(overshootCenter[1] < 9).to.eql(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create resolution constraint', function() {
|
describe('create resolution constraint', function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user