diff --git a/examples/kinetic.html b/examples/kinetic.html new file mode 100644 index 0000000000..39e1c3cdef --- /dev/null +++ b/examples/kinetic.html @@ -0,0 +1,81 @@ + + + OpenLayers Kinetic Dragging Example + + + + + + + +

Kinetic Dragging Example

+ +
+ kinetic, dragging +
+ +

+ Demonstrates Kinetic Dragging. +

+ +
+ +
+ +

+ 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 @@ + + + + + + +
+
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index ce18c36578..b894091841 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -208,5 +208,6 @@
  • Tile/Image/IFrame.html
  • Tile/WFS.html
  • Tween.html
  • +
  • Kinetic.html
  • Util.html