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/DragPan.js",
|
||||
"OpenLayers/Control/Navigation.js",
|
||||
"OpenLayers/Control/PinchZoom.js",
|
||||
"OpenLayers/Control/TouchNavigation.js",
|
||||
"OpenLayers/Control/MouseDefaults.js",
|
||||
"OpenLayers/Control/MousePosition.js",
|
||||
|
||||
192
lib/OpenLayers/Control/PinchZoom.js
Normal file
192
lib/OpenLayers/Control/PinchZoom.js
Normal file
@@ -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/PinchZoom.js
|
||||
* @requires OpenLayers/Handler/Click.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class: OpenLayers.Control.TouchNavigation
|
||||
* The navigation control handles map browsing with touch events (dragging,
|
||||
* double-tapping, and tap with two fingers). Create a new navigation
|
||||
* control with the <OpenLayers.Control.TouchNavigation> control.
|
||||
* double-tapping, tap with two fingers, and pinch zoom). Create a new
|
||||
* control with the <OpenLayers.Control.TouchNavigation> constructor.
|
||||
*
|
||||
* Inherits:
|
||||
* - <OpenLayers.Control>
|
||||
@@ -31,6 +32,18 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
dragPanOptions: null,
|
||||
|
||||
/**
|
||||
* Property: pinchZoom
|
||||
* {<OpenLayers.Control.PinchZoom>}
|
||||
*/
|
||||
pinchZoom: null,
|
||||
|
||||
/**
|
||||
* APIProprety: pinchZoomOptions
|
||||
* {Object} Options passed to the PinchZoom control.
|
||||
*/
|
||||
pinchZoomOptions: null,
|
||||
|
||||
/**
|
||||
* APIProperty: documentDrag
|
||||
* {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 = null;
|
||||
if (this.pinchZoom) {
|
||||
this.pinchZoom.destroy();
|
||||
delete this.pinchZoom;
|
||||
}
|
||||
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)) {
|
||||
this.dragPan.activate();
|
||||
this.handlers.click.activate();
|
||||
this.pinchZoom.activate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -92,6 +110,7 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
||||
if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
|
||||
this.dragPan.deactivate();
|
||||
this.handlers.click.deactivate();
|
||||
this.pinchZoom.deactivate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -119,6 +138,9 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
||||
}, this.dragPanOptions)
|
||||
);
|
||||
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
|
||||
return;
|
||||
}
|
||||
// add clientX & clientY to all events - only corresponds to the first touch
|
||||
if (evt.touches && evt.touches[0]) {
|
||||
evt.clientX = evt.touches[0].clientX;
|
||||
evt.clientY = evt.touches[0].clientY;
|
||||
// add clientX & clientY to all events - corresponds to average x, y
|
||||
var touches = evt.touches;
|
||||
if (touches && touches[0]) {
|
||||
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) {
|
||||
evt.xy = this.getMousePosition(evt);
|
||||
|
||||
@@ -120,13 +120,15 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* {Boolean} Let the event propagate.
|
||||
*/
|
||||
touchmove: function(evt) {
|
||||
var propagate = true;
|
||||
if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
|
||||
this.pinching = true;
|
||||
var current = this.getPinchData(evt);
|
||||
this.callback("move", [evt, 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.
|
||||
*/
|
||||
touchend: function(evt) {
|
||||
var propagate = true;
|
||||
if (this.started) {
|
||||
this.started = false;
|
||||
this.pinching = false;
|
||||
this.callback("done", [evt, this.start, this.last]);
|
||||
this.start = null;
|
||||
this.last = null;
|
||||
propagate = false;
|
||||
}
|
||||
return true;
|
||||
return propagate;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
87
tests/Control/PinchZoom.html
Normal file
87
tests/Control/PinchZoom.html
Normal file
@@ -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/PanZoomBar.html</li>
|
||||
<li>Control/Permalink.html</li>
|
||||
<li>Control/PinchZoom.html</li>
|
||||
<li>Control/Scale.html</li>
|
||||
<li>Control/ScaleLine.html</li>
|
||||
<li>Control/SelectFeature.html</li>
|
||||
|
||||
Reference in New Issue
Block a user