From 7cf84d8bb500af483626eb0296b4b08c0359d9bb Mon Sep 17 00:00:00 2001 From: "Luis A. Leiva" Date: Sat, 11 Jul 2015 18:45:06 +0200 Subject: [PATCH] Improved multitouch support --- jquery.sketchable.js | 241 +++++++++++++++++++++------------------ jquery.sketchable.min.js | 2 +- 2 files changed, 131 insertions(+), 112 deletions(-) diff --git a/jquery.sketchable.js b/jquery.sketchable.js index 606d7e1..3fe6301 100644 --- a/jquery.sketchable.js +++ b/jquery.sketchable.js @@ -13,18 +13,18 @@ * @name $.fn * @memberof $ * @description This just documents the method that is added to jQuery by this plugin. - * See the jQuery library for full details. + * See the jQuery library for full details. */ ;(function($){ // Custom namespace ID. var _ns = "sketchable"; - /** - * jQuery sketchable plugin API. + /** + * jQuery sketchable plugin API. * @namespace methods */ var methods = { - /** - * Initializes the selected jQuery objects. + /** + * Initializes the selected jQuery objects. * @param {Object} opts plugin configuration (see defaults). * @return jQuery * @ignore @@ -36,16 +36,16 @@ var options = $.extend(true, {}, $.fn.sketchable.defaults, opts || {}); return this.each(function() { var elem = $(this), data = elem.data(_ns); - // Check if element is not initialized yet. + // Check if element is not initialized yet. if (!data) { // Attach event listeners. if (options.interactive) { elem.bind("mousedown", mousedownHandler); - elem.bind("mouseup", mouseupHandler); elem.bind("mousemove", mousemoveHandler); - elem.bind("touchstart", touchHandler); - elem.bind("touchend", touchHandler); - elem.bind("touchmove", touchHandler); + elem.bind("mouseup", mouseupHandler); + elem.bind("touchstart", touchdownHandler); + elem.bind("touchmove", touchmoveHandler); + elem.bind("touchend", touchupHandler); // Fix Chrome "bug". this.onselectstart = function(){ return false }; } @@ -56,13 +56,13 @@ // Reconfigure element data. elem.data(_ns, { // All strokes will be stored here. - strokes: [], + strokes: [], // This will store one stroke per touching finger. - coords: {}, + coords: {}, // Date of first coord, used as time origin. - timestamp: new Date().getTime(), + timestamp: (new Date).getTime(), // Save a pointer to the drawing canvas (jSketch instance). - sketch: sketch, + sketch: sketch, // Save also a pointer to the given options. options: options }); @@ -72,7 +72,7 @@ } }); }, - /** + /** * Gets/Sets drawing data strokes sequence. * @param {Array} arr - Multidimensional array of [x,y,time,status] tuples; status = 0 (pen down) or 1 (pen up). * @return Strokes object on get, jQuery on set (with the new data attached) @@ -90,9 +90,9 @@ } else { // getter var data = $(this).data(_ns); return data.strokes; - } + } }, - /** + /** * Allows low-level manipulation of the sketchable canvas. * @param {Function} callback - Callback function, invoked with 2 arguments: elem (jQuery element) and data (jQuery element data). * @return jQuery @@ -106,10 +106,10 @@ return this.each(function() { var elem = $(this), data = elem.data(_ns); callback(elem, data); - }); + }); }, - /** - * Clears canvas (together with strokes data). + /** + * Clears canvas (together with strokes data). * If you need to clear canvas only, just invoke data.sketch.clear() via $(selector).sketchable('handler'). * @see methods.handler * @return jQuery @@ -122,59 +122,59 @@ data.sketch.clear(); data.strokes = []; data.coords = {}; - + if (typeof options.events.clear === 'function') { options.events.clear(elem, data); } }); }, - /** + /** * Reinitializes a sketchable canvas with given opts. * @param {Object} opts - Configuration options. * @return jQuery * @namespace methods.reset - * @example + * @example * $(selector).sketchable('reset'); * $(selector).sketchable('reset', {interactive:false}); - */ + */ reset: function(opts) { return this.each(function(){ var elem = $(this), data = elem.data(_ns), options = data.options; elem.sketchable('destroy').sketchable(opts); - + if (typeof options.events.reset === 'function') { options.events.reset(elem, data); } - }); + }); }, - /** + /** * Destroys sketchable canvas (together with strokes data and events). * @return jQuery * @namespace methods.destroy - * @example $(selector).sketchable('destroy'); + * @example $(selector).sketchable('destroy'); */ destroy: function() { return this.each(function(){ var elem = $(this), data = elem.data(_ns), options = data.options; if (options.interactive) { - elem.unbind("mousedown", mousedownHandler); elem.unbind("mouseup", mouseupHandler); elem.unbind("mousemove", mousemoveHandler); + elem.unbind("mousedown", mousedownHandler); elem.unbind("touchstart", touchHandler); - elem.unbind("touchend", touchHandler); elem.unbind("touchmove", touchHandler); + elem.unbind("touchend", touchHandler); } elem.removeData(_ns); - + if (typeof options.events.destroy === 'function') { options.events.destroy(elem, data); } }); } - + }; - /** + /** * Creates a jQuery.sketchable instance. * This is a jQuery plugin for the jSketch drawing class. * @param {String|Object} method - Method to invoke, or a configuration object. @@ -183,7 +183,7 @@ * @version 1.8 * @date 9 Jul 2014 * @author Luis A. Leiva - * @license MIT license + * @license MIT license * @example * $(selector).sketchable(); * $(selector).sketchable({interactive:false}); @@ -203,8 +203,8 @@ } return this; }; - - /** + + /** * Default configuration. * Note that mouse* callbacks are triggered only if interactive is set to true. * @name defaults @@ -216,15 +216,15 @@ * mouseupMovements: false, * relTimestamps: false, * events: { - * init: function(elem, data){ }, - * clear: function(elem, data){ }, - * destroy: function(elem, data){ }, - * mousedown: function(elem, data, evt){ }, - * mousemove: function(elem, data, evt){ }, - * mouseup: function(elem, data, evt){ }, + * init: function(elem, data){ }, + * clear: function(elem, data){ }, + * destroy: function(elem, data){ }, + * mousedown: function(elem, data, evt){ }, + * mousemove: function(elem, data, evt){ }, + * mouseup: function(elem, data, evt){ }, * }, * graphics: { - * firstPointSize: 3, + * firstPointSize: 3, * lineWidth: 3, * strokeStyle: '#F0F', * fillStyle: '#F0F', @@ -244,12 +244,12 @@ relTimestamps: false, // Event callbacks. events: { - // init: function(elem, data){ }, - // clear: function(elem, data){ }, - // destroy: function(elem, data){ }, - // mousedown: function(elem, data, evt){ }, - // mousemove: function(elem, data, evt){ }, - // mouseup: function(elem, data, evt){ }, + // init: function(elem, data){ }, + // clear: function(elem, data){ }, + // destroy: function(elem, data){ }, + // mousedown: function(elem, data, evt){ }, + // mousemove: function(elem, data, evt){ }, + // mouseup: function(elem, data, evt){ }, }, graphics: { firstPointSize: 3, @@ -282,7 +282,7 @@ if (!data.coords[idx]) { data.coords[idx] = []; } - + var time = (new Date).getTime(); if (data.options.relTimestamps) { // The first timestamp is relative to initialization time; @@ -290,43 +290,77 @@ if (data.strokes.length === 0 && data.coords[idx].length === 0) data.timestamp = time; time -= data.timestamp; } - + data.coords[idx].push([ pt.x, pt.y, time, +data.sketch.isDrawing ]); }; /** * @private */ - function mousemoveHandler(e, idx) { - if (typeof idx === 'undefined') idx = 0; - - var elem = $(e.target), data = elem.data(_ns), options = data.options; - //if (!options.mouseupMovements && !data.sketch.isDrawing) return; - // This would grab all penup strokes AFTER drawing something on the canvas for the first time. - if ( (!options.mouseupMovements || data.strokes.length === 0) && !data.sketch.isDrawing ) return; - - var p = getMousePos(e); - if (data.sketch.isDrawing) { - var last = data.coords[idx][ data.coords[idx].length - 1 ]; - data.sketch.beginPath().line(last[0], last[1], p.x, p.y).stroke().closePath(); + function mousedownHandler(e) { + if (e.originalEvent.touches) return false; + downHandler(e); + }; + + /** + * @private + */ + function mousemoveHandler(e) { + if (e.originalEvent.touches) return false; + moveHandler(e); + }; + + /** + * @private + */ + function mouseupHandler(e) { + if (e.originalEvent.touches) return false; + upHandler(e); + }; + + /** + * @private + */ + function touchdownHandler(e) { + var touches = e.originalEvent.changedTouches; + for (var i = 0; i < touches.length; i++) { + var touch = touches[i]; + downHandler(touch); } - saveMousePos(idx, data, p); - - if (typeof options.events.mousemove === 'function') { - options.events.mousemove(elem, data, e); + e.preventDefault(); + }; + + /** + * @private + */ + function touchmoveHandler(e) { + var touches = e.originalEvent.changedTouches; + for (var i = 0; i < touches.length; i++) { + var touch = touches[i]; + moveHandler(touch); + } + e.preventDefault(); + }; + + /** + * @private + */ + function touchupHandler(e) { + var touches = e.originalEvent.changedTouches; + for (var i = 0; i < touches.length; i++) { + var touch = touches[i]; + upHandler(touch); } }; /** * @private */ - function mousedownHandler(e, idx) { - if (typeof idx === 'undefined') idx = 0; - + function downHandler(e) { + var idx = e.identifier || 0; var elem = $(e.target), data = elem.data(_ns), options = data.options; data.sketch.isDrawing = true; var p = getMousePos(e); - // Mark visually 1st point of stroke. if (options.graphics.firstPointSize > 0) { data.sketch.fillCircle(p.x, p.y, options.graphics.firstPointSize); @@ -341,7 +375,7 @@ data.coords[idx] = []; } saveMousePos(idx, data, p); - + if (typeof options.events.mousedown === 'function') { options.events.mousedown(elem, data, e); } @@ -350,53 +384,38 @@ /** * @private */ - function mouseupHandler(e, idx) { - if (typeof idx === 'undefined') idx = 0; - + function moveHandler(e) { + var idx = e.identifier || 0; var elem = $(e.target), data = elem.data(_ns), options = data.options; - data.sketch.isDrawing = false; - data.strokes.push(data.coords[idx]); - data.coords[idx] = []; - - if (typeof options.events.mouseup === 'function') { - options.events.mouseup(elem, data, e); + //if (!options.mouseupMovements && !data.sketch.isDrawing) return; + // This would grab all penup strokes AFTER drawing something on the canvas for the first time. + if ( (!options.mouseupMovements || data.strokes.length === 0) && !data.sketch.isDrawing ) return; + + var p = getMousePos(e); + if (data.sketch.isDrawing) { + var last = data.coords[idx][ data.coords[idx].length - 1 ]; + data.sketch.beginPath().line(last[0], last[1], p.x, p.y).stroke().closePath(); + } + saveMousePos(idx, data, p); + + if (typeof options.events.mousemove === 'function') { + options.events.mousemove(elem, data, e); } }; /** * @private */ - function touchHandler(e) { - e.preventDefault(); - var elem = $(e.target); - var touches = e.originalEvent.changedTouches; - // Remove (emulated) mouse events on mobile devices. - switch (e.type) { - case "touchstart": - elem.unbind(e.type, mousedownHandler); - for (var i = 0, t = touches[i]; i < touches.length; i++) { - for (var o in e) t[o] = e[o]; - mousedownHandler(t, t.identifier); - } - break; - case "touchmove": - elem.unbind(e.type, mousemoveHandler); - for (var i = 0, t = touches[i]; i < touches.length; i++) { - for (var o in e) t[o] = e[o]; - mousemoveHandler(t, t.identifier); - } - break; - case "touchend": - elem.unbind(e.type, mouseupHandler); - for (var i = 0, t = touches[i]; i < touches.length; i++) { - for (var o in e) t[o] = e[o]; - mouseupHandler(t, t.identifier); - } - break; - default: - return; + function upHandler(e) { + var idx = e.identifier || 0; + var elem = $(e.target), data = elem.data(_ns), options = data.options; + data.sketch.isDrawing = false; + data.strokes.push(data.coords[idx]); + data.coords[idx] = []; + + if (typeof options.events.mouseup === 'function') { + options.events.mouseup(elem, data, e); } - return false; }; })(jQuery); diff --git a/jquery.sketchable.min.js b/jquery.sketchable.min.js index f767230..80eb936 100644 --- a/jquery.sketchable.min.js +++ b/jquery.sketchable.min.js @@ -2,4 +2,4 @@ * jQuery sketchable | v1.8 | Luis A. Leiva | MIT license * A jQuery plugin for the jSketch drawing library. */ -(function(g){var e="sketchable";var b={init:function(k){var j=g.extend(true,{},g.fn.sketchable.defaults,k||{});return this.each(function(){var l=g(this),m=l.data(e);if(!m){if(j.interactive){l.bind("mousedown",h);l.bind("mouseup",i);l.bind("mousemove",f);l.bind("touchstart",d);l.bind("touchend",d);l.bind("touchmove",d);this.onselectstart=function(){return false}}}var n=new jSketch(this,j.graphics);n.isDrawing=false;l.data(e,{strokes:[],coords:{},timestamp:new Date().getTime(),sketch:n,options:j});if(typeof j.events.init==="function"){j.events.init(l,l.data(e))}})},strokes:function(j){if(j){return this.each(function(){var l=g(this),m=l.data(e);m.strokes=j})}else{var k=g(this).data(e);return k.strokes}},handler:function(j){return this.each(function(){var k=g(this),l=k.data(e);j(k,l)})},clear:function(){return this.each(function(){var k=g(this),l=k.data(e),j=l.options;l.sketch.clear();l.strokes=[];l.coords={};if(typeof j.events.clear==="function"){j.events.clear(k,l)}})},reset:function(j){return this.each(function(){var l=g(this),m=l.data(e),k=m.options;l.sketchable("destroy").sketchable(j);if(typeof k.events.reset==="function"){k.events.reset(l,m)}})},destroy:function(){return this.each(function(){var k=g(this),l=k.data(e),j=l.options;if(j.interactive){k.unbind("mousedown",h);k.unbind("mouseup",i);k.unbind("mousemove",f);k.unbind("touchstart",d);k.unbind("touchend",d);k.unbind("touchmove",d)}k.removeData(e);if(typeof j.events.destroy==="function"){j.events.destroy(k,l)}})}};g.fn.sketchable=function(j){if("methods functions hooks".split(" ").indexOf(j)>-1){return b}else{if(b[j]){return b[j].apply(this,Array.prototype.slice.call(arguments,1))}else{if(typeof j==="object"||!j){return b.init.apply(this,arguments)}else{g.error("Method "+j+' does not exist. See jQuery.sketchable("methods").')}}}return this};g.fn.sketchable.defaults={interactive:true,mouseupMovements:false,relTimestamps:false,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}};function c(k){var j=g(k.target),l=j.offset();return{x:Math.round(k.pageX-l.left),y:Math.round(k.pageY-l.top)}}function a(j,k,m){if(!k.coords[j]){k.coords[j]=[]}var l=(new Date).getTime();if(k.options.relTimestamps){if(k.strokes.length===0&&k.coords[j].length===0){k.timestamp=l}l-=k.timestamp}k.coords[j].push([m.x,m.y,l,+k.sketch.isDrawing])}function f(q,j){if(typeof j==="undefined"){j=0}var m=g(q.target),n=m.data(e),k=n.options;if((!k.mouseupMovements||n.strokes.length===0)&&!n.sketch.isDrawing){return}var o=c(q);if(n.sketch.isDrawing){var l=n.coords[j][n.coords[j].length-1];n.sketch.beginPath().line(l[0],l[1],o.x,o.y).stroke().closePath()}a(j,n,o);if(typeof k.events.mousemove==="function"){k.events.mousemove(m,n,q)}}function h(o,j){if(typeof j==="undefined"){j=0}var l=g(o.target),m=l.data(e),k=m.options;m.sketch.isDrawing=true;var n=c(o);if(k.graphics.firstPointSize>0){m.sketch.fillCircle(n.x,n.y,k.graphics.firstPointSize)}if(!m.coords[j]){m.coords[j]=[]}if(m.coords[j].length>0){m.strokes.push(m.coords[j]);m.coords[j]=[]}a(j,m,n);if(typeof k.events.mousedown==="function"){k.events.mousedown(l,m,o)}}function i(n,j){if(typeof j==="undefined"){j=0}var l=g(n.target),m=l.data(e),k=m.options;m.sketch.isDrawing=false;m.strokes.push(m.coords[j]);m.coords[j]=[];if(typeof k.events.mouseup==="function"){k.events.mouseup(l,m,n)}}function d(n){n.preventDefault();var l=g(n.target);var m=n.originalEvent.changedTouches;switch(n.type){case"touchstart":l.unbind(n.type,h);for(var k=0,j=m[k];k-1){return e}else{if(e[o]){return e[o].apply(this,Array.prototype.slice.call(arguments,1))}else{if(typeof o==="object"||!o){return e.init.apply(this,arguments)}else{j.error("Method "+o+' does not exist. See jQuery.sketchable("methods").')}}}return this};j.fn.sketchable.defaults={interactive:true,mouseupMovements:false,relTimestamps:false,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}};function f(p){var o=j(p.target),q=o.offset();return{x:Math.round(p.pageX-q.left),y:Math.round(p.pageY-q.top)}}function c(o,p,r){if(!p.coords[o]){p.coords[o]=[]}var q=(new Date).getTime();if(p.options.relTimestamps){if(p.strokes.length===0&&p.coords[o].length===0){p.timestamp=q}q-=p.timestamp}p.coords[o].push([r.x,r.y,q,+p.sketch.isDrawing])}function l(o){if(o.originalEvent.touches){return false}m(o)}function h(o){if(o.originalEvent.touches){return false}k(o)}function n(o){if(o.originalEvent.touches){return false}a(o)}function i(q){var p=q.originalEvent.changedTouches;for(var o=0;o0){s.sketch.fillCircle(t.x,t.y,q.graphics.firstPointSize)}if(!s.coords[o]){s.coords[o]=[]}if(s.coords[o].length>0){s.strokes.push(s.coords[o]);s.coords[o]=[]}c(o,s,t);if(typeof q.events.mousedown==="function"){q.events.mousedown(r,s,u)}}function k(v){var o=v.identifier||0;var s=j(v.target),t=s.data(g),q=t.options;if((!q.mouseupMovements||t.strokes.length===0)&&!t.sketch.isDrawing){return}var u=f(v);if(t.sketch.isDrawing){var r=t.coords[o][t.coords[o].length-1];t.sketch.beginPath().line(r[0],r[1],u.x,u.y).stroke().closePath()}c(o,t,u);if(typeof q.events.mousemove==="function"){q.events.mousemove(s,t,v)}}function a(s){var o=s.identifier||0;var q=j(s.target),r=q.data(g),p=r.options;r.sketch.isDrawing=false;r.strokes.push(r.coords[o]);r.coords[o]=[];if(typeof p.events.mouseup==="function"){p.events.mouseup(q,r,s)}}})(jQuery); \ No newline at end of file