Merge pull request #3523 from Leaflet/fractional-zoom-controls
Fractional zoom controls
This commit is contained in:
commit
09c3221211
109
debug/map/zoom-delta.html
Normal file
109
debug/map/zoom-delta.html
Normal file
@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Leaflet debug page</title>
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0" />
|
||||
|
||||
<link rel="stylesheet" href="../../dist/leaflet.css" />
|
||||
|
||||
<link rel="stylesheet" href="../css/mobile.css" />
|
||||
|
||||
<script type="text/javascript" src="../../build/deps.js"></script>
|
||||
<script src="../leaflet-include.js"></script>
|
||||
<style>
|
||||
.container {
|
||||
float:left; width: 600px; height: 600px;
|
||||
position: relative;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
#map1, #map2 {
|
||||
position:absolute;
|
||||
top:2em;
|
||||
bottom:2em;
|
||||
left:0;
|
||||
right:0;
|
||||
}
|
||||
#zoom1, #zoom2 {
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
left:0;
|
||||
right:0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Zoom delta test.</h1>
|
||||
|
||||
<p>Zooming with touch zoom, box zoom or flyTo then <code>map.stop()</code> must make the zoom level snap to the value of the <code>zoomSnap</code> option. Zoom interactions (keyboard, mouse wheel, zoom control buttons must change the zoom by the amount in the <code>zoomDelta</code> option.</p>
|
||||
|
||||
<div>
|
||||
<button id="sf">SF</button>
|
||||
<button id="trd">TRD</button>
|
||||
<button id="stop">stop</button>
|
||||
</div>
|
||||
|
||||
<div class='container'>
|
||||
Snap: 0.25. Delta: 0.5.
|
||||
<div id="map1"></div>
|
||||
<span id="zoom1"></span>
|
||||
</div>
|
||||
<div class='container'>
|
||||
Snap: 0 (off). Delta: 0.25.
|
||||
<div id="map2"></div>
|
||||
<span id="zoom2"></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var sf = [37.77, -122.42],
|
||||
trd = [63.41, 10.41];
|
||||
|
||||
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
osm1 = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib}),
|
||||
osm2 = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib}),
|
||||
center = L.latLng(63.41, 10.41);
|
||||
|
||||
var map1 = new L.Map('map1', {
|
||||
center: center,
|
||||
layers: [osm1],
|
||||
zoom: 5,
|
||||
zoomSnap: 0.25,
|
||||
zoomDelta: 0.5,
|
||||
wheelPxPerZoomLevel: 50
|
||||
});
|
||||
|
||||
var map2 = new L.Map('map2', {
|
||||
center: center,
|
||||
layers: [osm2],
|
||||
zoom: 5,
|
||||
zoomSnap: 0,
|
||||
zoomDelta: 0.25,
|
||||
wheelPxPerZoomLevel: 50
|
||||
});
|
||||
|
||||
map1.on('zoomend',function(){
|
||||
document.getElementById('zoom1').innerHTML = "Zoom level: " + map1.getZoom();
|
||||
});
|
||||
map2.on('zoomend',function(){
|
||||
document.getElementById('zoom2').innerHTML = "Zoom level: " + map2.getZoom();
|
||||
});
|
||||
|
||||
document.getElementById('sf').onclick = function () {
|
||||
map1.flyTo(sf, 10, {duration: 20});
|
||||
map2.flyTo(sf, 10, {duration: 20});
|
||||
};
|
||||
document.getElementById('trd').onclick = function () {
|
||||
map1.flyTo(trd, 10, {duration: 20});
|
||||
map2.flyTo(trd, 10, {duration: 20});
|
||||
};
|
||||
document.getElementById('stop').onclick = function () {
|
||||
map1.stop();
|
||||
map2.stop();
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -624,6 +624,142 @@ describe("Map", function () {
|
||||
map.setView([0, 0], 0);
|
||||
map.once('zoomend', callback).flyTo(newCenter, newZoom);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#zoomIn and #zoomOut', function () {
|
||||
var center = L.latLng(22, 33);
|
||||
beforeEach(function () {
|
||||
map.setView(center, 10);
|
||||
});
|
||||
|
||||
it('zoomIn zooms by 1 zoom level by default', function (done) {
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(11);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
map.zoomIn(null, {animate: false});
|
||||
});
|
||||
|
||||
it('zoomOut zooms by 1 zoom level by default', function (done) {
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(9);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
map.zoomOut(null, {animate: false});
|
||||
});
|
||||
|
||||
it('zoomIn ignores the zoomDelta option on non-any3d browsers', function (done) {
|
||||
L.Browser.any3d = false;
|
||||
map.options.zoomSnap = 0.25;
|
||||
map.options.zoomDelta = 0.25;
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(11);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
map.zoomIn(null, {animate: false});
|
||||
});
|
||||
|
||||
it('zoomIn respects the zoomDelta option on any3d browsers', function (done) {
|
||||
L.Browser.any3d = true;
|
||||
map.options.zoomSnap = 0.25;
|
||||
map.options.zoomDelta = 0.25;
|
||||
map.setView(center, 10);
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(10.25);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
map.zoomIn(null, {animate: false});
|
||||
});
|
||||
|
||||
it('zoomOut respects the zoomDelta option on any3d browsers', function (done) {
|
||||
L.Browser.any3d = true;
|
||||
map.options.zoomSnap = 0.25;
|
||||
map.options.zoomDelta = 0.25;
|
||||
map.setView(center, 10);
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(9.75);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
map.zoomOut(null, {animate: false});
|
||||
});
|
||||
|
||||
it('zoomIn snaps to zoomSnap on any3d browsers', function (done) {
|
||||
map.options.zoomSnap = 0.25;
|
||||
map.setView(center, 10);
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(10.25);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
L.Browser.any3d = true;
|
||||
map.zoomIn(0.22, {animate: false});
|
||||
});
|
||||
|
||||
it('zoomOut snaps to zoomSnap on any3d browsers', function (done) {
|
||||
map.options.zoomSnap = 0.25;
|
||||
map.setView(center, 10);
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(9.75);
|
||||
expect(map.getCenter()).to.eql(center);
|
||||
done();
|
||||
});
|
||||
L.Browser.any3d = true;
|
||||
map.zoomOut(0.22, {animate: false});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fitBounds', function () {
|
||||
var center = L.latLng(22, 33),
|
||||
bounds = L.latLngBounds(L.latLng(1, 102), L.latLng(11, 122)),
|
||||
boundsCenter = bounds.getCenter();
|
||||
|
||||
beforeEach(function () {
|
||||
// fitBounds needs a map container with non-null area
|
||||
var container = map.getContainer();
|
||||
container.style.width = container.style.height = "100px";
|
||||
document.body.appendChild(container);
|
||||
map.setView(center, 10);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
document.body.removeChild(map.getContainer());
|
||||
});
|
||||
|
||||
it('Snaps zoom level to integer by default', function (done) {
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(2);
|
||||
expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true);
|
||||
done();
|
||||
});
|
||||
map.fitBounds(bounds, {animate: false});
|
||||
});
|
||||
|
||||
it('Snaps zoom to zoomSnap on any3d browsers', function (done) {
|
||||
map.options.zoomSnap = 0.25;
|
||||
L.Browser.any3d = true;
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(2.75);
|
||||
expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true);
|
||||
done();
|
||||
});
|
||||
map.fitBounds(bounds, {animate: false});
|
||||
});
|
||||
|
||||
it('Ignores zoomSnap on non-any3d browsers', function (done) {
|
||||
map.options.zoomSnap = 0.25;
|
||||
L.Browser.any3d = false;
|
||||
map.once('zoomend', function () {
|
||||
expect(map.getZoom()).to.eql(2);
|
||||
expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true);
|
||||
done();
|
||||
});
|
||||
map.fitBounds(bounds, {animate: false});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -45,13 +45,13 @@ L.Control.Zoom = L.Control.extend({
|
||||
|
||||
_zoomIn: function (e) {
|
||||
if (!this._disabled) {
|
||||
this._map.zoomIn(e.shiftKey ? 3 : 1);
|
||||
this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
|
||||
}
|
||||
},
|
||||
|
||||
_zoomOut: function (e) {
|
||||
if (!this._disabled) {
|
||||
this._map.zoomOut(e.shiftKey ? 3 : 1);
|
||||
this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -17,7 +17,9 @@ L.Map = L.Evented.extend({
|
||||
trackResize: true,
|
||||
markerZoomAnimation: true,
|
||||
maxBoundsViscosity: 0.0,
|
||||
transform3DLimit: 8388608 // Precision limit of a 32-bit float
|
||||
transform3DLimit: 8388608, // Precision limit of a 32-bit float
|
||||
zoomSnap: 1,
|
||||
zoomDelta: 1
|
||||
},
|
||||
|
||||
initialize: function (id, options) { // (HTMLElement or String, Object)
|
||||
@ -72,11 +74,13 @@ L.Map = L.Evented.extend({
|
||||
},
|
||||
|
||||
zoomIn: function (delta, options) {
|
||||
return this.setZoom(this._zoom + (delta || 1), options);
|
||||
delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
|
||||
return this.setZoom(this._zoom + delta, options);
|
||||
},
|
||||
|
||||
zoomOut: function (delta, options) {
|
||||
return this.setZoom(this._zoom - (delta || 1), options);
|
||||
delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
|
||||
return this.setZoom(this._zoom - delta, options);
|
||||
},
|
||||
|
||||
setZoomAround: function (latlng, zoom, options) {
|
||||
@ -232,11 +236,8 @@ L.Map = L.Evented.extend({
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
L.Util.cancelAnimFrame(this._flyToFrame);
|
||||
if (this._panAnim) {
|
||||
this._panAnim.stop();
|
||||
}
|
||||
return this;
|
||||
this.setZoom(this._limitZoom(this._zoom));
|
||||
return this._stop();
|
||||
},
|
||||
|
||||
// TODO handler.addTo
|
||||
@ -330,31 +331,25 @@ L.Map = L.Evented.extend({
|
||||
|
||||
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
|
||||
bounds = L.latLngBounds(bounds);
|
||||
|
||||
var zoom = this.getMinZoom() - (inside ? 1 : 0),
|
||||
maxZoom = this.getMaxZoom(),
|
||||
size = this.getSize(),
|
||||
|
||||
nw = bounds.getNorthWest(),
|
||||
se = bounds.getSouthEast(),
|
||||
|
||||
zoomNotFound = true,
|
||||
boundsSize;
|
||||
|
||||
padding = L.point(padding || [0, 0]);
|
||||
|
||||
do {
|
||||
zoom++;
|
||||
boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding).floor();
|
||||
zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
|
||||
var zoom = this.getZoom(),
|
||||
min = this.getMinZoom(),
|
||||
max = this.getMaxZoom(),
|
||||
nw = bounds.getNorthWest(),
|
||||
se = bounds.getSouthEast(),
|
||||
size = this.getSize(),
|
||||
boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding),
|
||||
snap = L.Browser.any3d ? this.options.zoomSnap : 1;
|
||||
|
||||
} while (zoomNotFound && zoom <= maxZoom);
|
||||
var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
|
||||
zoom = this.getScaleZoom(scale, zoom);
|
||||
|
||||
if (zoomNotFound && inside) {
|
||||
return null;
|
||||
if (snap) {
|
||||
zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
|
||||
}
|
||||
|
||||
return inside ? zoom : zoom - 1;
|
||||
return Math.max(min, Math.min(max, zoom));
|
||||
},
|
||||
|
||||
getSize: function () {
|
||||
@ -582,6 +577,14 @@ L.Map = L.Evented.extend({
|
||||
return this.fire('moveend');
|
||||
},
|
||||
|
||||
_stop: function () {
|
||||
L.Util.cancelAnimFrame(this._flyToFrame);
|
||||
if (this._panAnim) {
|
||||
this._panAnim.stop();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
_rawPanBy: function (offset) {
|
||||
L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
|
||||
},
|
||||
@ -837,9 +840,11 @@ L.Map = L.Evented.extend({
|
||||
|
||||
_limitZoom: function (zoom) {
|
||||
var min = this.getMinZoom(),
|
||||
max = this.getMaxZoom();
|
||||
if (!L.Browser.any3d) { zoom = Math.round(zoom); }
|
||||
|
||||
max = this.getMaxZoom(),
|
||||
snap = L.Browser.any3d ? this.options.zoomSnap : 1;
|
||||
if (snap) {
|
||||
zoom = Math.round(zoom / snap) * snap;
|
||||
}
|
||||
return Math.max(min, Math.min(max, zoom));
|
||||
}
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ L.Map.include({
|
||||
return this.setView(targetCenter, targetZoom, options);
|
||||
}
|
||||
|
||||
this.stop();
|
||||
this._stop();
|
||||
|
||||
var from = this.project(this.getCenter()),
|
||||
to = this.project(targetCenter),
|
||||
|
@ -10,7 +10,7 @@ L.Map.include({
|
||||
center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
|
||||
options = options || {};
|
||||
|
||||
this.stop();
|
||||
this._stop();
|
||||
|
||||
if (this._loaded && !options.reset && options !== true) {
|
||||
|
||||
|
@ -18,7 +18,8 @@ L.Map.DoubleClickZoom = L.Handler.extend({
|
||||
_onDoubleClick: function (e) {
|
||||
var map = this._map,
|
||||
oldZoom = map.getZoom(),
|
||||
zoom = e.originalEvent.shiftKey ? Math.ceil(oldZoom) - 1 : Math.floor(oldZoom) + 1;
|
||||
delta = map.options.zoomDelta,
|
||||
zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
|
||||
|
||||
if (map.options.doubleClickZoom === 'center') {
|
||||
map.setZoom(zoom);
|
||||
|
@ -54,7 +54,7 @@ L.Map.Drag = L.Handler.extend({
|
||||
},
|
||||
|
||||
_onDown: function () {
|
||||
this._map.stop();
|
||||
this._map._stop();
|
||||
},
|
||||
|
||||
_onDragStart: function () {
|
||||
|
@ -4,8 +4,7 @@
|
||||
|
||||
L.Map.mergeOptions({
|
||||
keyboard: true,
|
||||
keyboardPanOffset: 80,
|
||||
keyboardZoomOffset: 1
|
||||
keyboardPanDelta: 80
|
||||
});
|
||||
|
||||
L.Map.Keyboard = L.Handler.extend({
|
||||
@ -22,8 +21,8 @@ L.Map.Keyboard = L.Handler.extend({
|
||||
initialize: function (map) {
|
||||
this._map = map;
|
||||
|
||||
this._setPanOffset(map.options.keyboardPanOffset);
|
||||
this._setZoomOffset(map.options.keyboardZoomOffset);
|
||||
this._setPanDelta(map.options.keyboardPanDelta);
|
||||
this._setZoomDelta(map.options.zoomDelta);
|
||||
},
|
||||
|
||||
addHooks: function () {
|
||||
@ -84,35 +83,35 @@ L.Map.Keyboard = L.Handler.extend({
|
||||
this._map.fire('blur');
|
||||
},
|
||||
|
||||
_setPanOffset: function (pan) {
|
||||
_setPanDelta: function (panDelta) {
|
||||
var keys = this._panKeys = {},
|
||||
codes = this.keyCodes,
|
||||
i, len;
|
||||
|
||||
for (i = 0, len = codes.left.length; i < len; i++) {
|
||||
keys[codes.left[i]] = [-1 * pan, 0];
|
||||
keys[codes.left[i]] = [-1 * panDelta, 0];
|
||||
}
|
||||
for (i = 0, len = codes.right.length; i < len; i++) {
|
||||
keys[codes.right[i]] = [pan, 0];
|
||||
keys[codes.right[i]] = [panDelta, 0];
|
||||
}
|
||||
for (i = 0, len = codes.down.length; i < len; i++) {
|
||||
keys[codes.down[i]] = [0, pan];
|
||||
keys[codes.down[i]] = [0, panDelta];
|
||||
}
|
||||
for (i = 0, len = codes.up.length; i < len; i++) {
|
||||
keys[codes.up[i]] = [0, -1 * pan];
|
||||
keys[codes.up[i]] = [0, -1 * panDelta];
|
||||
}
|
||||
},
|
||||
|
||||
_setZoomOffset: function (zoom) {
|
||||
_setZoomDelta: function (zoomDelta) {
|
||||
var keys = this._zoomKeys = {},
|
||||
codes = this.keyCodes,
|
||||
i, len;
|
||||
|
||||
for (i = 0, len = codes.zoomIn.length; i < len; i++) {
|
||||
keys[codes.zoomIn[i]] = zoom;
|
||||
keys[codes.zoomIn[i]] = zoomDelta;
|
||||
}
|
||||
for (i = 0, len = codes.zoomOut.length; i < len; i++) {
|
||||
keys[codes.zoomOut[i]] = -zoom;
|
||||
keys[codes.zoomOut[i]] = -zoomDelta;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
L.Map.mergeOptions({
|
||||
scrollWheelZoom: true,
|
||||
wheelDebounceTime: 40
|
||||
wheelDebounceTime: 40,
|
||||
wheelPxPerZoomLevel: 50
|
||||
});
|
||||
|
||||
L.Map.ScrollWheelZoom = L.Handler.extend({
|
||||
@ -40,14 +41,15 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
|
||||
|
||||
_performZoom: function () {
|
||||
var map = this._map,
|
||||
delta = this._delta,
|
||||
zoom = map.getZoom();
|
||||
|
||||
map.stop(); // stop panning and fly animations if any
|
||||
map._stop(); // stop panning and fly animations if any
|
||||
|
||||
// map the delta with a sigmoid function to -4..4 range leaning on -1..1
|
||||
var d2 = Math.ceil(4 * Math.log(2 / (1 + Math.exp(-Math.abs(delta / 200)))) / Math.LN2);
|
||||
delta = map._limitZoom(zoom + (delta > 0 ? d2 : -d2)) - zoom;
|
||||
var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
|
||||
d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
|
||||
d4 = Math.max(d3, this._map.options.zoomSnap || 0),
|
||||
delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
|
||||
|
||||
this._delta = 0;
|
||||
this._startTime = null;
|
||||
|
@ -36,7 +36,7 @@ L.Map.TouchZoom = L.Handler.extend({
|
||||
this._moved = false;
|
||||
this._zooming = true;
|
||||
|
||||
map.stop();
|
||||
map._stop();
|
||||
|
||||
L.DomEvent
|
||||
.on(document, 'touchmove', this._onTouchMove, this)
|
||||
@ -97,11 +97,8 @@ L.Map.TouchZoom = L.Handler.extend({
|
||||
.off(document, 'touchmove', this._onTouchMove)
|
||||
.off(document, 'touchend', this._onTouchEnd);
|
||||
|
||||
var zoom = this._zoom;
|
||||
zoom = this._map._limitZoom(zoom - this._startZoom > 0 ? Math.ceil(zoom) : Math.floor(zoom));
|
||||
|
||||
|
||||
this._map._animateZoom(this._center, zoom, true, true);
|
||||
// Pinch updates GridLayers' levels only when snapZoom is off, so snapZoom becomes noUpdate.
|
||||
this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.snapZoom);
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user