+ OpenLayers Kinetic Dragging inspired from Tile5, and
+ kineticscrolling for Google Maps API V3.
+
+
+
+
+ As shown in this example Kinetic Dragging is enabled by setting
+ enableKinetic to true in the config object provided to the
+ Control.DragPan constructor. When using
+ Control.Navigation or Control.TouchNavigation
+ providing options to the underlying Control.DragPan
+ instance is done through the dragPanOptions config
+ property.
+
+
+
+
+
+
diff --git a/examples/mobile.js b/examples/mobile.js
index e8826b780c..957b69b278 100644
--- a/examples/mobile.js
+++ b/examples/mobile.js
@@ -19,7 +19,7 @@ function init() {
-20037508.34, -20037508.34, 20037508.34, 20037508.34
),
controls: [
- new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.Navigation({dragPanOptions: {enableKinetic: true}}),
new OpenLayers.Control.Attribution(),
new OpenLayers.Control.DrawFeature(
vector, OpenLayers.Handler.Point, {id: "point-control"}
diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js
index 802b92e6e1..b73d7a6f6e 100644
--- a/lib/OpenLayers.js
+++ b/lib/OpenLayers.js
@@ -105,6 +105,7 @@
"OpenLayers/BaseTypes/Size.js",
"OpenLayers/Console.js",
"OpenLayers/Tween.js",
+ "OpenLayers/Kinetic.js",
"Rico/Corner.js",
"Rico/Color.js",
"OpenLayers/Ajax.js",
diff --git a/lib/OpenLayers/Control/DragPan.js b/lib/OpenLayers/Control/DragPan.js
index eaebc8fc53..61001f2bc4 100644
--- a/lib/OpenLayers/Control/DragPan.js
+++ b/lib/OpenLayers/Control/DragPan.js
@@ -43,16 +43,48 @@ OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
* mouse cursor leaves the map viewport. Default is false.
*/
documentDrag: false,
-
+
+ /**
+ * Property: kinetic
+ * {OpenLayers.Kinetic} The OpenLayers.Kinetic object.
+ */
+ kinetic: null,
+
+ /**
+ * APIProperty: enableKinetic
+ * {Boolean} Set this option to enable "kinetic dragging". Can be
+ * set to true or to an object. If set to an object this
+ * object will be passed to the {}
+ * constructor. Defaults to false.
+ */
+ enableKinetic: false,
+
+ /**
+ * APIProperty: kineticInterval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * scrolling". Applies only if enableKinetic is set. Defaults
+ * to 10 milliseconds.
+ */
+ kineticInterval: 10,
+
+
/**
* Method: draw
* Creates a Drag handler, using and
* as callbacks.
*/
draw: function() {
+ if(this.enableKinetic) {
+ var config = {interval: this.kineticInterval};
+ if(typeof this.enableKinetic === "object") {
+ config = OpenLayers.Util.extend(config, this.enableKinetic);
+ }
+ this.kinetic = new OpenLayers.Kinetic(config);
+ }
this.handler = new OpenLayers.Handler.Drag(this, {
"move": this.panMap,
- "done": this.panMapDone
+ "done": this.panMapDone,
+ "down": this.panMapStart
}, {
interval: this.interval,
documentDrag: this.documentDrag
@@ -60,6 +92,15 @@ OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
);
},
+ /**
+ * Method: panMapStart
+ */
+ panMapStart: function() {
+ if(this.kinetic) {
+ this.kinetic.begin();
+ }
+ },
+
/**
* Method: panMap
*
@@ -67,11 +108,14 @@ OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
* xy - {} Pixel of the mouse position
*/
panMap: function(xy) {
+ if(this.kinetic) {
+ this.kinetic.update(xy);
+ }
this.panned = true;
this.map.pan(
this.handler.last.x - xy.x,
this.handler.last.y - xy.y,
- {dragging: this.handler.dragging, animate: false}
+ {dragging: true, animate: false}
);
},
@@ -85,7 +129,21 @@ OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
*/
panMapDone: function(xy) {
if(this.panned) {
- this.panMap(xy);
+ var res = null;
+ if (this.kinetic) {
+ res = this.kinetic.end(xy);
+ }
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: !!res, animate: false}
+ );
+ if (res) {
+ var self = this;
+ this.kinetic.move(res, function(x, y, end) {
+ self.map.pan(x, y, {dragging: !end, animate: false});
+ });
+ }
this.panned = false;
}
},
diff --git a/lib/OpenLayers/Kinetic.js b/lib/OpenLayers/Kinetic.js
new file mode 100644
index 0000000000..c780eb6219
--- /dev/null
+++ b/lib/OpenLayers/Kinetic.js
@@ -0,0 +1,169 @@
+OpenLayers.Kinetic = OpenLayers.Class({
+
+ /**
+ * Property: threshold
+ * In most cases changing the threshold isn't needed.
+ * In px/ms, default to 0.
+ */
+ threshold: 0,
+
+ /**
+ * Property: interval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * dragging". Defaults to 10 milliseconds.
+ */
+ interval: 10,
+
+ /**
+ * Property: deceleration
+ * {Float} the deseleration in px/ms², default to 0.0035.
+ */
+ deceleration: 0.0035,
+
+ /**
+ * Property: nbPoints
+ * {Integer} the number of points we use to calculate the kinetic
+ * initial values.
+ */
+ nbPoints: 100,
+
+ /**
+ * Property: delay
+ * {Float} time to consider to calculate the kinetic initial values.
+ * In ms, default to 200.
+ */
+ delay: 200,
+
+ /**
+ * Property: points
+ * List of points use to calculate the kinetic initial values.
+ */
+ points: undefined,
+
+ /**
+ * Property: timerId
+ * ID of the timer.
+ */
+ timerId: undefined,
+
+ /**
+ * Constructor: OpenLayers.Kinetic
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: begin
+ *
+ * Begins the dragging.
+ */
+ begin: function() {
+ clearInterval(this.timerId);
+ this.timerId = undefined;
+ this.points = [];
+ },
+
+ /**
+ * Method: update
+ *
+ * Updates during the dragging.
+ */
+ update: function(xy) {
+ this.points.unshift({xy: xy, tick: new Date().getTime()});
+ if (this.points.length > this.nbPoints) {
+ this.points.pop();
+ }
+ },
+
+ /**
+ * Method: end
+ *
+ * Ends the dragging, start the kinetic.
+ */
+ end: function(xy) {
+ var last, now = new Date().getTime();
+ for (var i = 0, l = this.points.length, point; i < l; i++) {
+ point = this.points[i];
+ if (now - point.tick > this.delay) {
+ break;
+ }
+ last = point;
+ }
+ if (!last) {
+ return;
+ }
+ var time = new Date().getTime() - last.tick;
+ var dist = Math.sqrt(Math.pow(xy.x - last.xy.x, 2) +
+ Math.pow(xy.y - last.xy.y, 2));
+ var speed = dist / time;
+ if (speed == 0 || speed < this.threshold) {
+ return;
+ }
+ var theta = Math.asin((xy.y - last.xy.y) / dist);
+ if (last.xy.x <= xy.x) {
+ theta = Math.PI - theta;
+ }
+ return {speed: speed, theta: theta};
+ },
+
+ /**
+ * Method: move
+ *
+ * Launch the kinetic move pan.
+ *
+ * Parameters:
+ * info - {Object}
+ * callback - arguments x, y (values to pan), end (is the last point)
+ */
+ move: function(info, callback) {
+ var v0 = info.speed;
+ var fx = Math.cos(info.theta);
+ var fy = -Math.sin(info.theta);
+
+ var time = 0;
+ var initialTime = new Date().getTime();
+
+ var lastX = 0;
+ var lastY = 0;
+
+ var timerCallback = function() {
+ if (this.timerId == null) {
+ return;
+ }
+
+ time += this.interval;
+ var realTime = new Date().getTime() - initialTime;
+ var t = (time + realTime) / 2.0;
+
+ var p = (-this.deceleration * Math.pow(t, 2)) / 2.0 + v0 * t;
+ var x = p * fx;
+ var y = p * fy;
+
+ var args = {};
+ args.end = false;
+ var v = -this.deceleration * t + v0;
+
+ if (v <= 0) {
+ clearInterval(this.timerId);
+ this.timerId = null;
+ args.end = true;
+ }
+
+ args.x = x - lastX;
+ args.y = y - lastY;
+ lastX = x;
+ lastY = y;
+ callback(args.x, args.y, args.end);
+ };
+
+ this.timerId = window.setInterval(
+ OpenLayers.Function.bind(timerCallback, this),
+ this.interval);
+ },
+
+ CLASS_NAME: "OpenLayers.Kinetic"
+});
diff --git a/tests/Kinetic.html b/tests/Kinetic.html
new file mode 100644
index 0000000000..6ef01ceea3
--- /dev/null
+++ b/tests/Kinetic.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+