Adding a PinchZoom control for smooth zooming on multi-touch devices. p=bbinet,me r=crschmidt (closes #3105)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@11544 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
@@ -182,6 +182,7 @@
|
|||||||
"OpenLayers/Control/ZoomToMaxExtent.js",
|
"OpenLayers/Control/ZoomToMaxExtent.js",
|
||||||
"OpenLayers/Control/DragPan.js",
|
"OpenLayers/Control/DragPan.js",
|
||||||
"OpenLayers/Control/Navigation.js",
|
"OpenLayers/Control/Navigation.js",
|
||||||
|
"OpenLayers/Control/PinchZoom.js",
|
||||||
"OpenLayers/Control/TouchNavigation.js",
|
"OpenLayers/Control/TouchNavigation.js",
|
||||||
"OpenLayers/Control/MouseDefaults.js",
|
"OpenLayers/Control/MouseDefaults.js",
|
||||||
"OpenLayers/Control/MousePosition.js",
|
"OpenLayers/Control/MousePosition.js",
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for
|
||||||
|
* full list of contributors). Published under the Clear BSD license.
|
||||||
|
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
|
||||||
|
* full text of the license. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires OpenLayers/Handler/Pinch.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class: OpenLayers.Control.PinchZoom
|
||||||
|
*
|
||||||
|
* Inherits:
|
||||||
|
* - <OpenLayers.Control>
|
||||||
|
*/
|
||||||
|
OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: type
|
||||||
|
* {OpenLayers.Control.TYPES}
|
||||||
|
*/
|
||||||
|
type: OpenLayers.Control.TYPE_TOOL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: containerOrigin
|
||||||
|
* {Object} Cached object representing the layer container origin (in pixels).
|
||||||
|
*/
|
||||||
|
containerOrigin: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: pinchOrigin
|
||||||
|
* {Object} Cached object representing the pinch start (in pixels).
|
||||||
|
*/
|
||||||
|
pinchOrigin: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: currentCenter
|
||||||
|
* {Object} Cached object representing the latest pinch center (in pixels).
|
||||||
|
*/
|
||||||
|
currentCenter: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIProperty: autoActivate
|
||||||
|
* {Boolean} Activate the control when it is added to a map. Default is
|
||||||
|
* true.
|
||||||
|
*/
|
||||||
|
autoActivate: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor: OpenLayers.Control.PinchZoom
|
||||||
|
* Create a control for zooming with pinch gestures. This works on devices
|
||||||
|
* with multi-touch support.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* options - {Object} An optional object whose properties will be set on
|
||||||
|
* the control
|
||||||
|
*/
|
||||||
|
initialize: function(options) {
|
||||||
|
OpenLayers.Control.prototype.initialize.apply(this, arguments);
|
||||||
|
this.handler = new OpenLayers.Handler.Pinch(this, {
|
||||||
|
start: this.pinchStart,
|
||||||
|
move: this.pinchMove,
|
||||||
|
done: this.pinchDone
|
||||||
|
}, this.handlerOptions);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIMethod: activate
|
||||||
|
* Activate this control. Must be called after the control is added to a
|
||||||
|
* map.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* {Boolean} The control was successfully activated.
|
||||||
|
*/
|
||||||
|
activate: function() {
|
||||||
|
var activated = OpenLayers.Control.prototype.activate.apply(this,arguments);
|
||||||
|
if (activated) {
|
||||||
|
this.map.events.on({
|
||||||
|
moveend: this.updateContainerOrigin,
|
||||||
|
scope: this
|
||||||
|
});
|
||||||
|
this.updateContainerOrigin();
|
||||||
|
}
|
||||||
|
return activated;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIMethod: deactivate
|
||||||
|
* Deactivate this control.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* {Boolean} The control was successfully deactivated.
|
||||||
|
*/
|
||||||
|
deactivate: function() {
|
||||||
|
var deactivated = OpenLayers.Control.prototype.deactivate.apply(this,arguments);
|
||||||
|
if (this.map && this.map.events) {
|
||||||
|
this.map.events.un({
|
||||||
|
moveend: this.updateContainerOrigin,
|
||||||
|
scope: this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deactivated;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: updateContainerOrigin
|
||||||
|
* Must be called each time the layer container origin changes.
|
||||||
|
*/
|
||||||
|
updateContainerOrigin: function() {
|
||||||
|
var container = this.map.layerContainerDiv;
|
||||||
|
this.containerOrigin = {
|
||||||
|
x: parseInt(container.style.left, 10),
|
||||||
|
y: parseInt(container.style.top, 10)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: pinchStart
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* evt - {Event}
|
||||||
|
* pinchData - {Object} pinch data object related to the current touchmove
|
||||||
|
* of the pinch gesture. This give us the current scale of the pinch.
|
||||||
|
*/
|
||||||
|
pinchStart: function(evt, pinchData) {
|
||||||
|
this.pinchOrigin = evt.xy;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: pinchMove
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* evt - {Event}
|
||||||
|
* pinchData - {Object} pinch data object related to the current touchmove
|
||||||
|
* of the pinch gesture. This give us the current scale of the pinch.
|
||||||
|
*/
|
||||||
|
pinchMove: function(evt, pinchData) {
|
||||||
|
var scale = pinchData.scale;
|
||||||
|
var containerOrigin = this.containerOrigin;
|
||||||
|
var pinchOrigin = this.pinchOrigin;
|
||||||
|
var current = evt.xy;
|
||||||
|
|
||||||
|
var dx = Math.round((current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
|
||||||
|
var dy = Math.round((current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
|
||||||
|
|
||||||
|
this.applyTransform(
|
||||||
|
"translate(" + dx + "px, " + dy + "px) scale(" + scale + ")"
|
||||||
|
);
|
||||||
|
this.currentCenter = current;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: applyTransform
|
||||||
|
* Applies the given transform to layers.
|
||||||
|
*/
|
||||||
|
applyTransform: function(transform) {
|
||||||
|
var style = this.map.layerContainerDiv.style;
|
||||||
|
style['-webkit-transform'] = transform;
|
||||||
|
style['-moz-transform'] = transform;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: pinchDone
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* evt - {Event}
|
||||||
|
* start - {Object} pinch data object related to the touchstart event that
|
||||||
|
* started the pinch gesture.
|
||||||
|
* last - {Object} pinch data object related to the last touchmove event
|
||||||
|
* of the pinch gesture. This give us the final scale of the pinch.
|
||||||
|
*/
|
||||||
|
pinchDone: function(evt, start, last) {
|
||||||
|
var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
|
||||||
|
var resolution = this.map.getResolutionForZoom(zoom);
|
||||||
|
|
||||||
|
var location = this.map.getLonLatFromPixel(this.pinchOrigin);
|
||||||
|
var zoomPixel = this.currentCenter;
|
||||||
|
var size = this.map.getSize();
|
||||||
|
|
||||||
|
location.lon += resolution * ((size.w / 2) - zoomPixel.x);
|
||||||
|
location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
|
||||||
|
|
||||||
|
this.map.setCenter(location, zoom);
|
||||||
|
|
||||||
|
var style = this.map.layerContainerDiv.style;
|
||||||
|
style['-webkit-transform'] = "";
|
||||||
|
style['-moz-transform'] = "";
|
||||||
|
},
|
||||||
|
|
||||||
|
CLASS_NAME: "OpenLayers.Control.PinchZoom"
|
||||||
|
|
||||||
|
});
|
||||||
@@ -5,14 +5,15 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @requires OpenLayers/Control/DragPan.js
|
* @requires OpenLayers/Control/DragPan.js
|
||||||
|
* @requires OpenLayers/Control/PinchZoom.js
|
||||||
* @requires OpenLayers/Handler/Click.js
|
* @requires OpenLayers/Handler/Click.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class: OpenLayers.Control.TouchNavigation
|
* Class: OpenLayers.Control.TouchNavigation
|
||||||
* The navigation control handles map browsing with touch events (dragging,
|
* The navigation control handles map browsing with touch events (dragging,
|
||||||
* double-tapping, and tap with two fingers). Create a new navigation
|
* double-tapping, tap with two fingers, and pinch zoom). Create a new
|
||||||
* control with the <OpenLayers.Control.TouchNavigation> control.
|
* control with the <OpenLayers.Control.TouchNavigation> constructor.
|
||||||
*
|
*
|
||||||
* Inherits:
|
* Inherits:
|
||||||
* - <OpenLayers.Control>
|
* - <OpenLayers.Control>
|
||||||
@@ -31,6 +32,18 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
*/
|
*/
|
||||||
dragPanOptions: null,
|
dragPanOptions: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: pinchZoom
|
||||||
|
* {<OpenLayers.Control.PinchZoom>}
|
||||||
|
*/
|
||||||
|
pinchZoom: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIProprety: pinchZoomOptions
|
||||||
|
* {Object} Options passed to the PinchZoom control.
|
||||||
|
*/
|
||||||
|
pinchZoomOptions: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIProperty: documentDrag
|
* APIProperty: documentDrag
|
||||||
* {Boolean} Allow panning of the map by dragging outside map viewport.
|
* {Boolean} Allow panning of the map by dragging outside map viewport.
|
||||||
@@ -70,6 +83,10 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
this.dragPan.destroy();
|
this.dragPan.destroy();
|
||||||
}
|
}
|
||||||
this.dragPan = null;
|
this.dragPan = null;
|
||||||
|
if (this.pinchZoom) {
|
||||||
|
this.pinchZoom.destroy();
|
||||||
|
delete this.pinchZoom;
|
||||||
|
}
|
||||||
OpenLayers.Control.prototype.destroy.apply(this,arguments);
|
OpenLayers.Control.prototype.destroy.apply(this,arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -80,6 +97,7 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
|
if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
|
||||||
this.dragPan.activate();
|
this.dragPan.activate();
|
||||||
this.handlers.click.activate();
|
this.handlers.click.activate();
|
||||||
|
this.pinchZoom.activate();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -92,11 +110,12 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
|
if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
|
||||||
this.dragPan.deactivate();
|
this.dragPan.deactivate();
|
||||||
this.handlers.click.deactivate();
|
this.handlers.click.deactivate();
|
||||||
|
this.pinchZoom.deactivate();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method: draw
|
* Method: draw
|
||||||
*/
|
*/
|
||||||
@@ -119,6 +138,9 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
|||||||
}, this.dragPanOptions)
|
}, this.dragPanOptions)
|
||||||
);
|
);
|
||||||
this.dragPan.draw();
|
this.dragPan.draw();
|
||||||
|
this.pinchZoom = new OpenLayers.Control.PinchZoom(
|
||||||
|
OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -821,10 +821,20 @@ OpenLayers.Events = OpenLayers.Class({
|
|||||||
// noone's listening, bail out
|
// noone's listening, bail out
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// add clientX & clientY to all events - only corresponds to the first touch
|
// add clientX & clientY to all events - corresponds to average x, y
|
||||||
if (evt.touches && evt.touches[0]) {
|
var touches = evt.touches;
|
||||||
evt.clientX = evt.touches[0].clientX;
|
if (touches && touches[0]) {
|
||||||
evt.clientY = evt.touches[0].clientY;
|
var x = 0;
|
||||||
|
var y = 0;
|
||||||
|
var num = touches.length;
|
||||||
|
var touch;
|
||||||
|
for (var i=0; i<num; ++i) {
|
||||||
|
touch = touches[i];
|
||||||
|
x += touch.clientX;
|
||||||
|
y += touch.clientY;
|
||||||
|
}
|
||||||
|
evt.clientX = x / num;
|
||||||
|
evt.clientY = y / num;
|
||||||
}
|
}
|
||||||
if (this.includeXY) {
|
if (this.includeXY) {
|
||||||
evt.xy = this.getMousePosition(evt);
|
evt.xy = this.getMousePosition(evt);
|
||||||
|
|||||||
@@ -120,13 +120,15 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
|||||||
* {Boolean} Let the event propagate.
|
* {Boolean} Let the event propagate.
|
||||||
*/
|
*/
|
||||||
touchmove: function(evt) {
|
touchmove: function(evt) {
|
||||||
|
var propagate = true;
|
||||||
if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
|
if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
|
||||||
this.pinching = true;
|
this.pinching = true;
|
||||||
var current = this.getPinchData(evt);
|
var current = this.getPinchData(evt);
|
||||||
this.callback("move", [evt, current]);
|
this.callback("move", [evt, current]);
|
||||||
this.last = current;
|
this.last = current;
|
||||||
|
propagate = false;
|
||||||
}
|
}
|
||||||
return true;
|
return propagate;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,14 +142,16 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
|||||||
* {Boolean} Let the event propagate.
|
* {Boolean} Let the event propagate.
|
||||||
*/
|
*/
|
||||||
touchend: function(evt) {
|
touchend: function(evt) {
|
||||||
|
var propagate = true;
|
||||||
if (this.started) {
|
if (this.started) {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.pinching = false;
|
this.pinching = false;
|
||||||
this.callback("done", [evt, this.start, this.last]);
|
this.callback("done", [evt, this.start, this.last]);
|
||||||
this.start = null;
|
this.start = null;
|
||||||
this.last = null;
|
this.last = null;
|
||||||
|
propagate = false;
|
||||||
}
|
}
|
||||||
return true;
|
return propagate;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="../OLLoader.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function test_constructor(t) {
|
||||||
|
t.plan(2);
|
||||||
|
var control = new OpenLayers.Control.PinchZoom();
|
||||||
|
t.ok(control instanceof OpenLayers.Control.PinchZoom, "got an instance");
|
||||||
|
t.ok(control.handler instanceof OpenLayers.Handler.Pinch, "control has pinch handler");
|
||||||
|
control.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_destroy(t) {
|
||||||
|
t.plan(1);
|
||||||
|
var control = new OpenLayers.Control.PinchZoom();
|
||||||
|
control.destroy();
|
||||||
|
t.ok(!control.handler, "handler destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_activate(t) {
|
||||||
|
t.plan(3);
|
||||||
|
var control = new OpenLayers.Control.PinchZoom();
|
||||||
|
t.ok(!control.active, "control not activated after construction");
|
||||||
|
|
||||||
|
var map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
controls: [control]
|
||||||
|
});
|
||||||
|
t.ok(control.active, "control activated after being added to the map");
|
||||||
|
|
||||||
|
control.deactivate();
|
||||||
|
t.ok(!control.active, "control deactivated");
|
||||||
|
|
||||||
|
map.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_pinchMove(t) {
|
||||||
|
|
||||||
|
var control = new OpenLayers.Control.PinchZoom();
|
||||||
|
|
||||||
|
var map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
controls: [control]
|
||||||
|
});
|
||||||
|
|
||||||
|
var log = [];
|
||||||
|
control.applyTransform = function(transform) {
|
||||||
|
log.push(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
control.containerOrigin = {
|
||||||
|
x: 0, y: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
control.pinchOrigin = {
|
||||||
|
x: 100, y: 50
|
||||||
|
};
|
||||||
|
|
||||||
|
var cases = [
|
||||||
|
{x: 100, y: 60, scale: 1, transform: "translate(0px, 10px) scale(1)"},
|
||||||
|
{x: 150, y: 60, scale: 1, transform: "translate(50px, 10px) scale(1)"},
|
||||||
|
{x: 150, y: 60, scale: 2, transform: "translate(-50px, -40px) scale(2)"},
|
||||||
|
{x: 50, y: 20, scale: 2.5, transform: "translate(-200px, -105px) scale(2.5)"},
|
||||||
|
{x: 150, y: 60, scale: 2, transform: "translate(-50px, -40px) scale(2)"},
|
||||||
|
{x: 50, y: 20, scale: 0.25, transform: "translate(25px, 8px) scale(0.25)"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var len = cases.length;
|
||||||
|
t.plan(len*2);
|
||||||
|
|
||||||
|
var c;
|
||||||
|
for (var i=0; i<len; ++i) {
|
||||||
|
c = cases[i];
|
||||||
|
control.pinchMove({xy: {x: c.x, y: c.y}}, {scale: c.scale});
|
||||||
|
t.eq(log.length, i+1, i + " called once");
|
||||||
|
t.eq(log[i], c.transform, i + " correct transform");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map" style="width: 256px; height: 256px;"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
<li>Control/PanZoom.html</li>
|
<li>Control/PanZoom.html</li>
|
||||||
<li>Control/PanZoomBar.html</li>
|
<li>Control/PanZoomBar.html</li>
|
||||||
<li>Control/Permalink.html</li>
|
<li>Control/Permalink.html</li>
|
||||||
|
<li>Control/PinchZoom.html</li>
|
||||||
<li>Control/Scale.html</li>
|
<li>Control/Scale.html</li>
|
||||||
<li>Control/ScaleLine.html</li>
|
<li>Control/ScaleLine.html</li>
|
||||||
<li>Control/SelectFeature.html</li>
|
<li>Control/SelectFeature.html</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user