mirror of https://github.com/luileito/jsketch.git
Version 2.0 🎉
This commit is contained in:
parent
02c59f02ef
commit
bc4f2a1c74
|
|
@ -1,3 +1,6 @@
|
||||||
node_modules
|
node_modules
|
||||||
.*
|
.*
|
||||||
*~
|
*~
|
||||||
|
local
|
||||||
|
*.log
|
||||||
|
*.bak
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2012--2017 Luis A. Leiva
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
39
README.md
39
README.md
|
|
@ -1,9 +1,36 @@
|
||||||
jsketch
|
# jSketch
|
||||||
=======
|
|
||||||
|
|
||||||
A lightweight JavaScript library for drawing facilities on HTML5 canvas,
|
A lightweight JavaScript library for drawing facilities on an HTML5 canvas.
|
||||||
conveniently wrapped as a jQuery plugin.
|
|
||||||
|
|
||||||
Demos and docs available at http://personales.upv.es/luileito/jsketch/
|
Conveniently wrapped in a `Sketchable` class.
|
||||||
|
Available also as a jQuery plugin.
|
||||||
|
|
||||||
Documented with JSDoc: https://github.com/jsdoc3/jsdoc
|
[Demos and documentation](https://luis.leiva.name/jsketch/)
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## TL;DR:
|
||||||
|
|
||||||
|
Add `<script src="dist/sketchable.full.min.js"></script>` to your page and just do:
|
||||||
|
```js
|
||||||
|
var sketcher = new Sketchable('canvas');
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `<script src="dist/jquery.sketchable.full.min.js"></script>` to your page and just do:
|
||||||
|
```js
|
||||||
|
var $sketcher = $('canvas').sketchable();
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
|
**Want to know more?**
|
||||||
|
Go to [demos and documentation](https://luis.leiva.name/jsketch/).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This libray is released with the [MIT license](LICENSE).
|
||||||
|
The only requirement is that you keep my copyright notice intact when you repurpose, redistribute, or reuse this code.
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
!function(a){function b(b,g){function h(a){if(i&&i.events&&"function"==typeof i.events[a]){var c=i.events[a];i.events[a]=function(){var d=Array.prototype.slice.call(arguments,0);c.apply(b,d),j[a].apply(b,d)}}else i.events[a]=j[a]}var i=a.extend(!0,{},e.defaults,g);if(!i.interactive)return g;var j={init:function(a,b){b.memento=new c(a),b.memento.save(),b.memento.init()},clear:function(a,b){b.memento.reset(),b.memento.save()},mouseup:function(a,b,c){b.memento.save()},destroy:function(a,b){b.memento.destroy()}};if(!e.isMementoReady){for(var k="init mouseup clear destroy".split(" "),l=0;l<k.length;l++)h(k[l]);e.isMementoReady=!0}return a.extend(f,{undo:function(){var b=a(this),c=b.data(d);c.memento.undo()},redo:function(){var b=a(this),c=b.data(d);c.memento.redo()},save:function(){var b=a(this),c=b.data(d);c.memento.save()}}),i}var c=function(b){function c(){if(h>0){h--;var a=new Image;a.src=g[h].image,a.onload=function(){e(this)}}}function d(){if(h<g.length-1){h++;var a=new Image;a.src=g[h].image,a.onload=function(){e(this)}}}function e(a){b.sketchable("handler",function(b,c){c.sketch.clear(),c.sketch.graphics.drawImage(a,0,0)})}function f(a){if(a.ctrlKey)switch(a.which){case 26:a.shiftKey?i.redo():i.undo();break;case 25:i.redo()}}var g=[],h=-1,i=this;this.undo=function(){c(),b.sketchable("handler",function(a,b){g[h]&&(b.strokes=g[h].strokes.slice())})},this.redo=function(){d(),b.sketchable("handler",function(a,b){g[h]&&(b.strokes=g[h].strokes.slice())})},this.reset=function(){g=[],h=-1},this.save=function(){h++,h<g.length&&(g.length=h),b.sketchable("handler",function(a,b){g.push({image:a[0].toDataURL(),strokes:b.strokes.slice()})})},this.init=function(){a(document).off("keypress",f),a(document).on("keypress",f)},this.destroy=function(){a(document).off("keypress",f),this.reset()}},d="sketchable",e=a.fn.sketchable,f=e("methods"),g=f.init;f.init=function(c){return this.each(function(){var d=a(this),e=b(d,c);g.call(d,e)})}}(jQuery);
|
!function(a){function b(b){function c(){if(h>0){h--;var a=new Image;a.src=g[h].image,a.onload=function(){e(this)}}}function d(){if(h<g.length-1){h++;var a=new Image;a.src=g[h].image,a.onload=function(){e(this)}}}function e(a){b.sketchable("handler",function(b,c){c.sketch.clear(),c.sketch.graphics.drawImage(a,0,0)})}function f(a){if(a.ctrlKey)switch(a.which){case 26:a.shiftKey?i.redo():i.undo();break;case 25:i.redo()}}var g=[],h=-1,i=this;this.undo=function(){return c(),b.sketchable("handler",function(a,b){g[h]&&(b.strokes=g[h].strokes.slice())}),this},this.redo=function(){return d(),b.sketchable("handler",function(a,b){g[h]&&(b.strokes=g[h].strokes.slice())}),this},this.reset=function(){return g=[],h=-1,this},this.save=function(){return h++,h<g.length&&(g.length=h),b.sketchable("handler",function(a,b){g.push({image:a[0].toDataURL(),strokes:b.strokes.slice()})}),this},this.init=function(){return a(document).off("keypress",f),a(document).on("keypress",f),this},this.destroy=function(){return a(document).off("keypress",f),this.reset()}}var c="sketchable";a.fn.sketchable.plugins.memento=function(d){function e(a){if(!f.options.$$bound)if(f.options.$$bound=!0,f.options.events&&"function"==typeof f.options.events[a]){var b=f.options.events[a];f.options.events[a]=function(){b.apply(d,arguments),g[a].apply(d,arguments)}}else f.options.events[a]=g[a]}for(var f=d.sketchable("config"),g={clear:function(a,b){b.memento.reset()},mouseup:function(a,b,c){b.memento.save()},destroy:function(a,b){b.memento.destroy()}},h="mouseup clear destroy".split(" "),i=0;i<h.length;i++)e(h[i]);a.extend(a.fn.sketchable.api,{undo:function(){var b=a(this),d=b.data(c);d.memento.undo()},redo:function(){var b=a(this),d=b.data(c);d.memento.redo()},save:function(){var b=a(this),d=b.data(c);d.memento.save()}}),f.memento=new b(d),f.memento.init().save()}}(jQuery);
|
||||||
|
|
@ -1 +1 @@
|
||||||
!function(a){function b(a,b){b||(b=a.data(o).options),b.cssCursors&&(a[0].style.cursor=b.interactive?"pointer":"not-allowed")}function c(b){var c=a(b.target),d=c.offset();return{x:Math.round(b.pageX-d.left),y:Math.round(b.pageY-d.top)}}function d(a,b,c){b.coords[a]||(b.coords[a]=[]);var d=(new Date).getTime();b.options.relTimestamps&&(0===b.strokes.length&&0===b.coords[a].length&&(b.timestamp=d),d-=b.timestamp),b.coords[a].push([c.x,c.y,d,+b.sketch.isDrawing])}function e(a){return a.originalEvent.touches?!1:void l(a)}function f(a){return a.originalEvent.touches?!1:void m(a)}function g(a){return a.originalEvent.touches?!1:void n(a)}function h(b,c){var d=a(b.target),e=d.data(o),f=e.options,g=b.originalEvent.changedTouches;if(f.multitouch)for(var h=0;h<g.length;h++){var i=g[h];i.type=b.type,c(i)}else{var i=g[0];i.type=b.type,c(i)}}function i(a){h(a,l),a.preventDefault()}function j(a){h(a,m),a.preventDefault()}function k(a){h(a,n),a.preventDefault()}function l(b){if(3===b.which)return!1;var e=b.identifier||0,f=a(b.target),g=f.data(o),h=g.options;if(h.interactive){g.sketch.isDrawing=!0;var i=c(b);h.graphics.firstPointSize>0&&g.sketch.fillCircle(i.x,i.y,h.graphics.firstPointSize),g.coords[e]||(g.coords[e]=[]),g.coords[e].length>0&&(g.strokes.push(g.coords[e]),g.coords[e]=[]),d(e,g,i),"function"==typeof h.events.mousedown&&h.events.mousedown(f,g,b)}}function m(b){var e=b.identifier||0,f=a(b.target),g=f.data(o),h=g.options;if(h.interactive&&(h.mouseupMovements&&0!==g.strokes.length||g.sketch.isDrawing)){var i=c(b);if(g.sketch.isDrawing){var j=g.coords[e][g.coords[e].length-1];g.sketch.beginPath().line(j[0],j[1],i.x,i.y).stroke().closePath()}d(e,g,i),"function"==typeof h.events.mousemove&&h.events.mousemove(f,g,b)}}function n(b){var c=b.identifier||0,d=a(b.target),e=d.data(o),f=e.options;f.interactive&&(e.sketch.isDrawing=!1,e.strokes.push(e.coords[c]),e.coords[c]=[],"function"==typeof f.events.mouseup&&f.events.mouseup(d,e,b))}var o="sketchable",p={init:function(c){var d=a.extend(!0,{},a.fn.sketchable.defaults,c||{});return this.each(function(){var c=a(this),h=c.data(o);h||(d.interactive&&(c.bind("mousedown",e),c.bind("mousemove",f),c.bind("mouseup",g),c.bind("touchstart",i),c.bind("touchmove",j),c.bind("touchend",k),this.onselectstart=function(){return!1}),b(c,d));var l=new jSketch(this,d.graphics);c.data(o,{strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:l,options:d}),"function"==typeof d.events.init&&d.events.init(c,c.data(o))})},config:function(c){return this.each(function(){var d=a(this),e=d.data(o);e.options=a.extend(!0,{},a.fn.sketchable.defaults,e.options,c||{}),b(d)})},strokes:function(b){if(b)return this.each(function(){var c=a(this),d=c.data(o);d.strokes=b});var c=a(this).data(o);return c.strokes},handler:function(b){return this.each(function(){var c=a(this),d=c.data(o);b(c,d)})},clear:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;c.sketch&&(c.sketch.clear(),c.strokes=[],c.coords={}),d&&"function"==typeof d.events.clear&&d.events.clear(b,c)})},reset:function(b){return this.each(function(){var c=a(this),d=c.data(o)||{},e=d.options;c.sketchable("destroy").sketchable(b),e&&"function"==typeof e.events.reset&&e.events.reset(c,d)})},destroy:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;d.interactive&&(b.unbind("mouseup",g),b.unbind("mousemove",f),b.unbind("mousedown",e),b.unbind("touchstart",i),b.unbind("touchmove",j),b.unbind("touchend",k)),b.removeData(o),d&&"function"==typeof d.events.destroy&&d.events.destroy(b,c)})}};a.fn.sketchable=function(b){return"methods functions hooks".split(" ").indexOf(b)>-1?p:p[b]?p[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?(a.error("Method "+b+' does not exist. See jQuery.sketchable("methods").'),this):p.init.apply(this,arguments)},a.fn.sketchable.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}}}(jQuery);
|
!function(a){function b(a,b){b||(b=a.data(o).options),b.cssCursors&&(a[0].style.cursor=b.interactive?"pointer":"not-allowed")}function c(b){var c=a(b.target),d=c.offset();return{x:Math.round(b.pageX-d.left),y:Math.round(b.pageY-d.top)}}function d(a,b,c){b.coords[a]||(b.coords[a]=[]);var d=(new Date).getTime();b.options.relTimestamps&&(0===b.strokes.length&&0===b.coords[a].length&&(b.timestamp=d),d-=b.timestamp),b.coords[a].push([c.x,c.y,d,+b.sketch.isDrawing])}function e(a){return a.originalEvent.touches?!1:void l(a)}function f(a){return a.originalEvent.touches?!1:void m(a)}function g(a){return a.originalEvent.touches?!1:void n(a)}function h(b,c){var d=a(b.target),e=d.data(o),f=e.options,g=b.originalEvent.touches;if(f.multitouch)for(var h=0;h<g.length;h++){var i=g[h];i.type=b.type,c(i)}else{var i=g[0];i.type=b.type,c(i)}}function i(a){h(a,l),a.preventDefault()}function j(a){h(a,m),a.preventDefault()}function k(a){h(a,n),a.preventDefault()}function l(b){if(3===b.which)return!1;var e=b.identifier||0,f=a(b.target),g=f.data(o),h=g.options;if(h.interactive){g.sketch.isDrawing=!0;var i=c(b);h.graphics.firstPointSize>0&&g.sketch.beginFill(h.graphics.fillStyle).fillCircle(i.x,i.y,h.graphics.firstPointSize).endFill(),g.coords[e]||(g.coords[e]=[]),g.coords[e].length>0&&(g.strokes.push(g.coords[e]),g.coords[e]=[]),d(e,g,i),"function"==typeof h.events.mousedown&&h.events.mousedown(f,g,b)}}function m(b){var e=b.identifier||0,f=a(b.target),g=f.data(o),h=g.options;if(h.interactive&&(h.mouseupMovements&&0!==g.strokes.length||g.sketch.isDrawing)){var i=c(b);if(g.sketch.isDrawing){var j=g.coords[e][g.coords[e].length-1];g.sketch.beginPath().line(j[0],j[1],i.x,i.y).stroke().closePath()}d(e,g,i),"function"==typeof h.events.mousemove&&h.events.mousemove(f,g,b)}}function n(b){var c=b.identifier||0,d=a(b.target),e=d.data(o),f=e.options;f.interactive&&(e.sketch.isDrawing=!1,e.strokes.push(e.coords[c]),e.coords[c]=[],"function"==typeof f.events.mouseup&&f.events.mouseup(d,e,b))}var o="sketchable",p={init:function(c){var d=a.extend(!0,{},a.fn.sketchable.defaults,c||{});return this.each(function(){var c=a(this),h=c.data(o);h||(c.bind("mousedown",e),c.bind("mousemove",f),c.bind("mouseup",g),c.bind("touchstart",i),c.bind("touchmove",j),c.bind("touchend",k),this.onselectstart=function(){return!1},b(c,d));var l=new jSketch(this,d.graphics);c.data(o,{strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:l,options:d}),d.events&&"function"==typeof d.events.init&&d.events.init(c,c.data(o));for(var m in a.fn.sketchable.plugins)a.fn.sketchable.plugins[m](c)})},config:function(c){return c?this.each(function(){var d=a(this),e=d.data(o);e.options=a.extend(!0,{},a.fn.sketchable.defaults,e.options,c),b(d)}):a(this).data(o)},strokes:function(b){if(b)return this.each(function(){var c=a(this),d=c.data(o);d.strokes=b});var c=a(this).data(o);return c.strokes},handler:function(b){return this.each(function(){var c=a(this),d=c.data(o);b(c,d)})},clear:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;c.sketch&&(c.sketch.clear(),c.strokes=[],c.coords={}),d&&"function"==typeof d.events.clear&&d.events.clear(b,c)})},reset:function(b){return this.each(function(){var c=a(this),d=c.data(o)||{},e=d.options;c.sketchable("destroy").sketchable(b),e&&"function"==typeof e.events.reset&&e.events.reset(c,d)})},destroy:function(){return this.each(function(){var b=a(this),c=b.data(o)||{},d=c.options;b.unbind("mouseup",g),b.unbind("mousemove",f),b.unbind("mousedown",e),b.unbind("touchstart",i),b.unbind("touchmove",j),b.unbind("touchend",k),b.removeData(o),d&&"function"==typeof d.events.destroy&&d.events.destroy(b,c)})}};a.fn.sketchable=function(b){return"object"!=typeof b&&b?p[b]?p[b].apply(this,Array.prototype.slice.call(arguments,1)):(a.error("Unknown method: "+b),this):p.init.apply(this,arguments)},a.fn.sketchable.api=p,a.fn.sketchable.plugins={},a.fn.sketchable.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}}}(jQuery);
|
||||||
|
|
@ -1 +1 @@
|
||||||
!function(a){var b=function(a,b){return new c(a,b)},c=function(a,b){return a?(this.context(a),this.stageWidth=a.width,this.stageHeight=a.height,this.data={},this.drawingDefaults(b),this):void 0};b.fn=c.prototype={context:function(a){if(null===a)throw"No canvas element specified.";return this.canvas=a,this.graphics=a.getContext("2d"),this},drawingDefaults:function(a){return"undefined"==typeof a&&(a={}),"undefined"==typeof a.fillStyle&&(a.fillStyle="#F00"),"undefined"==typeof a.strokeStyle&&(a.strokeStyle="#F0F"),"undefined"==typeof a.lineWidth&&(a.lineWidth=2),"undefined"==typeof a.lineCap&&(a.lineCap="round"),"undefined"==typeof a.lineJoin&&(a.lineJoin="round"),"undefined"==typeof a.miterLimit&&(a.miterLimit=10),this.saveGraphics(a),this.restoreGraphics(a),this},size:function(a,b){return this.stageWidth=a,this.stageHeight=b,this.canvas.width=a,this.canvas.height=b,this.restoreGraphics(),this},background:function(a){return this.beginFill(a),this.graphics.fillRect(0,0,this.stageWidth,this.stageHeight),this.endFill(),this},stage:function(a,b,c){return this.size(a,b).background(c),this},beginFill:function(a){return this.saveGraphics(),this.graphics.fillStyle=a,this},endFill:function(){return this.restoreGraphics(),this},lineStyle:function(a,b,c,d,e){var f={strokeStyle:a||this.graphics.strokeStyle,lineWidth:b||this.graphics.lineWidth,lineCap:c||this.graphics.lineCap,lineJoin:d||this.graphics.lineJoin,miterLimit:e||this.graphics.miterLimit};return this.saveGraphics(f),this.restoreGraphics(f),this},moveTo:function(a,b){return this.graphics.moveTo(a,b),this},lineTo:function(a,b){return this.graphics.lineTo(a,b),this},line:function(a,b,c,d){return this.graphics.moveTo(a,b),this.lineTo(c,d),this},curveTo:function(a,b,c,d){return this.graphics.quadraticCurveTo(c,d,a,b),this},curve:function(a,b,c,d,e,f){return this.graphics.moveTo(a,b),this.curveTo(c,d,e,f),this},stroke:function(){return this.graphics.stroke(),this},strokeRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.strokeRect(a,b,c,d),this.graphics.closePath(),this},fillRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.fillRect(a,b,c,d),this.graphics.closePath(),this},strokeCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.stroke(),this.graphics.closePath(),this},fillCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.fill(),this.graphics.closePath(),this},radialCircle:function(a,b,c,d,e){var f=this.graphics.createRadialGradient(a,b,c,a,b,e);return f.addColorStop(0,d),f.addColorStop(1,"rgba(0,0,0,0)"),this.graphics.fillStyle=f,this.fillCircle(a,b,c),this},beginPath:function(){return this.saveGraphics(),this.graphics.beginPath(),this},closePath:function(){return this.graphics.closePath(),this.restoreGraphics(),this},eraser:function(a){return"undefined"==typeof a&&(a=15),this.graphics.globalCompositeOperation="destination-out",this.lineStyle(null,a),this},pencil:function(a){return"undefined"==typeof a&&(a=2),this.graphics.globalCompositeOperation="source-over",this.lineStyle(null,a),this},clear:function(){return this.graphics.clearRect(0,0,this.stageWidth,this.stageHeight),this},save:function(){return this.graphics.save(),this},restore:function(){return this.graphics.restore(),this},saveGraphics:function(a){return"undefined"==typeof a&&(a=this.data.options),this.data.options=a,this},restoreGraphics:function(a){"undefined"==typeof a&&(a=this.data.options);for(var b in a)this.graphics[b]=a[b];return this},drawImage:function(a,b,c){"undefined"==typeof b&&(b=0),"undefined"==typeof c&&(c=0);var d=this,e=new Image;return e.src=a,e.onload=function(){d.graphics.drawImage(e,b,c)},this}},a.jSketch=b}(this);
|
!function(a){var b=function(a,b){return new c(a,b)},c=function(a,b){return a?(this.context(a),this.stageWidth=a.width,this.stageHeight=a.height,this.data={},this.drawingDefaults(b),this):void 0};b.fn=c.prototype={context:function(a){if(null===a)throw"No canvas element specified.";return this.canvas=a,this.graphics=a.getContext("2d"),this},drawingDefaults:function(a){return"undefined"==typeof a&&(a={}),"undefined"==typeof a.fillStyle&&(a.fillStyle="#F00"),"undefined"==typeof a.strokeStyle&&(a.strokeStyle="#F0F"),"undefined"==typeof a.lineWidth&&(a.lineWidth=2),"undefined"==typeof a.lineCap&&(a.lineCap="round"),"undefined"==typeof a.lineJoin&&(a.lineJoin="round"),"undefined"==typeof a.miterLimit&&(a.miterLimit=10),this.saveGraphics(a),this.restoreGraphics(),this},size:function(a,b){return this.stageWidth=a,this.stageHeight=b,this.canvas.width=a,this.canvas.height=b,this.restoreGraphics(),this},background:function(a){return this.beginFill(a),this.graphics.fillRect(0,0,this.stageWidth,this.stageHeight),this.endFill(),this},stage:function(a,b,c){return this.size(a,b).background(c),this},beginFill:function(a){return this.saveGraphics(),this.graphics.fillStyle=a,this},endFill:function(){return this.restoreGraphics(),this},lineStyle:function(a,b,c,d,e){var f={strokeStyle:a||this.graphics.strokeStyle,lineWidth:b||this.graphics.lineWidth,lineCap:c||this.graphics.lineCap,lineJoin:d||this.graphics.lineJoin,miterLimit:e||this.graphics.miterLimit};return this.saveGraphics(f),this.restoreGraphics(),this},moveTo:function(a,b){return this.graphics.moveTo(a,b),this},lineTo:function(a,b){return this.graphics.lineTo(a,b),this},line:function(a,b,c,d){return this.graphics.moveTo(a,b),this.lineTo(c,d),this},curveTo:function(a,b,c,d){return this.graphics.quadraticCurveTo(c,d,a,b),this},curve:function(a,b,c,d,e,f){return this.graphics.moveTo(a,b),this.curveTo(c,d,e,f),this},stroke:function(){return this.graphics.stroke(),this},strokeRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.strokeRect(a,b,c,d),this.graphics.closePath(),this},fillRect:function(a,b,c,d){return this.graphics.beginPath(),this.graphics.fillRect(a,b,c,d),this.graphics.closePath(),this},strokeCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.stroke(),this.graphics.closePath(),this},fillCircle:function(a,b,c){return this.graphics.beginPath(),this.graphics.arc(a,b,c,0,2*Math.PI,!1),this.graphics.fill(),this.graphics.closePath(),this},radialCircle:function(a,b,c,d,e){var f=this.graphics.createRadialGradient(a,b,c,a,b,d||5);if("array"!==e.constructor.name)e=[this.graphics.fillStyle,"white"];else for(var g=0;g<e.length;g++){var h=e[i];f.addColorStop(i,h)}return this.beginFill(f).fillCircle(a,b,c).endFill(),this},beginPath:function(){return this.saveGraphics(),this.graphics.beginPath(),this},closePath:function(){return this.graphics.closePath(),this.restoreGraphics(),this},eraser:function(a){return"undefined"==typeof a&&(a=15),this.graphics.globalCompositeOperation="destination-out",this.lineStyle(null,a),this},pencil:function(a){return"undefined"==typeof a&&(a=2),this.graphics.globalCompositeOperation="source-over",this.lineStyle(null,a),this},clear:function(){return this.graphics.clearRect(0,0,this.stageWidth,this.stageHeight),this},save:function(){return this.graphics.save(),this},restore:function(){return this.graphics.restore(),this},saveGraphics:function(a){return"undefined"!=typeof a&&(this.data.options=a),this},restoreGraphics:function(a){"undefined"==typeof a&&(a=this.data.options);for(var b in a)this.graphics[b]=a[b];return this},drawImage:function(a,b,c){"undefined"==typeof b&&(b=0),"undefined"==typeof c&&(c=0);var d=this,e=new Image;return e.src=a,e.onload=function(){d.graphics.drawImage(e,b,c)},this}},a.jSketch=b}(this);
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
!function(a){function b(a,b){function g(a){if(h&&h.events&&"function"==typeof h.events[a]){var b=h.events[a];h.events[a]=function(){var c=Array.prototype.slice.call(arguments,0);b.apply(i,c),j[a].apply(i,c)}}else f.events[a]=j[a]}var h=deepExtend(f,b);if(!h.interactive)return b;var i=a.elem,j={init:function(b,d){d.memento=new c(a),d.memento.save(),d.memento.init()},clear:function(a,b){b.memento.reset(),b.memento.save()},mouseup:function(a,b,c){b.memento.save()},destroy:function(a,b){b.memento.destroy()}};if(!e.isMementoReady){for(var k="init mouseup clear destroy".split(" "),l=0;l<k.length;l++)g(k[l]);e.isMementoReady=!0}return deepExtend(e,{undo:function(){var a=this.elem,b=dataBind(a)[d];b.memento.undo()},redo:function(){var a=this.elem,b=dataBind(a)[d];b.memento.redo()},save:function(){var a=this.elem,b=dataBind(a)[d];b.memento.save()}}),h}var c=function(a){function b(){if(g>0){g--;var a=new Image;a.src=f[g].image,a.onload=function(){d(this)}}}function c(){if(g<f.length-1){g++;var a=new Image;a.src=f[g].image,a.onload=function(){d(this)}}}function d(b){a.handler(function(a,c){c.sketch.clear(),c.sketch.graphics.drawImage(b,0,0)})}function e(b){if(b.ctrlKey)switch(b.which){case 26:b.shiftKey?a.redo():a.undo();break;case 25:a.redo()}}var f=[],g=-1;this.undo=function(){b(),a.handler(function(a,b){f[g]&&(b.strokes=f[g].strokes.slice())})},this.redo=function(){c(),a.handler(function(a,b){f[g]&&(b.strokes=f[g].strokes.slice())})},this.reset=function(){f=[],g=-1},this.save=function(){g++,g<f.length&&(f.length=g),a.handler(function(a,b){f.push({image:a.toDataURL(),strokes:b.strokes.slice()})})},this.init=function(){Event.remove(document,"keypress",e),Event.add(document,"keypress",e)},this.destroy=function(){Event.remove(document,"keypress",e),this.reset()}},d="sketchable",e=Sketchable.fn,f=Sketchable.fn.defaults,g=e.init;e.init=function(a){var c=b(this,a);return g.call(this,c),this}}(this);
|
!function(a){function b(a){function b(){if(g>0){g--;var a=new Image;a.src=f[g].image,a.onload=function(){d(this)}}}function c(){if(g<f.length-1){g++;var a=new Image;a.src=f[g].image,a.onload=function(){d(this)}}}function d(b){a.handler(function(a,c){c.sketch.clear(),c.sketch.graphics.drawImage(b,0,0)})}function e(a){if(a.ctrlKey)switch(a.which){case 26:a.shiftKey?h.redo():h.undo();break;case 25:h.redo()}}var f=[],g=-1,h=this;this.undo=function(){return b(),a.handler(function(a,b){f[g]&&(b.strokes=f[g].strokes.slice())}),this},this.redo=function(){return c(),a.handler(function(a,b){f[g]&&(b.strokes=f[g].strokes.slice())}),this},this.reset=function(){return f=[],g=-1,this},this.save=function(){return g++,g<f.length&&(f.length=g),a.handler(function(a,b){f.push({image:a.toDataURL(),strokes:b.strokes.slice()})}),this},this.init=function(){return Event.remove(document,"keypress",e),Event.add(document,"keypress",e),this},this.destroy=function(){return Event.remove(document,"keypress",e),this.reset()}}var c="sketchable";Sketchable.prototype.plugins.memento=function(a){function d(b){if(!e.options.$$bound)if(e.options.$$bound=!0,e.options.events&&"function"==typeof e.options.events[b]){var c=e.options.events[b];e.options.events[b]=function(){c.apply(a,arguments),f[b].apply(a,arguments)}}else e.options.events[b]=f[b]}for(var e=a.config(),f={clear:function(a,b){b.memento.reset()},mouseup:function(a,b,c){b.memento.save()},destroy:function(a,b){b.memento.destroy()}},g="mouseup clear destroy".split(" "),h=0;h<g.length;h++)d(g[h]);deepExtend(a,{undo:function(){var a=this.elem,b=dataBind(a)[c];b.memento.undo()},redo:function(){var a=this.elem,b=dataBind(a)[c];b.memento.redo()},save:function(){var a=this.elem,b=dataBind(a)[c];b.memento.save()}}),e.memento=new b(a),e.memento.init().save()}}(this);
|
||||||
|
|
@ -1 +1 @@
|
||||||
!function(a){function b(b){var c=b.getBoundingClientRect(),d=document.body,e=document.documentElement,f=a.pageYOffset||e.scrollTop||d.scrollTop,g=a.pageXOffset||e.scrollLeft||d.scrollLeft,h=e.clientTop||d.clientTop||0,i=e.clientLeft||d.clientLeft||0,j=c.top+f-h,k=c.left+g-i;return{top:Math.round(j),left:Math.round(k)}}function c(a){var c=a.target,d=b(c);return{x:Math.round(a.pageX-d.left),y:Math.round(a.pageY-d.top)}}function d(a,b,c){b.coords[a]||(b.coords[a]=[]);var d=(new Date).getTime();b.options.relTimestamps&&(0===b.strokes.length&&0===b.coords[a].length&&(b.timestamp=d),d-=b.timestamp),b.coords[a].push([c.x,c.y,d,+b.sketch.isDrawing])}function e(a){return a.touches?!1:void l(a)}function f(a){return a.touches?!1:void m(a)}function g(a){return a.touches?!1:void n(a)}function h(a,b){var c=a.target,d=dataBind(c)[o],e=d.options,f=a.changedTouches;if(e.multitouch)for(var g=0;g<f.length;g++){var h=f[g];h.type=a.type,b(h)}else{var h=f[0];h.type=a.type,b(h)}}function i(a){h(a,l),a.preventDefault()}function j(a){h(a,m),a.preventDefault()}function k(a){h(a,n),a.preventDefault()}function l(a){if(Event.isRightClick(a))return!1;var b=a.identifier||0,e=a.target,f=dataBind(e)[o],g=f.options;if(g.interactive){f.sketch.isDrawing=!0;var h=c(a);g.graphics.firstPointSize>0&&f.sketch.fillCircle(h.x,h.y,g.graphics.firstPointSize),f.coords[b]||(f.coords[b]=[]),f.coords[b].length>0&&(f.strokes.push(f.coords[b]),f.coords[b]=[]),d(b,f,h),"function"==typeof g.events.mousedown&&g.events.mousedown(e,f,a)}}function m(a){var b=a.identifier||0,e=a.target,f=dataBind(e)[o],g=f.options;if(g.interactive&&(g.mouseupMovements&&0!==f.strokes.length||f.sketch.isDrawing)){var h=c(a);if(f.sketch.isDrawing){var i=f.coords[b][f.coords[b].length-1];f.sketch.beginPath().line(i[0],i[1],h.x,h.y).stroke().closePath()}d(b,f,h),"function"==typeof g.events.mousemove&&g.events.mousemove(e,f,a)}}function n(a){var b=a.identifier||0,c=a.target,d=dataBind(c)[o],e=d.options;e.interactive&&(d.sketch.isDrawing=!1,d.strokes.push(d.coords[b]),d.coords[b]=[],"function"==typeof e.events.mouseup&&e.events.mouseup(c,d,a))}var o="sketchable",p=function(a,b){return new q(a,b)},q=function(a,b){return a?(this.elem=a,"undefined"==typeof b&&(b={}),this.init(b)):void 0};p.fn=q.prototype={init:function(a){var b=deepExtend(p.fn.defaults,a||{}),c=this.elem,d=dataBind(c)[o];d||(b.interactive&&(Event.add(c,"mousedown",e),Event.add(c,"mousemove",f),Event.add(c,"mouseup",g),Event.add(c,"touchstart",i),Event.add(c,"touchmove",j),Event.add(c,"touchend",k),this.onselectstart=function(){return!1}),b.cssCursors&&(c.style.cursor=b.interactive?"pointer":"not-allowed"));var h=new jSketch(c,b.graphics);return dataBind(c)[o]={strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:h,options:b},"function"==typeof b.events.init&&b.events.init(c,dataBind(c)[o]),this},config:function(a){var b=this.elem,c=dataBind(b)[o];return c.options=deepExtend(p.fn.defaults,a||{}),this},strokes:function(a){var b=this.elem;if(a){var c=dataBind(b)[o];return c.strokes=a,this}var c=dataBind(b)[o];return c.strokes},handler:function(a){var b=this.elem,c=dataBind(b)[o];return a(b,c),this},clear:function(){var a=this.elem,b=dataBind(a)[o],c=b.options;return b.sketch.clear(),b.strokes=[],b.coords={},"function"==typeof c.events.clear&&c.events.clear(a,b),this},reset:function(a){var b=this.elem,c=dataBind(b)[o],d=c.options;return this.destroy().init(a),"function"==typeof d.events.reset&&d.events.reset(b,c),this},destroy:function(){var a=this.elem,b=dataBind(a)[o],c=b.options;return c.interactive&&(Event.remove(a,"mouseup",g),Event.remove(a,"mousemove",f),Event.remove(a,"mousedown",e),Event.remove(a,"touchstart",i),Event.remove(a,"touchmove",j),Event.remove(a,"touchend",k)),dataBind(a)[o]=null,"function"==typeof c.events.destroy&&c.events.destroy(a,b),this}},p.fn.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}},a.Sketchable=p}(this);
|
!function(a){function b(a,b){if(!a)throw new Error("Sketchable requires a DOM element.");return"string"==typeof a&&(a=q.querySelector(a)),this.elem=a,this.init(b)}function c(b){var c=b.getBoundingClientRect(),d=q.body,e=q.documentElement,f=a.pageYOffset||e.scrollTop||d.scrollTop,g=a.pageXOffset||e.scrollLeft||d.scrollLeft,h=e.clientTop||d.clientTop||0,i=e.clientLeft||d.clientLeft||0,j=c.top+f-h,k=c.left+g-i;return{top:Math.round(j),left:Math.round(k)}}function d(a){var b=a.target,d=c(b);return{x:Math.round(a.pageX-d.left),y:Math.round(a.pageY-d.top)}}function e(a,b,c){b.coords[a]||(b.coords[a]=[]);var d=(new Date).getTime();b.options.relTimestamps&&(0===b.strokes.length&&0===b.coords[a].length&&(b.timestamp=d),d-=b.timestamp),b.coords[a].push([c.x,c.y,d,+b.sketch.isDrawing])}function f(a){return a.touches?!1:void m(a)}function g(a){return a.touches?!1:void n(a)}function h(a){return a.touches?!1:void o(a)}function i(a,b){var c=a.target,d=dataBind(c)[p],e=d.options,f=a.touches;if(e.multitouch)for(var g=0;g<f.length;g++){var h=f[g];h.type=a.type,b(h)}else{var h=f[0];h.type=a.type,b(h)}a.preventDefault()}function j(a){i(a,m),a.preventDefault()}function k(a){i(a,n),a.preventDefault()}function l(a){i(a,o),a.preventDefault()}function m(a){if(Event.isRightClick(a))return!1;var b=a.identifier||0,c=a.target,f=dataBind(c)[p],g=f.options;if(g.interactive){f.sketch.isDrawing=!0;var h=d(a);g.graphics.firstPointSize>0&&f.sketch.beginFill(g.graphics.fillStyle).fillCircle(h.x,h.y,g.graphics.firstPointSize).endFill(),f.coords[b]||(f.coords[b]=[]),f.coords[b].length>0&&(f.strokes.push(f.coords[b]),f.coords[b]=[]),e(b,f,h),"function"==typeof g.events.mousedown&&g.events.mousedown(c,f,a)}}function n(a){var b=a.identifier||0,c=a.target,f=dataBind(c)[p],g=f.options;if(g.interactive&&(g.mouseupMovements&&0!==f.strokes.length||f.sketch.isDrawing)){var h=d(a);if(f.sketch.isDrawing){var i=f.coords[b][f.coords[b].length-1];f.sketch.beginPath().line(i[0],i[1],h.x,h.y).stroke().closePath()}e(b,f,h),"function"==typeof g.events.mousemove&&g.events.mousemove(c,f,a)}}function o(a){var b=a.identifier||0,c=a.target,d=dataBind(c)[p],e=d.options;e.interactive&&(d.sketch.isDrawing=!1,d.strokes.push(d.coords[b]),d.coords[b]=[],"function"==typeof e.events.mouseup&&e.events.mouseup(c,d,a))}var p="sketchable",q=a.document;b.prototype={init:function(a){var a=deepExtend({},b.prototype.defaults,a||{}),c=this.elem,d=dataBind(c)[p];d||(Event.add(c,"mousedown",f),Event.add(c,"mousemove",g),Event.add(c,"mouseup",h),Event.add(c,"touchstart",j),Event.add(c,"touchmove",k),Event.add(c,"touchend",l),c.onselectstart=function(){return!1},a.cssCursors&&(c.style.cursor=a.interactive?"pointer":"not-allowed"));var e=new jSketch(c,a.graphics);dataBind(c)[p]=d={strokes:[],coords:{},timestamp:(new Date).getTime(),sketch:e,sketchable:this,options:a},"function"==typeof a.events.init&&a.events.init(c,d);for(var i in this.plugins)this.plugins[i](this);return this},config:function(a){var c=this.elem,d=dataBind(c)[p];return a?(d.options=deepExtend({},b.prototype.defaults,a||{}),this):d},strokes:function(a){var b=this.elem;if(a){var c=dataBind(b)[p];return c.strokes=a,this}var c=dataBind(b)[p];return c.strokes},handler:function(a){var b=this.elem,c=dataBind(b)[p];return a(b,c),this},clear:function(){var a=this.elem,b=dataBind(a)[p],c=b.options;return b.sketch.clear(),b.strokes=[],b.coords={},"function"==typeof c.events.clear&&c.events.clear(a,b),this},reset:function(a){var b=this.elem,c=dataBind(b)[p],a=c.options;return this.destroy().init(a),"function"==typeof a.events.reset&&a.events.reset(b,c),this},destroy:function(){var a=this.elem,b=dataBind(a)[p],c=b.options;return Event.remove(a,"mouseup",h),Event.remove(a,"mousemove",g),Event.remove(a,"mousedown",f),Event.remove(a,"touchstart",j),Event.remove(a,"touchmove",k),Event.remove(a,"touchend",l),dataBind(a)[p]=null,"function"==typeof c.events.destroy&&c.events.destroy(a,b),this}},b.prototype.plugins={},b.prototype.defaults={interactive:!0,mouseupMovements:!1,relTimestamps:!1,multitouch:!0,cssCursors:!0,events:{},graphics:{firstPointSize:3,lineWidth:3,strokeStyle:"#F0F",fillStyle:"#F0F",lineCap:"round",lineJoin:"round",miterLimit:10}},a.Sketchable=b}(this);
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
|
|
@ -1,59 +1,57 @@
|
||||||
/*!
|
/*!
|
||||||
* jQuery sketchable | v1.8.1 | Luis A. Leiva | MIT license
|
* jQuery sketchable | v2.0 | Luis A. Leiva | MIT license
|
||||||
* A jQuery plugin for the jSketch drawing library.
|
* A jQuery plugin for the jSketch drawing library.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name $
|
* @method $
|
||||||
* @class
|
* @description jQuery constructor. See {@link https://jquery.com}
|
||||||
* @ignore
|
* @param {String} selector - jQuery selector.
|
||||||
* @description This just documents the method that is added to jQuery by this plugin.
|
* @return {Object} jQuery
|
||||||
* See <a href="http://jquery.com/">the jQuery library</a> for full details.
|
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @name $.fn
|
* @namespace $.fn
|
||||||
* @memberof $
|
* @description jQuery prototype. See {@link https://learn.jquery.com/plugins/}
|
||||||
* @description This just documents the method that is added to jQuery by this plugin.
|
|
||||||
* See <a href="http://jquery.com/">the jQuery library</a> for full details.
|
|
||||||
*/
|
*/
|
||||||
;(function($){
|
|
||||||
// Custom namespace ID.
|
/* eslint-env browser */
|
||||||
var _ns = 'sketchable';
|
;(function($) {
|
||||||
/**
|
|
||||||
* jQuery sketchable plugin API.
|
// Custom namespace ID, for private data bindind.
|
||||||
* @namespace methods
|
var namespace = 'sketchable';
|
||||||
*/
|
|
||||||
|
// Begin jQuery Sketchable plugin API.
|
||||||
var methods = {
|
var methods = {
|
||||||
/**
|
/**
|
||||||
* Initializes the selected jQuery objects.
|
* Initialize the selected jQuery objects.
|
||||||
* @param {Object} opts - Plugin configuration (see defaults).
|
* @param {Object} [options] - Configuration (default: {@link $.fn.sketchable.defaults}).
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
|
* @memberof $.fn.sketchable
|
||||||
* @ignore
|
* @ignore
|
||||||
* @namespace methods.init
|
* @protected
|
||||||
* @example $(selector).sketchable();
|
|
||||||
*/
|
*/
|
||||||
init: function(opts) {
|
init: function(opts) {
|
||||||
// Options will be available for all plugin methods.
|
|
||||||
var options = $.extend(true, {}, $.fn.sketchable.defaults, opts || {});
|
var options = $.extend(true, {}, $.fn.sketchable.defaults, opts || {});
|
||||||
|
|
||||||
return this.each(function() {
|
return this.each(function() {
|
||||||
var elem = $(this), data = elem.data(_ns);
|
var elem = $(this), data = elem.data(namespace);
|
||||||
// Check if element is not initialized yet.
|
// Check if element is not initialized yet.
|
||||||
if (!data) {
|
if (!data) {
|
||||||
// Attach event listeners.
|
// Attach event listeners.
|
||||||
if (options.interactive) {
|
elem.bind('mousedown', mousedownHandler);
|
||||||
elem.bind('mousedown', mousedownHandler);
|
elem.bind('mousemove', mousemoveHandler);
|
||||||
elem.bind('mousemove', mousemoveHandler);
|
elem.bind('mouseup', mouseupHandler);
|
||||||
elem.bind('mouseup', mouseupHandler);
|
elem.bind('touchstart', touchdownHandler);
|
||||||
elem.bind('touchstart', touchdownHandler);
|
elem.bind('touchmove', touchmoveHandler);
|
||||||
elem.bind('touchmove', touchmoveHandler);
|
elem.bind('touchend', touchupHandler);
|
||||||
elem.bind('touchend', touchupHandler);
|
// Fix unwanted highlight "bug". Note: `this` is the actual DOM element.
|
||||||
// Fix Chrome "bug".
|
this.onselectstart = function() { return false };
|
||||||
this.onselectstart = function(){ return false };
|
|
||||||
}
|
|
||||||
postProcess(elem, options);
|
postProcess(elem, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sketch = new jSketch(this, options.graphics);
|
var sketch = new jSketch(this, options.graphics);
|
||||||
// Reconfigure element data.
|
// Reconfigure element data.
|
||||||
elem.data(_ns, {
|
elem.data(namespace, {
|
||||||
// All strokes will be stored here.
|
// All strokes will be stored here.
|
||||||
strokes: [],
|
strokes: [],
|
||||||
// This will store one stroke per touching finger.
|
// This will store one stroke per touching finger.
|
||||||
|
|
@ -65,76 +63,93 @@
|
||||||
// Save also a pointer to the given options.
|
// Save also a pointer to the given options.
|
||||||
options: options
|
options: options
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger init event.
|
// Trigger init event.
|
||||||
if (typeof options.events.init === 'function') {
|
if (options.events && typeof options.events.init === 'function') {
|
||||||
options.events.init(elem, elem.data(_ns));
|
options.events.init(elem, elem.data(namespace));
|
||||||
|
}
|
||||||
|
// Initialize plugins.
|
||||||
|
for (var name in $.fn.sketchable.plugins) {
|
||||||
|
$.fn.sketchable.plugins[name](elem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Changes config on the fly of an existing sketchable element.
|
* Change configuration of an existing jQuery Sketchable element.
|
||||||
* Previous options are retained. To completely reconfigure them just use the reset method.
|
* @param {Object} [options] - Configuration (default: {@link $.fn.sketchable.defaults}).
|
||||||
* @param {Object} opts - Plugin configuration (see defaults).
|
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
* @namespace methods.config
|
* @memberof $.fn.sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('config', { interactive: false }); // Later on:
|
* var $canvas = $('canvas').sketchable('config', { interactive: false });
|
||||||
* $(selector).sketchable('config', { interactive: true });
|
* // Update later on:
|
||||||
|
* $canvas.sketchable('config', { interactive: true });
|
||||||
*/
|
*/
|
||||||
config: function(opts) {
|
config: function(opts) {
|
||||||
return this.each(function(){
|
if (opts) { // setter
|
||||||
var elem = $(this), data = elem.data(_ns);
|
return this.each(function() {
|
||||||
data.options = $.extend(true, {}, $.fn.sketchable.defaults, data.options, opts || {});
|
var elem = $(this), data = elem.data(namespace);
|
||||||
postProcess(elem);
|
data.options = $.extend(true, {}, $.fn.sketchable.defaults, data.options, opts);
|
||||||
});
|
postProcess(elem);
|
||||||
|
});
|
||||||
|
} else { // getter
|
||||||
|
return $(this).data(namespace);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Gets/Sets drawing data strokes sequence.
|
* Get/Set drawing data strokes sequence.
|
||||||
* @param {Array} arr - Multidimensional array of [x,y,time,status] tuples; status = 0 (pen down) or 1 (pen up).
|
* @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)
|
* @return Strokes object on get, jQuery instance on set (with the new data attached).
|
||||||
* @namespace methods.strokes
|
* @memberof $.fn.sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('strokes'); // Getter
|
* // Getter: read associated strokes.
|
||||||
* $(selector).sketchable('strokes', [ [arr1], ..., [arrN] ]); // Setter
|
* $('canvas').sketchable('strokes');
|
||||||
|
* // Setter: replace associated strokes.
|
||||||
|
* $('canvas').sketchable('strokes', [ [arr1], ..., [arrN] ]);
|
||||||
*/
|
*/
|
||||||
strokes: function(arr) {
|
strokes: function(arr) {
|
||||||
if (arr) { // setter
|
if (arr) { // setter
|
||||||
return this.each(function() {
|
return this.each(function() {
|
||||||
var elem = $(this), data = elem.data(_ns);
|
var elem = $(this), data = elem.data(namespace);
|
||||||
data.strokes = arr;
|
data.strokes = arr;
|
||||||
});
|
});
|
||||||
} else { // getter
|
} else { // getter
|
||||||
var data = $(this).data(_ns);
|
var data = $(this).data(namespace);
|
||||||
return data.strokes;
|
return data.strokes;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Allows low-level manipulation of the sketchable canvas.
|
* Allow low-level manipulation of the sketchable canvas.
|
||||||
* @param {Function} callback - Callback function, invoked with 2 arguments: elem (jQuery element) and data (jQuery element data).
|
* @param {Function} callback - Callback function, invoked with 2 arguments: elem (CANVAS element) and data (private element data).
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
* @namespace methods.handler
|
* @memberof $.fn.sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('handler', function(elem, data){
|
* $('canvas').sketchable('handler', function(elem, data) {
|
||||||
* // do something with elem or data
|
* // do something with elem or data
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
handler: function(callback) {
|
handler: function(callback) {
|
||||||
return this.each(function() {
|
return this.each(function() {
|
||||||
var elem = $(this), data = elem.data(_ns);
|
var elem = $(this), data = elem.data(namespace);
|
||||||
callback(elem, data);
|
callback(elem, data);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Clears canvas (together with strokes data).
|
* Clears canvas <b>together with</b> associated strokes data.
|
||||||
* If you need to clear canvas only, just invoke <tt>data.sketch.clear()</tt> via <tt>$(selector).sketchable('handler')</tt>.
|
|
||||||
* @see methods.handler
|
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
* @namespace methods.clear
|
* @memberof $.fn.sketchable
|
||||||
* @example $(selector).sketchable('clear');
|
* @see $.fn.sketchable.handler
|
||||||
|
* @example
|
||||||
|
* var $canvas = $('canvas').sketchable();
|
||||||
|
* // This will remove strokes data as well.
|
||||||
|
* $canvas.clear();
|
||||||
|
* // If you only need to clear the canvas, just do:
|
||||||
|
* $canvas.sketchable('handler', function(elem, data) {
|
||||||
|
* data.sketch.clear();
|
||||||
|
* });
|
||||||
*/
|
*/
|
||||||
clear: function() {
|
clear: function() {
|
||||||
return this.each(function() {
|
return this.each(function() {
|
||||||
var elem = $(this), data = elem.data(_ns) || {}, options = data.options;
|
var elem = $(this), data = elem.data(namespace) || {}, options = data.options;
|
||||||
if (data.sketch) {
|
if (data.sketch) {
|
||||||
data.sketch.clear();
|
data.sketch.clear();
|
||||||
data.strokes = [];
|
data.strokes = [];
|
||||||
|
|
@ -146,17 +161,21 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Reinitializes a sketchable canvas with given opts.
|
* Reinitialize a sketchable canvas with given configuration options.
|
||||||
* @param {Object} opts - Plugin configuration (see defaults).
|
* @param {Object} [options] - Configuration (default: {@link $.fn.sketchable.defaults}).
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
* @namespace methods.reset
|
* @memberof $.fn.sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('reset');
|
* var $canvas = $('canvas').sketchable();
|
||||||
* $(selector).sketchable('reset', {interactive:false});
|
* // Reset default state.
|
||||||
|
* $canvas.sketchable('reset');
|
||||||
|
* // Reset with custom configuration.
|
||||||
|
* $canvas.sketchable('reset', { interactive:false });
|
||||||
*/
|
*/
|
||||||
reset: function(opts) {
|
reset: function(opts) {
|
||||||
return this.each(function(){
|
return this.each(function() {
|
||||||
var elem = $(this), data = elem.data(_ns) || {}, options = data.options;
|
var elem = $(this), data = elem.data(namespace) || {}, options = data.options;
|
||||||
|
|
||||||
elem.sketchable('destroy').sketchable(opts);
|
elem.sketchable('destroy').sketchable(opts);
|
||||||
|
|
||||||
if (options && typeof options.events.reset === 'function') {
|
if (options && typeof options.events.reset === 'function') {
|
||||||
|
|
@ -165,83 +184,115 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Destroys sketchable canvas (together with strokes data and events).
|
* Destroy sketchable canvas, together with strokes data and associated events.
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
* @namespace methods.destroy
|
* @memberof $.fn.sketchable
|
||||||
* @example $(selector).sketchable('destroy');
|
* @example
|
||||||
|
* var $canvas = $('canvas').sketchable();
|
||||||
|
* // This will leave the canvas element intact.
|
||||||
|
* $canvas.sketchable('destroy');
|
||||||
*/
|
*/
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
return this.each(function(){
|
return this.each(function() {
|
||||||
var elem = $(this), data = elem.data(_ns) || {}, options = data.options;
|
var elem = $(this), data = elem.data(namespace) || {}, options = data.options;
|
||||||
if (options.interactive) {
|
|
||||||
elem.unbind('mouseup', mouseupHandler);
|
elem.unbind('mouseup', mouseupHandler);
|
||||||
elem.unbind('mousemove', mousemoveHandler);
|
elem.unbind('mousemove', mousemoveHandler);
|
||||||
elem.unbind('mousedown', mousedownHandler);
|
elem.unbind('mousedown', mousedownHandler);
|
||||||
elem.unbind('touchstart', touchdownHandler);
|
elem.unbind('touchstart', touchdownHandler);
|
||||||
elem.unbind('touchmove', touchmoveHandler);
|
elem.unbind('touchmove', touchmoveHandler);
|
||||||
elem.unbind('touchend', touchupHandler);
|
elem.unbind('touchend', touchupHandler);
|
||||||
}
|
|
||||||
elem.removeData(_ns);
|
elem.removeData(namespace);
|
||||||
|
|
||||||
if (options && typeof options.events.destroy === 'function') {
|
if (options && typeof options.events.destroy === 'function') {
|
||||||
options.events.destroy(elem, data);
|
options.events.destroy(elem, data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a <tt>jQuery.sketchable</tt> instance.
|
* Create a <tt>jQuery Sketchable</tt> instance.
|
||||||
* This is a jQuery plugin for the <tt>jSketch</tt> drawing class.
|
* This is a jQuery wrapper for the <tt>jSketch</tt> drawing class.
|
||||||
|
* @namespace $.fn.sketchable
|
||||||
* @param {String|Object} method - Method to invoke, or a configuration object.
|
* @param {String|Object} method - Method to invoke, or a configuration object.
|
||||||
* @return jQuery
|
* @return jQuery
|
||||||
* @class
|
* @version 1.9
|
||||||
* @version 1.8.1
|
|
||||||
* @date 28 Nov 2016
|
|
||||||
* @author Luis A. Leiva
|
* @author Luis A. Leiva
|
||||||
* @license MIT license
|
* @license MIT license
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable();
|
* $('canvas').sketchable();
|
||||||
* $(selector).sketchable({interactive:false});
|
* $('canvas').sketchable({ interactive:false });
|
||||||
* @see methods
|
|
||||||
*/
|
*/
|
||||||
$.fn.sketchable = function(method) {
|
$.fn.sketchable = function(method) {
|
||||||
// These "magic" keywords return internal plugin methods,
|
if (typeof method === 'object' || !method) {
|
||||||
// so that they can be easily extended/overriden.
|
return methods.init.apply(this, arguments);
|
||||||
if ('methods functions hooks'.split(' ').indexOf(method) > -1) {
|
|
||||||
return methods;
|
|
||||||
} else if (methods[method]) {
|
} else if (methods[method]) {
|
||||||
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||||
} else if (typeof method === 'object' || !method) {
|
|
||||||
return methods.init.apply(this, arguments);
|
|
||||||
} else {
|
} else {
|
||||||
$.error('Method '+ method +' does not exist. See jQuery.sketchable("methods").');
|
$.error('Unknown method: ' + method);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default configuration.
|
* Public API. Provides access to all methods of jQuery Sketchable instances.<br>
|
||||||
* Note that mouse* callbacks are triggered only if <tt>interactive</tt> is set to <tt>true</tt>.
|
* Note: This is equivalent to accessing `Sketchable.prototype` in the non-jQuery version.
|
||||||
* @name defaults
|
* @namespace $.fn.sketchable.api
|
||||||
* @default
|
* @type {Object}
|
||||||
* @memberof $.fn.sketchable
|
* @see Sketchable.prototype
|
||||||
|
*/
|
||||||
|
$.fn.sketchable.api = methods;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugins store.
|
||||||
|
* @namespace $.fn.sketchable.plugins
|
||||||
|
* @type {Object}
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable({
|
* // All plugins are created after instance initialization:
|
||||||
|
* $.fn.sketchable.plugins['your-awesome-plugin'] = function($instance) {
|
||||||
|
* // Do something with the jQuery Sketchable instance.
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
$.fn.sketchable.plugins = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default configuration.
|
||||||
|
* Note that `events.mouse*` callbacks are triggered only if <tt>interactive</tt> is set to <tt>true</tt>.
|
||||||
|
* @namespace $.fn.sketchable.defaults
|
||||||
|
* @type {Object}
|
||||||
|
* @example
|
||||||
|
* // The following is the default configuration:
|
||||||
|
* new Sketchable('canvas', {
|
||||||
* interactive: true,
|
* interactive: true,
|
||||||
* mouseupMovements: false,
|
* mouseupMovements: false,
|
||||||
* relTimestamps: false,
|
* relTimestamps: false,
|
||||||
* multitouch: true,
|
* multitouch: false,
|
||||||
* cssCursors: true,
|
* cssCursors: true,
|
||||||
|
* // Event hooks.
|
||||||
* events: {
|
* events: {
|
||||||
* init: function(elem, data){ },
|
* init: function(elem, data) {
|
||||||
* clear: function(elem, data){ },
|
* // Called when the Sketchable instance is created.
|
||||||
* destroy: function(elem, data){ },
|
* },
|
||||||
* mousedown: function(elem, data, evt){ },
|
* destroy: function(elem, data) {
|
||||||
* mousemove: function(elem, data, evt){ },
|
* // Called when the Sketchable instance is destroyed.
|
||||||
* mouseup: function(elem, data, evt){ },
|
* },
|
||||||
|
* clear: function(elem, data) {
|
||||||
|
* // Called when the canvas is cleared.
|
||||||
|
* // This event includes clearing strokes data, too.
|
||||||
|
* },
|
||||||
|
* mousedown: function(elem, data, evt) {
|
||||||
|
* // Called when the user clicks or taps on the canvas.
|
||||||
|
* },
|
||||||
|
* mousemove: function(elem, data, evt) {
|
||||||
|
* // Called when the user moves the mouse or finger over the canvas.
|
||||||
|
* },
|
||||||
|
* mouseup: function(elem, data, evt) {
|
||||||
|
* // Called when the user lifts the mouse or finger off the canvas.
|
||||||
|
* },
|
||||||
* },
|
* },
|
||||||
|
* // Drawing options, to be used in jSketch lib.
|
||||||
* graphics: {
|
* graphics: {
|
||||||
* firstPointSize: 3,
|
* firstPointSize: 3,
|
||||||
* lineWidth: 3,
|
* lineWidth: 3,
|
||||||
|
|
@ -265,15 +316,16 @@
|
||||||
multitouch: true,
|
multitouch: true,
|
||||||
// Display CSS cursors, mainly to indicate whether the element is interactive or not.
|
// Display CSS cursors, mainly to indicate whether the element is interactive or not.
|
||||||
cssCursors: true,
|
cssCursors: true,
|
||||||
// Event callbacks.
|
// Event hooks.
|
||||||
events: {
|
events: {
|
||||||
// init: function(elem, data){ },
|
// init: function(elem, data) { },
|
||||||
// clear: function(elem, data){ },
|
// clear: function(elem, data) { },
|
||||||
// destroy: function(elem, data){ },
|
// destroy: function(elem, data) { },
|
||||||
// mousedown: function(elem, data, evt){ },
|
// mousedown: function(elem, data, evt) { },
|
||||||
// mousemove: function(elem, data, evt){ },
|
// mousemove: function(elem, data, evt) { },
|
||||||
// mouseup: function(elem, data, evt){ },
|
// mouseup: function(elem, data, evt) { },
|
||||||
},
|
},
|
||||||
|
// Drawing options, to be used in jSketch lib.
|
||||||
graphics: {
|
graphics: {
|
||||||
firstPointSize: 3,
|
firstPointSize: 3,
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
|
|
@ -289,7 +341,7 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function postProcess(elem, options) {
|
function postProcess(elem, options) {
|
||||||
if (!options) options = elem.data(_ns).options;
|
if (!options) options = elem.data(namespace).options;
|
||||||
if (options.cssCursors) {
|
if (options.cssCursors) {
|
||||||
// Visually indicate whether this element is interactive or not.
|
// Visually indicate whether this element is interactive or not.
|
||||||
elem[0].style.cursor = options.interactive ? 'pointer' : 'not-allowed';
|
elem[0].style.cursor = options.interactive ? 'pointer' : 'not-allowed';
|
||||||
|
|
@ -351,9 +403,12 @@
|
||||||
upHandler(e);
|
upHandler(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function execTouchEvent(e, callback) {
|
function execTouchEvent(e, callback) {
|
||||||
var elem = $(e.target), data = elem.data(_ns), options = data.options;
|
var elem = $(e.target), data = elem.data(namespace), options = data.options;
|
||||||
var touches = e.originalEvent.changedTouches;
|
var touches = e.originalEvent.touches;
|
||||||
if (options.multitouch) {
|
if (options.multitouch) {
|
||||||
for (var i = 0; i < touches.length; i++) {
|
for (var i = 0; i < touches.length; i++) {
|
||||||
var touch = touches[i];
|
var touch = touches[i];
|
||||||
|
|
@ -402,7 +457,7 @@
|
||||||
|
|
||||||
var idx = e.identifier || 0,
|
var idx = e.identifier || 0,
|
||||||
elem = $(e.target),
|
elem = $(e.target),
|
||||||
data = elem.data(_ns),
|
data = elem.data(namespace),
|
||||||
options = data.options;
|
options = data.options;
|
||||||
// Exit early if interactivity is disabled.
|
// Exit early if interactivity is disabled.
|
||||||
if (!options.interactive) return;
|
if (!options.interactive) return;
|
||||||
|
|
@ -433,11 +488,9 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function moveHandler(e) {
|
function moveHandler(e) {
|
||||||
var idx = e.identifier || 0,
|
var idx = e.identifier || 0;
|
||||||
elem = $(e.target),
|
var elem = $(e.target), data = elem.data(namespace), options = data.options;
|
||||||
data = elem.data(_ns),
|
|
||||||
options = data.options;
|
|
||||||
// Exit early if interactivity is disabled.
|
|
||||||
if (!options.interactive) return;
|
if (!options.interactive) return;
|
||||||
|
|
||||||
//if (!options.mouseupMovements && !data.sketch.isDrawing) return;
|
//if (!options.mouseupMovements && !data.sketch.isDrawing) return;
|
||||||
|
|
@ -460,11 +513,9 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function upHandler(e) {
|
function upHandler(e) {
|
||||||
var idx = e.identifier || 0,
|
var idx = e.identifier || 0;
|
||||||
elem = $(e.target),
|
var elem = $(e.target), data = elem.data(namespace), options = data.options;
|
||||||
data = elem.data(_ns),
|
|
||||||
options = data.options;
|
|
||||||
// Exit early if interactivity is disabled.
|
|
||||||
if (!options.interactive) return;
|
if (!options.interactive) return;
|
||||||
|
|
||||||
data.sketch.isDrawing = false;
|
data.sketch.isDrawing = false;
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,31 @@
|
||||||
/*!
|
/*!
|
||||||
* Memento plugin for jQuery sketchable | v1.2 | Luis A. Leiva | MIT license
|
* Memento plugin for jQuery Sketchable | v2.0 | Luis A. Leiva | MIT license
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @name $
|
|
||||||
* @class
|
|
||||||
* See <a href="http://jquery.com/">the jQuery library</a> for full details.
|
|
||||||
* This just documents the method that is added to jQuery by this plugin.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @name $.fn
|
|
||||||
* @class
|
|
||||||
* See <a href="http://jquery.com/">the jQuery library</a> for full details.
|
|
||||||
* This just documents the method that is added to jQuery by this plugin.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* eslint-env browser */
|
||||||
;(function($) {
|
;(function($) {
|
||||||
|
|
||||||
/**
|
// Custom namespace ID, for private data bindind.
|
||||||
* This plugin implements the <a href="https://en.wikipedia.org/wiki/Memento_pattern">Memento pattern</a>.
|
var namespace = 'sketchable';
|
||||||
* This plugin automatically modifies the jSketch instances, so no need to configure it.
|
|
||||||
* @name MementoCanvas
|
|
||||||
* @class
|
|
||||||
* @version 1.2
|
|
||||||
* @date 28 Nov 2016
|
|
||||||
* @return Object
|
|
||||||
* @example
|
|
||||||
* var mc = new MementoCanvas( $('canvas-selector') );
|
|
||||||
*/
|
|
||||||
var MementoCanvas = function($canvas) {
|
|
||||||
|
|
||||||
// Private stuff //////////////////////////////////////////////////////////
|
/**
|
||||||
|
* This class implements the <a href="https://en.wikipedia.org/wiki/Memento_pattern">Memento pattern</a>
|
||||||
|
* and is part of the {@link $.fn.sketchable.plugins.memento} plugin.
|
||||||
|
* @class
|
||||||
|
* @version 2.0
|
||||||
|
* @example
|
||||||
|
* var sketcher = $('canvas').sketchable();
|
||||||
|
* // This is internally done by the plugin, plus some checks:
|
||||||
|
* new MementoCanvas(sketcher);
|
||||||
|
*/
|
||||||
|
function MementoCanvas($instance) {
|
||||||
|
// Begin private stuff.
|
||||||
var stack = [];
|
var stack = [];
|
||||||
var stpos = -1;
|
var stpos = -1;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function prev() {
|
function prev() {
|
||||||
if (stpos > 0) {
|
if (stpos > 0) {
|
||||||
stpos--;
|
stpos--;
|
||||||
|
|
@ -43,7 +36,9 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function next() {
|
function next() {
|
||||||
if (stpos < stack.length - 1) {
|
if (stpos < stack.length - 1) {
|
||||||
stpos++;
|
stpos++;
|
||||||
|
|
@ -54,11 +49,15 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Snashot restorer.
|
||||||
|
* @param {String} snapshot Base64 image.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function restore(snapshot) {
|
function restore(snapshot) {
|
||||||
// Manipulate canvas via jQuery sketchable API.
|
// Manipulate canvas via jQuery sketchable API.
|
||||||
// This way, we don't lose default drawing settings et al.
|
// This way, we don't lose default drawing settings et al.
|
||||||
$canvas.sketchable('handler', function(elem, data){
|
$instance.sketchable('handler', function(elem, data){
|
||||||
//data.sketch.clear().drawImage(snapshot.src);
|
//data.sketch.clear().drawImage(snapshot.src);
|
||||||
// Note: jSketch.drawImage after clear creates some flickering,
|
// Note: jSketch.drawImage after clear creates some flickering,
|
||||||
// so use the native HTMLCanvasElement.drawImage method instead.
|
// so use the native HTMLCanvasElement.drawImage method instead.
|
||||||
|
|
@ -66,11 +65,14 @@
|
||||||
data.sketch.graphics.drawImage(snapshot, 0,0);
|
data.sketch.graphics.drawImage(snapshot, 0,0);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
// Key event manager.
|
* Key event manager.
|
||||||
// Undo: "Ctrl + Z"
|
* - Undo: "Ctrl + Z"
|
||||||
// Redo: "Ctrl + Y" or "Ctrl + Shift + Z"
|
* - Redo: "Ctrl + Y" or "Ctrl + Shift + Z"
|
||||||
// TODO: decouple shortcut definition, perhaps via jquery.hotkeys plugin.
|
* @param {Object} e DOM event.
|
||||||
|
* @private
|
||||||
|
* @todo Decouple shortcut definition, perhaps via jquery.hotkeys plugin.
|
||||||
|
*/
|
||||||
function keyManager(e) {
|
function keyManager(e) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
switch (e.which) {
|
switch (e.which) {
|
||||||
|
|
@ -87,93 +89,82 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Public stuff ///////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Goes back to the last saved state, if available.
|
* Goes back to the last saved state, if available.
|
||||||
* @name undo
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
prev();
|
prev();
|
||||||
$canvas.sketchable('handler', function(elem, data) {
|
$instance.sketchable('handler', function(elem, data) {
|
||||||
if (stack[stpos])
|
if (stack[stpos])
|
||||||
data.strokes = stack[stpos].strokes.slice();
|
data.strokes = stack[stpos].strokes.slice();
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Goes forward to the last saved state, if available.
|
* Goes forward to the last saved state, if available.
|
||||||
* @name redo
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
next();
|
next();
|
||||||
$canvas.sketchable('handler', function(elem, data) {
|
$instance.sketchable('handler', function(elem, data) {
|
||||||
if (stack[stpos])
|
if (stack[stpos])
|
||||||
data.strokes = stack[stpos].strokes.slice();
|
data.strokes = stack[stpos].strokes.slice();
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Resets stack.
|
* Resets stack.
|
||||||
* @name reset
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.reset = function() {
|
this.reset = function() {
|
||||||
stack = [];
|
stack = [];
|
||||||
stpos = -1;
|
stpos = -1;
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Save state.
|
* Save current state.
|
||||||
* @name save
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.save = function() {
|
this.save = function() {
|
||||||
stpos++;
|
stpos++;
|
||||||
if (stpos < stack.length) stack.length = stpos;
|
if (stpos < stack.length) stack.length = stpos;
|
||||||
$canvas.sketchable('handler', function(elem, data) {
|
$instance.sketchable('handler', function(elem, data) {
|
||||||
stack.push({ image: elem[0].toDataURL(), strokes: data.strokes.slice() });
|
stack.push({ image: elem[0].toDataURL(), strokes: data.strokes.slice() });
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Init instance.
|
* Init instance. Currently just (re)attach key event listeners.
|
||||||
* @name init
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.init = function() {
|
this.init = function() {
|
||||||
$(document).off('keypress', keyManager);
|
$(document).off('keypress', keyManager);
|
||||||
$(document).on('keypress', keyManager);
|
$(document).on('keypress', keyManager);
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Destroy instance.
|
* Destroy instance: reset state and remove key event listeners.
|
||||||
* @name destroy
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.destroy = function() {
|
this.destroy = function() {
|
||||||
$(document).off('keypress', keyManager);
|
$(document).off('keypress', keyManager);
|
||||||
this.reset();
|
return this.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind plugin extension ////////////////////////////////////////////////////
|
/**
|
||||||
var namespace = 'sketchable';
|
* Memento plugin constructor for jQuery Sketchable instances.
|
||||||
var plugin = $.fn.sketchable;
|
* @param {Object} $instance - A jQuery Sketchable instance.
|
||||||
var availMethods = plugin('methods');
|
* @memberof $.fn.sketchable.plugins
|
||||||
|
*/
|
||||||
function configure(elem, opts) {
|
$.fn.sketchable.plugins.memento = function($instance) {
|
||||||
var options = $.extend(true, {}, plugin.defaults, opts);
|
var config = $instance.sketchable('config');
|
||||||
// Actually this plugin is singleton, so exit early.
|
|
||||||
if (!options.interactive) return opts;
|
|
||||||
|
|
||||||
var callbacks = {
|
var callbacks = {
|
||||||
init: function(elem, data) {
|
|
||||||
data.memento = new MementoCanvas(elem);
|
|
||||||
data.memento.save();
|
|
||||||
data.memento.init();
|
|
||||||
},
|
|
||||||
clear: function(elem, data) {
|
clear: function(elem, data) {
|
||||||
data.memento.reset();
|
data.memento.reset();
|
||||||
data.memento.save();
|
|
||||||
},
|
},
|
||||||
mouseup: function(elem, data, evt) {
|
mouseup: function(elem, data, evt) {
|
||||||
data.memento.save();
|
data.memento.save();
|
||||||
|
|
@ -185,62 +176,65 @@
|
||||||
|
|
||||||
// A helper function to override user-defined event listeners.
|
// A helper function to override user-defined event listeners.
|
||||||
function override(ev) {
|
function override(ev) {
|
||||||
if (options && options.events && typeof options.events[ev] === 'function') {
|
// Flag event override so that it doesn't get fired more than once.
|
||||||
var fn = options.events[ev];
|
if (config.options.$$bound) return;
|
||||||
options.events[ev] = function() {
|
config.options.$$bound = true;
|
||||||
|
|
||||||
|
if (config.options.events && typeof config.options.events[ev] === 'function') {
|
||||||
|
// User has defined this event, so wrap it.
|
||||||
|
var fn = config.options.events[ev];
|
||||||
|
config.options.events[ev] = function() {
|
||||||
// Exec original function first, then exec our callback.
|
// Exec original function first, then exec our callback.
|
||||||
var args = Array.prototype.slice.call(arguments, 0);
|
fn.apply($instance, arguments);
|
||||||
fn.apply(elem, args);
|
callbacks[ev].apply($instance, arguments);
|
||||||
callbacks[ev].apply(elem, args);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
options.events[ev] = callbacks[ev];
|
// User has not defined this event, so attach our callback.
|
||||||
|
config.options.events[ev] = callbacks[ev];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event order matters.
|
// Note: the init event is used to create sketchable instances,
|
||||||
// Init must go first, since it's called when instantiating the plugin.
|
// therefore it should NOT be overriden.
|
||||||
var events = 'init mouseup clear destroy'.split(' ');
|
var events = 'mouseup clear destroy'.split(' ');
|
||||||
for (var i = 0; i < events.length; i++) {
|
for (var i = 0; i < events.length; i++) {
|
||||||
override(events[i]);
|
override(events[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose public API for jquery.sketchable plugin.
|
// Expose public API: all sketchable instances will have these methods.
|
||||||
$.extend(availMethods, {
|
$.extend($.fn.sketchable.api, {
|
||||||
|
/**
|
||||||
|
* Goes back to the previous CANVAS state, if available.
|
||||||
|
* @memberof $.fn.sketchable
|
||||||
|
* @example $('canvas').sketchable('undo');
|
||||||
|
*/
|
||||||
undo: function() {
|
undo: function() {
|
||||||
var elem = $(this), data = elem.data(namespace);
|
var elem = $(this), data = elem.data(namespace);
|
||||||
data.memento.undo();
|
data.memento.undo();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Goes forward to the previous CANVAS state, if available.
|
||||||
|
* @memberof $.fn.sketchable
|
||||||
|
* @example $('canvas').sketchable('redo');
|
||||||
|
*/
|
||||||
redo: function() {
|
redo: function() {
|
||||||
var elem = $(this), data = elem.data(namespace);
|
var elem = $(this), data = elem.data(namespace);
|
||||||
data.memento.redo();
|
data.memento.redo();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Save a snapshot of the current CANVAS status.
|
||||||
|
* @memberof $.fn.sketchable
|
||||||
|
* @example $('canvas').sketchable('save');
|
||||||
|
*/
|
||||||
save: function() {
|
save: function() {
|
||||||
var elem = $(this), data = elem.data(namespace);
|
var elem = $(this), data = elem.data(namespace);
|
||||||
data.memento.save();
|
data.memento.save();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return options;
|
// Initialize plugin here.
|
||||||
};
|
config.memento = new MementoCanvas($instance);
|
||||||
|
config.memento.init().save();
|
||||||
/**
|
|
||||||
* Creates a new memento-capable jQuery.sketchable object.
|
|
||||||
* @param {String|Object} method name of the method to invoke,
|
|
||||||
* or a configuration object.
|
|
||||||
* @return jQuery
|
|
||||||
* @class
|
|
||||||
* @example
|
|
||||||
* $(selector).sketchable();
|
|
||||||
* $(selector).sketchable({interactive:false});
|
|
||||||
*/
|
|
||||||
var initfn = availMethods.init;
|
|
||||||
availMethods.init = function(opts) {
|
|
||||||
return this.each(function() {
|
|
||||||
var elem = $(this);
|
|
||||||
var conf = configure(elem, opts);
|
|
||||||
initfn.call(elem, conf);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
|
||||||
17
jsketch.js
17
jsketch.js
|
|
@ -2,6 +2,7 @@
|
||||||
* jSketch 0.9 | Luis A. Leiva | MIT license
|
* jSketch 0.9 | Luis A. Leiva | MIT license
|
||||||
* A simple JavaScript library for drawing facilities on HTML5 canvas.
|
* A simple JavaScript library for drawing facilities on HTML5 canvas.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple JavaScript library for drawing facilities on HTML5 canvas.
|
* A simple JavaScript library for drawing facilities on HTML5 canvas.
|
||||||
* This class is mostly a wrapper for the HTML5 canvas API with some syntactic sugar,
|
* This class is mostly a wrapper for the HTML5 canvas API with some syntactic sugar,
|
||||||
|
|
@ -16,7 +17,7 @@
|
||||||
* var canvas2 = document.getElementById('bar');
|
* var canvas2 = document.getElementById('bar');
|
||||||
* // Instantiate once, reuse everywhere.
|
* // Instantiate once, reuse everywhere.
|
||||||
* var brush = new jSketch(canvas1).lineStyle('red').moveTo(50,50).lineTo(10,10).stroke();
|
* var brush = new jSketch(canvas1).lineStyle('red').moveTo(50,50).lineTo(10,10).stroke();
|
||||||
* // Actually, .moveTo(50,50).lineTo(10,10) can be just .line(50,50, 10,10)
|
* // Actually, `.moveTo(50,50).lineTo(10,10)` can be just `.line(50,50, 10,10)`.
|
||||||
* // Switching between contexts removes the need of having to reinstantiate the jSketch class.
|
* // Switching between contexts removes the need of having to reinstantiate the jSketch class.
|
||||||
* brush.context(canvas2).beginFill('#5F7').fillCircle(30,30,8).endFill();
|
* brush.context(canvas2).beginFill('#5F7').fillCircle(30,30,8).endFill();
|
||||||
*/
|
*/
|
||||||
|
|
@ -110,7 +111,7 @@
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sets the background color of canvas.
|
* Sets the background color of canvas.
|
||||||
* @param {Number|String} color - An HTML color.
|
* @param {String} color - An HTML color.
|
||||||
* @return jSketch
|
* @return jSketch
|
||||||
* @memberof jSketch
|
* @memberof jSketch
|
||||||
*/
|
*/
|
||||||
|
|
@ -124,7 +125,7 @@
|
||||||
* Shortcut for setting the size + background color.
|
* Shortcut for setting the size + background color.
|
||||||
* @param {Number} width - New canvas width.
|
* @param {Number} width - New canvas width.
|
||||||
* @param {Number} height - New canvas width.
|
* @param {Number} height - New canvas width.
|
||||||
* @param {Number|String} bgcolor - An HTML color.
|
* @param {String} bgcolor - An HTML color.
|
||||||
* @return jSketch
|
* @return jSketch
|
||||||
* @memberof jSketch
|
* @memberof jSketch
|
||||||
*/
|
*/
|
||||||
|
|
@ -134,7 +135,7 @@
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sets the fill color.
|
* Sets the fill color.
|
||||||
* @param {Number|String} color - An HTML color.
|
* @param {String} color - An HTML color.
|
||||||
* @return jSketch
|
* @return jSketch
|
||||||
* @memberof jSketch
|
* @memberof jSketch
|
||||||
*/
|
*/
|
||||||
|
|
@ -154,7 +155,7 @@
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sets the line style.
|
* Sets the line style.
|
||||||
* @param {Number|String} color - An HTML color.
|
* @param {String} color - An HTML color.
|
||||||
* @param {Number} thickness - Line thickness.
|
* @param {Number} thickness - Line thickness.
|
||||||
* @param {String} capStyle - Style of line cap.
|
* @param {String} capStyle - Style of line cap.
|
||||||
* @param {String} joinStyle - Style of line join.
|
* @param {String} joinStyle - Style of line join.
|
||||||
|
|
@ -347,7 +348,7 @@
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sets brush to eraser mode.
|
* Sets brush to eraser mode.
|
||||||
* @param {Number} [brushSize] - Brush size.
|
* @param {Number} [brushSize] - Brush size. Default: 15.
|
||||||
* @return jSketch
|
* @return jSketch
|
||||||
* @memberof jSketch
|
* @memberof jSketch
|
||||||
*/
|
*/
|
||||||
|
|
@ -359,7 +360,7 @@
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sets brush to pencil mode.
|
* Sets brush to pencil mode.
|
||||||
* @param {Number} [brushSize] - Brush size.
|
* @param {Number} [brushSize] - Brush size. Default: 2.
|
||||||
* @return jSketch
|
* @return jSketch
|
||||||
* @memberof jSketch
|
* @memberof jSketch
|
||||||
*/
|
*/
|
||||||
|
|
@ -422,7 +423,7 @@
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Draws an image.
|
* Draws an image.
|
||||||
* @param {Number} src - Image source path.
|
* @param {String} src - Image source path.
|
||||||
* @param {Number} [x] - Horizontal coordinate.
|
* @param {Number} [x] - Horizontal coordinate.
|
||||||
* @param {Number} [y] - Vertical coordinate.
|
* @param {Number} [y] - Vertical coordinate.
|
||||||
* @return jSketch
|
* @return jSketch
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "jsketch",
|
"name": "jsketch",
|
||||||
"version": "1.8.0",
|
"version": "2.0.0",
|
||||||
"description": "jSketch drawing lib",
|
"description": "jSketch drawing lib",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dist": "grunt",
|
||||||
|
"docs": "jsdoc --debug -c jsdoc.json && cp -rn figs/ docs/ || echo [sudo] npm i -g jsdoc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
346
sketchable.js
346
sketchable.js
|
|
@ -1,88 +1,86 @@
|
||||||
/*!
|
/*!
|
||||||
* sketchable | v1.8 | Luis A. Leiva | MIT license
|
* Sketchable | v2.0 | Luis A. Leiva | MIT license
|
||||||
* A plugin for the jSketch drawing library.
|
* A plugin for the jSketch drawing library.
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
Requires sketchable.utils.js to be loaded first.
|
|
||||||
globals: Event, dataBind, deepExtend.
|
|
||||||
*/
|
|
||||||
;(function(window){
|
|
||||||
// Custom namespace ID.
|
|
||||||
var _ns = 'sketchable';
|
|
||||||
/**
|
|
||||||
* Creates a <tt>sketchable</tt> instance.
|
|
||||||
* This is a plugin for the <tt>jSketch</tt> drawing class.
|
|
||||||
* @param {String|Object} method - Method to invoke, or a configuration object.
|
|
||||||
* @return jSketchable
|
|
||||||
* @class
|
|
||||||
* @version 1.8
|
|
||||||
* @date 9 Jul 2014
|
|
||||||
* @author Luis A. Leiva
|
|
||||||
* @license MIT license
|
|
||||||
* @example
|
|
||||||
* var canvas = document.getElementById('foo');
|
|
||||||
* var sketcher = new Sketchable(canvas, {interactive:false});
|
|
||||||
* @see methods
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} elem - MUST be a DOM element
|
|
||||||
* @param {Object} options - Configuration
|
|
||||||
*/
|
|
||||||
var jSketchable = function(elem, options) {
|
|
||||||
return new Sketchable(elem, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
var Sketchable = function(elem, options) {
|
// XXX: Requires `sketchable.utils.js` to be loaded first.
|
||||||
// Although discouraged, we can instantiate the class without arguments.
|
|
||||||
if (!elem) return;
|
/* eslint-env browser */
|
||||||
|
/* global Event, dataBind, deepExtend */
|
||||||
|
;(function(window) {
|
||||||
|
|
||||||
|
// Custom namespace ID, for private data bindind.
|
||||||
|
var namespace = 'sketchable';
|
||||||
|
// Convenient shortcut.
|
||||||
|
var document = window.document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin: make CANVAS elements drawable.<br>
|
||||||
|
* Contrary to the jQuery version, only one element can be passed in at a time.
|
||||||
|
* @param {Object|Strig} elem - DOM element or selector.
|
||||||
|
* @param {Object} [options] - Configuration (default: {@link Sketchable#defaults}).
|
||||||
|
* @class
|
||||||
|
* @global
|
||||||
|
* @version 1.9
|
||||||
|
* @author Luis A. Leiva
|
||||||
|
* @license MIT
|
||||||
|
* @example
|
||||||
|
* // Passing a DOM element:
|
||||||
|
* var sketcher1 = new Sketchable(document.getElementById('foo'));
|
||||||
|
* // Passing a selector:
|
||||||
|
* var sketcher2 = new Sketchable('#foo');
|
||||||
|
* // With custom configuration:
|
||||||
|
* var sketcher2 = new Sketchable('#foo', { multitouch:false });
|
||||||
|
* @see Sketchable#defaults
|
||||||
|
*/
|
||||||
|
function Sketchable(elem, options) {
|
||||||
|
if (!elem) throw new Error('Sketchable requires a DOM element.');
|
||||||
|
if (typeof elem === 'string') elem = document.querySelector(elem);
|
||||||
|
// Save a pointer to the DOM element to emulate jQuery capability.
|
||||||
this.elem = elem;
|
this.elem = elem;
|
||||||
// We can pass default setup values.
|
// Instance methods are chainable.
|
||||||
if (typeof options === 'undefined') options = {};
|
|
||||||
// Instantiate the class.
|
|
||||||
return this.init(options);
|
return this.init(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* jSketchable methods (publicly extensible).
|
* Sketchable prototype.
|
||||||
* @ignore
|
* @namespace Sketchable.prototype
|
||||||
* @memberof jSketchable
|
* @static
|
||||||
* @see jSketchable
|
|
||||||
*/
|
*/
|
||||||
jSketchable.fn = Sketchable.prototype = {
|
Sketchable.prototype = {
|
||||||
/**
|
/**
|
||||||
* Initializes the selected objects.
|
* Initialize the selected CANVAS elements.
|
||||||
* @param {Object} opts plugin configuration (see defaults).
|
* @param {Object} [options] - Configuration (default: {@link Sketchable#defaults}).
|
||||||
* @return jSketchable
|
* @return Sketchable
|
||||||
|
* @memberof Sketchable
|
||||||
|
* @protected
|
||||||
* @ignore
|
* @ignore
|
||||||
* @namespace methods.init
|
|
||||||
* @example $(selector).sketchable();
|
|
||||||
*/
|
*/
|
||||||
init: function(opts) {
|
init: function(options) {
|
||||||
// Options will be available for all plugin methods.
|
// Options will be available for all plugin methods.
|
||||||
var options = deepExtend(jSketchable.fn.defaults, opts || {});
|
var options = deepExtend({}, Sketchable.prototype.defaults, options || {});
|
||||||
var elem = this.elem, data = dataBind(elem)[_ns];
|
var elem = this.elem, data = dataBind(elem)[namespace];
|
||||||
// Check if element is not initialized yet.
|
// Check if element is not initialized yet.
|
||||||
if (!data) {
|
if (!data) {
|
||||||
// Attach event listeners.
|
// Attach event listeners.
|
||||||
if (options.interactive) {
|
Event.add(elem, 'mousedown', mousedownHandler);
|
||||||
Event.add(elem, 'mousedown', mousedownHandler);
|
Event.add(elem, 'mousemove', mousemoveHandler);
|
||||||
Event.add(elem, 'mousemove', mousemoveHandler);
|
Event.add(elem, 'mouseup', mouseupHandler);
|
||||||
Event.add(elem, 'mouseup', mouseupHandler);
|
Event.add(elem, 'touchstart', touchdownHandler);
|
||||||
Event.add(elem, 'touchstart', touchdownHandler);
|
Event.add(elem, 'touchmove', touchmoveHandler);
|
||||||
Event.add(elem, 'touchmove', touchmoveHandler);
|
Event.add(elem, 'touchend', touchupHandler);
|
||||||
Event.add(elem, 'touchend', touchupHandler);
|
// Fix unwanted highlight "bug".
|
||||||
// Fix Chrome "bug".
|
elem.onselectstart = function() { return false };
|
||||||
this.onselectstart = function(){ return false };
|
|
||||||
}
|
|
||||||
if (options.cssCursors) {
|
if (options.cssCursors) {
|
||||||
// Visually indicate whether this element is interactive or not.
|
// Visually indicate whether this element is interactive or not.
|
||||||
elem.style.cursor = options.interactive ? 'pointer' : 'not-allowed';
|
elem.style.cursor = options.interactive ? 'pointer' : 'not-allowed';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sketch = new jSketch(elem, options.graphics);
|
var sketch = new jSketch(elem, options.graphics);
|
||||||
// Reconfigure element data.
|
// Reconfigure element data.
|
||||||
dataBind(elem)[_ns] = {
|
dataBind(elem)[namespace] = data = {
|
||||||
// All strokes will be stored here.
|
// All strokes will be stored here.
|
||||||
strokes: [],
|
strokes: [],
|
||||||
// This will store one stroke per touching finger.
|
// This will store one stroke per touching finger.
|
||||||
|
|
@ -91,75 +89,97 @@
|
||||||
timestamp: (new Date).getTime(),
|
timestamp: (new Date).getTime(),
|
||||||
// Save a pointer to the drawing canvas (jSketch instance).
|
// Save a pointer to the drawing canvas (jSketch instance).
|
||||||
sketch: sketch,
|
sketch: sketch,
|
||||||
|
// Save a pointer to the drawing canvas (jSketch instance).
|
||||||
|
sketchable: this,
|
||||||
// Save also a pointer to the given options.
|
// Save also a pointer to the given options.
|
||||||
options: options
|
options: options
|
||||||
};
|
};
|
||||||
|
|
||||||
// Trigger init event.
|
// Trigger init event.
|
||||||
if (typeof options.events.init === 'function') {
|
if (typeof options.events.init === 'function') {
|
||||||
options.events.init(elem, dataBind(elem)[_ns]);
|
options.events.init(elem, data);
|
||||||
|
}
|
||||||
|
// Initialize plugins.
|
||||||
|
for (var name in this.plugins) {
|
||||||
|
this.plugins[name](this);
|
||||||
}
|
}
|
||||||
// Make methods chainable.
|
// Make methods chainable.
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Changes config on the fly of an existing sketchable element.
|
* Change configuration of an existing Sketchable instance.
|
||||||
* @param {Object} opts - Plugin configuration (see defaults).
|
* @param {Object} [options] - Configuration (default: {@link Sketchable#defaults}).
|
||||||
* @return jQuery
|
* @return Sketchable
|
||||||
* @namespace methods.config
|
* @memberof Sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('config', { interactive: false }); // Later on:
|
* var sketcher = new Sketchable('canvas').config({ interactive: false });
|
||||||
* $(selector).sketchable('config', { interactive: true });
|
* // Update later on:
|
||||||
|
* sketcher.config({ interactive: true });
|
||||||
*/
|
*/
|
||||||
config: function(opts) {
|
config: function(options) {
|
||||||
var elem = this.elem, data = dataBind(elem)[_ns];
|
var elem = this.elem, data = dataBind(elem)[namespace];
|
||||||
data.options = deepExtend(jSketchable.fn.defaults, opts || {});
|
if (options) { // setter
|
||||||
return this;
|
data.options = deepExtend({}, Sketchable.prototype.defaults, options || {});
|
||||||
|
return this;
|
||||||
|
} else { // getter
|
||||||
|
return data;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Gets/Sets drawing data strokes sequence.
|
* Get/Set drawing data strokes sequence.
|
||||||
* @param {Array} arr - Multidimensional array of [x,y,time,status] tuples; status = 0 (pen down) or 1 (pen up).
|
* @param {Array} [arr] - Multidimensional array of [x,y,time,status] tuples; status = 0 (pen down) or 1 (pen up).
|
||||||
* @return Strokes object on get, jSketchable on set (with the new data attached)
|
* @return Strokes object on get, Sketchable instance on set (with the new data attached).
|
||||||
* @namespace methods.strokes
|
* @memberof Sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('strokes'); // Getter
|
* // Getter: read associated strokes.
|
||||||
* $(selector).sketchable('strokes', [ [arr1], ..., [arrN] ]); // Setter
|
* new Sketchable('canvas').strokes();
|
||||||
|
* // Setter: replace associated strokes.
|
||||||
|
* new Sketchable('canvas').strokes([ [arr1], ..., [arrN] ]);
|
||||||
*/
|
*/
|
||||||
strokes: function(arr) {
|
strokes: function(arr) {
|
||||||
var elem = this.elem;
|
var elem = this.elem;
|
||||||
if (arr) { // setter
|
if (arr) { // setter
|
||||||
var data = dataBind(elem)[_ns];
|
var data = dataBind(elem)[namespace];
|
||||||
data.strokes = arr;
|
data.strokes = arr;
|
||||||
return this;
|
return this;
|
||||||
} else { // getter
|
} else { // getter
|
||||||
var data = dataBind(elem)[_ns];
|
var data = dataBind(elem)[namespace];
|
||||||
return data.strokes;
|
return data.strokes;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Allows low-level manipulation of the sketchable canvas.
|
* Allows low-level manipulation of the sketchable canvas.
|
||||||
* @param {Function} callback - Callback function, invoked with 2 arguments: elem (jSketchable element) and data (jSketchable element data).
|
* @param {Function} callback - Callback function, invoked with 2 arguments: elem (CANVAS element) and data (private element data).
|
||||||
* @return jSketchable
|
* @return Sketchable
|
||||||
* @namespace methods.handler
|
* @memberof Sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('handler', function(elem, data){
|
* new Sketchable('canvas').handler(function(elem, data) {
|
||||||
* // do something with elem or data
|
* // do something with elem or data
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
handler: function(callback) {
|
handler: function(callback) {
|
||||||
var elem = this.elem, data = dataBind(elem)[_ns];
|
var elem = this.elem, data = dataBind(elem)[namespace];
|
||||||
|
|
||||||
callback(elem, data);
|
callback(elem, data);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Clears canvas (together with strokes data).
|
* Clears canvas <b>together with</b> associated strokes data.
|
||||||
* If you need to clear canvas only, just invoke <tt>data.sketch.clear()</tt> via <tt>$(selector).sketchable('handler')</tt>.
|
* @see Sketchable.handler
|
||||||
* @see methods.handler
|
* @return Sketchable
|
||||||
* @return jSketchable
|
* @memberof Sketchable
|
||||||
* @namespace methods.clear
|
* @example
|
||||||
* @example $(selector).sketchable('clear');
|
* var sketcher = new Sketchable('canvas');
|
||||||
|
* // This will remove strokes data as well.
|
||||||
|
* sketcher.clear();
|
||||||
|
* // If you only need to clear the canvas, just do:
|
||||||
|
* sketcher.handler(function(elem, data) {
|
||||||
|
* data.sketch.clear();
|
||||||
|
* });
|
||||||
*/
|
*/
|
||||||
clear: function() {
|
clear: function() {
|
||||||
var elem = this.elem, data = dataBind(elem)[_ns], options = data.options;
|
var elem = this.elem, data = dataBind(elem)[namespace], options = data.options;
|
||||||
|
|
||||||
data.sketch.clear();
|
data.sketch.clear();
|
||||||
data.strokes = [];
|
data.strokes = [];
|
||||||
data.coords = {};
|
data.coords = {};
|
||||||
|
|
@ -170,17 +190,20 @@
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Reinitializes a sketchable canvas with given opts.
|
* Reinitializes a sketchable canvas with given configuration options.
|
||||||
* @param {Object} opts - Configuration options.
|
* @param {Object} [options] - Configuration (default: {@link Sketchable#defaults}).
|
||||||
* @return jSketchable
|
* @return Sketchable
|
||||||
* @namespace methods.reset
|
* @memberof Sketchable
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable('reset');
|
* // Reset default state.
|
||||||
* $(selector).sketchable('reset', {interactive:false});
|
* new Sketchable('canvas').reset();
|
||||||
|
* // Reset with custom configuration.
|
||||||
|
* new Sketchable('canvas').reset({ interactive:false });
|
||||||
*/
|
*/
|
||||||
reset: function(opts) {
|
reset: function(options) {
|
||||||
var elem = this.elem, data = dataBind(elem)[_ns], options = data.options;
|
var elem = this.elem, data = dataBind(elem)[namespace], options = data.options;
|
||||||
this.destroy().init(opts);
|
|
||||||
|
this.destroy().init(options);
|
||||||
|
|
||||||
if (typeof options.events.reset === 'function') {
|
if (typeof options.events.reset === 'function') {
|
||||||
options.events.reset(elem, data);
|
options.events.reset(elem, data);
|
||||||
|
|
@ -188,22 +211,24 @@
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Destroys sketchable canvas (together with strokes data and events).
|
* Destroys sketchable canvas, together with strokes data and associated events.
|
||||||
* @return jSketchable
|
* @return Sketchable
|
||||||
* @namespace methods.destroy
|
* @memberof Sketchable
|
||||||
* @example $(selector).sketchable('destroy');
|
* @example
|
||||||
|
* // This will leave the canvas element intact.
|
||||||
|
* new Sketchable('canvas').destroy();
|
||||||
*/
|
*/
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
var elem = this.elem, data = dataBind(elem)[_ns], options = data.options;
|
var elem = this.elem, data = dataBind(elem)[namespace], options = data.options;
|
||||||
if (options.interactive) {
|
|
||||||
Event.remove(elem, 'mouseup', mouseupHandler);
|
Event.remove(elem, 'mouseup', mouseupHandler);
|
||||||
Event.remove(elem, 'mousemove', mousemoveHandler);
|
Event.remove(elem, 'mousemove', mousemoveHandler);
|
||||||
Event.remove(elem, 'mousedown', mousedownHandler);
|
Event.remove(elem, 'mousedown', mousedownHandler);
|
||||||
Event.remove(elem, 'touchstart', touchdownHandler);
|
Event.remove(elem, 'touchstart', touchdownHandler);
|
||||||
Event.remove(elem, 'touchmove', touchmoveHandler);
|
Event.remove(elem, 'touchmove', touchmoveHandler);
|
||||||
Event.remove(elem, 'touchend', touchupHandler);
|
Event.remove(elem, 'touchend', touchupHandler);
|
||||||
}
|
|
||||||
dataBind(elem)[_ns] = null;
|
dataBind(elem)[namespace] = null;
|
||||||
|
|
||||||
if (typeof options.events.destroy === 'function') {
|
if (typeof options.events.destroy === 'function') {
|
||||||
options.events.destroy(elem, data);
|
options.events.destroy(elem, data);
|
||||||
|
|
@ -214,26 +239,55 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default configuration.
|
* Plugins store.
|
||||||
* Note that mouse* callbacks are triggered only if <tt>interactive</tt> is set to <tt>true</tt>.
|
* @namespace Sketchable.prototype.plugins
|
||||||
* @name defaults
|
* @type {Object}
|
||||||
* @default
|
* @static
|
||||||
* @memberof $.fn.sketchable
|
|
||||||
* @example
|
* @example
|
||||||
* $(selector).sketchable({
|
* // All plugins are created after instance initialization:
|
||||||
|
* Sketchable.prototype.plugins['your-awesome-plugin'] = function(instance) {
|
||||||
|
* // Do something with the Sketchable instance.
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
Sketchable.prototype.plugins = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default configuration.
|
||||||
|
* Note that `events.mouse*` callbacks are triggered only if <tt>interactive</tt> is set to <tt>true</tt>.
|
||||||
|
* @namespace Sketchable.prototype.defaults
|
||||||
|
* @type {Object}
|
||||||
|
* @static
|
||||||
|
* @example
|
||||||
|
* // The following is the default configuration:
|
||||||
|
* new Sketchable('canvas', {
|
||||||
* interactive: true,
|
* interactive: true,
|
||||||
* mouseupMovements: false,
|
* mouseupMovements: false,
|
||||||
* relTimestamps: false,
|
* relTimestamps: false,
|
||||||
* multitouch: false,
|
* multitouch: false,
|
||||||
* cssCursors: true,
|
* cssCursors: true,
|
||||||
|
* // Event hooks.
|
||||||
* events: {
|
* events: {
|
||||||
* init: function(elem, data){ },
|
* init: function(elem, data) {
|
||||||
* clear: function(elem, data){ },
|
* // Called when the Sketchable instance is created.
|
||||||
* destroy: function(elem, data){ },
|
* },
|
||||||
* mousedown: function(elem, data, evt){ },
|
* destroy: function(elem, data) {
|
||||||
* mousemove: function(elem, data, evt){ },
|
* // Called when the Sketchable instance is destroyed.
|
||||||
* mouseup: function(elem, data, evt){ },
|
* },
|
||||||
|
* clear: function(elem, data) {
|
||||||
|
* // Called when the canvas is cleared.
|
||||||
|
* // This event includes clearing strokes data, too.
|
||||||
|
* },
|
||||||
|
* mousedown: function(elem, data, evt) {
|
||||||
|
* // Called when the user clicks or taps on the canvas.
|
||||||
|
* },
|
||||||
|
* mousemove: function(elem, data, evt) {
|
||||||
|
* // Called when the user moves the mouse or finger over the canvas.
|
||||||
|
* },
|
||||||
|
* mouseup: function(elem, data, evt) {
|
||||||
|
* // Called when the user lifts the mouse or finger off the canvas.
|
||||||
|
* },
|
||||||
* },
|
* },
|
||||||
|
* // Drawing options, to be used in jSketch lib.
|
||||||
* graphics: {
|
* graphics: {
|
||||||
* firstPointSize: 3,
|
* firstPointSize: 3,
|
||||||
* lineWidth: 3,
|
* lineWidth: 3,
|
||||||
|
|
@ -245,7 +299,7 @@
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
jSketchable.fn.defaults = {
|
Sketchable.prototype.defaults = {
|
||||||
// In interactive mode, it's possible to draw via mouse/pen/touch input.
|
// In interactive mode, it's possible to draw via mouse/pen/touch input.
|
||||||
interactive: true,
|
interactive: true,
|
||||||
// Indicate whether non-drawing strokes should be registered as well.
|
// Indicate whether non-drawing strokes should be registered as well.
|
||||||
|
|
@ -257,15 +311,16 @@
|
||||||
multitouch: true,
|
multitouch: true,
|
||||||
// Display CSS cursors, mainly to indicate whether the element is interactive or not.
|
// Display CSS cursors, mainly to indicate whether the element is interactive or not.
|
||||||
cssCursors: true,
|
cssCursors: true,
|
||||||
// Event callbacks.
|
// Event hooks.
|
||||||
events: {
|
events: {
|
||||||
// init: function(elem, data){ },
|
// init: function(elem, data) { },
|
||||||
// clear: function(elem, data){ },
|
// clear: function(elem, data) { },
|
||||||
// destroy: function(elem, data){ },
|
// destroy: function(elem, data) { },
|
||||||
// mousedown: function(elem, data, evt){ },
|
// mousedown: function(elem, data, evt) { },
|
||||||
// mousemove: function(elem, data, evt){ },
|
// mousemove: function(elem, data, evt) { },
|
||||||
// mouseup: function(elem, data, evt){ },
|
// mouseup: function(elem, data, evt) { },
|
||||||
},
|
},
|
||||||
|
// Drawing options, to be used in jSketch lib.
|
||||||
graphics: {
|
graphics: {
|
||||||
firstPointSize: 3,
|
firstPointSize: 3,
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
|
|
@ -277,6 +332,9 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function offset(el) {
|
function offset(el) {
|
||||||
var box = el.getBoundingClientRect();
|
var box = el.getBoundingClientRect();
|
||||||
var body = document.body;
|
var body = document.body;
|
||||||
|
|
@ -348,9 +406,12 @@
|
||||||
upHandler(e);
|
upHandler(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function execTouchEvent(e, callback) {
|
function execTouchEvent(e, callback) {
|
||||||
var elem = e.target, data = dataBind(elem)[_ns], options = data.options;
|
var elem = e.target, data = dataBind(elem)[namespace], options = data.options;
|
||||||
var touches = e.changedTouches;
|
var touches = e.touches;
|
||||||
if (options.multitouch) {
|
if (options.multitouch) {
|
||||||
for (var i = 0; i < touches.length; i++) {
|
for (var i = 0; i < touches.length; i++) {
|
||||||
var touch = touches[i];
|
var touch = touches[i];
|
||||||
|
|
@ -364,6 +425,7 @@
|
||||||
touch.type = e.type;
|
touch.type = e.type;
|
||||||
callback(touch);
|
callback(touch);
|
||||||
}
|
}
|
||||||
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -398,7 +460,7 @@
|
||||||
if (Event.isRightClick(e)) return false;
|
if (Event.isRightClick(e)) return false;
|
||||||
|
|
||||||
var idx = e.identifier || 0;
|
var idx = e.identifier || 0;
|
||||||
var elem = e.target, data = dataBind(elem)[_ns], options = data.options;
|
var elem = e.target, data = dataBind(elem)[namespace], options = data.options;
|
||||||
// Exit early if interactivity is disabled.
|
// Exit early if interactivity is disabled.
|
||||||
if (!options.interactive) return;
|
if (!options.interactive) return;
|
||||||
|
|
||||||
|
|
@ -429,7 +491,8 @@
|
||||||
*/
|
*/
|
||||||
function moveHandler(e) {
|
function moveHandler(e) {
|
||||||
var idx = e.identifier || 0;
|
var idx = e.identifier || 0;
|
||||||
var elem = e.target, data = dataBind(elem)[_ns], options = data.options;
|
var elem = e.target, data = dataBind(elem)[namespace], options = data.options;
|
||||||
|
|
||||||
if (!options.interactive) return;
|
if (!options.interactive) return;
|
||||||
//if (!options.mouseupMovements && !data.sketch.isDrawing) return;
|
//if (!options.mouseupMovements && !data.sketch.isDrawing) return;
|
||||||
// This would grab all penup strokes AFTER drawing something on the canvas for the first time.
|
// This would grab all penup strokes AFTER drawing something on the canvas for the first time.
|
||||||
|
|
@ -452,7 +515,8 @@
|
||||||
*/
|
*/
|
||||||
function upHandler(e) {
|
function upHandler(e) {
|
||||||
var idx = e.identifier || 0;
|
var idx = e.identifier || 0;
|
||||||
var elem = e.target, data = dataBind(elem)[_ns], options = data.options;
|
var elem = e.target, data = dataBind(elem)[namespace], options = data.options;
|
||||||
|
|
||||||
if (!options.interactive) return;
|
if (!options.interactive) return;
|
||||||
|
|
||||||
data.sketch.isDrawing = false;
|
data.sketch.isDrawing = false;
|
||||||
|
|
@ -465,6 +529,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expose.
|
// Expose.
|
||||||
window.Sketchable = jSketchable;
|
window.Sketchable = Sketchable;
|
||||||
|
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,34 @@
|
||||||
/*!
|
/*!
|
||||||
* Memento plugin for sketchable | v1.2 | Luis A. Leiva | MIT license
|
* Memento plugin for Sketchable | v2.0 | Luis A. Leiva | MIT license
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
Requires sketchable.utils.js to be loaded first.
|
// XXX: Requires `sketchable.utils.js` to be loaded first.
|
||||||
globals: Event, dataBind, deepExtend.
|
|
||||||
*/
|
/* eslint-env browser */
|
||||||
|
/* global Event, dataBind, deepExtend */
|
||||||
;(function(window) {
|
;(function(window) {
|
||||||
|
|
||||||
/**
|
// Custom namespace ID, for private data bindind.
|
||||||
* This plugin implements the <a href="https://en.wikipedia.org/wiki/Memento_pattern">Memento pattern</a>.
|
var namespace = 'sketchable';
|
||||||
* This plugin automatically modifies the jSketch instances, so no need to configure it.
|
|
||||||
* @name MementoCanvas
|
|
||||||
* @class
|
|
||||||
* @version 1.2
|
|
||||||
* @return Object
|
|
||||||
* @example
|
|
||||||
* var mc = new MementoCanvas( $('canvas-selector') );
|
|
||||||
*/
|
|
||||||
var MementoCanvas = function(sketchable) {
|
|
||||||
|
|
||||||
// Private stuff //////////////////////////////////////////////////////////
|
/**
|
||||||
|
* This class implements the <a href="https://en.wikipedia.org/wiki/Memento_pattern">Memento pattern</a>
|
||||||
|
* and is part of the {@link Sketchable.plugins.memento} plugin.
|
||||||
|
* @class
|
||||||
|
* @version 2.0
|
||||||
|
* @example
|
||||||
|
* var sketcher = new Sketchable('canvas');
|
||||||
|
* // This is internally done by the plugin, plus some checks:
|
||||||
|
* new MementoCanvas(sketcher);
|
||||||
|
*/
|
||||||
|
function MementoCanvas(instance) {
|
||||||
|
// Begin private stuff.
|
||||||
var stack = [];
|
var stack = [];
|
||||||
var stpos = -1;
|
var stpos = -1;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function prev() {
|
function prev() {
|
||||||
if (stpos > 0) {
|
if (stpos > 0) {
|
||||||
stpos--;
|
stpos--;
|
||||||
|
|
@ -34,7 +39,9 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function next() {
|
function next() {
|
||||||
if (stpos < stack.length - 1) {
|
if (stpos < stack.length - 1) {
|
||||||
stpos++;
|
stpos++;
|
||||||
|
|
@ -45,11 +52,15 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Snashot restorer.
|
||||||
|
* @param {String} snapshot Base64 image.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
function restore(snapshot) {
|
function restore(snapshot) {
|
||||||
// Manipulate canvas via jQuery sketchable API.
|
// Manipulate canvas via jQuery sketchable API.
|
||||||
// This way, we don't lose default drawing settings et al.
|
// This way, we don't lose default drawing settings et al.
|
||||||
sketchable.handler(function(elem, data){
|
instance.handler(function(elem, data){
|
||||||
//data.sketch.clear().drawImage(snapshot.src);
|
//data.sketch.clear().drawImage(snapshot.src);
|
||||||
// Note: jSketch.drawImage after clear creates some flickering,
|
// Note: jSketch.drawImage after clear creates some flickering,
|
||||||
// so use the native HTMLCanvasElement.drawImage method instead.
|
// so use the native HTMLCanvasElement.drawImage method instead.
|
||||||
|
|
@ -57,11 +68,14 @@
|
||||||
data.sketch.graphics.drawImage(snapshot, 0,0);
|
data.sketch.graphics.drawImage(snapshot, 0,0);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
// Key event manager.
|
* Key event manager.
|
||||||
// Undo: "Ctrl + Z"
|
* - Undo: "Ctrl + Z"
|
||||||
// Redo: "Ctrl + Y" or "Ctrl + Shift + Z"
|
* - Redo: "Ctrl + Y" or "Ctrl + Shift + Z"
|
||||||
// TODO: decouple shortcut definition.
|
* @param {Object} e DOM event.
|
||||||
|
* @private
|
||||||
|
* @todo Decouple shortcut definition.
|
||||||
|
*/
|
||||||
function keyManager(e) {
|
function keyManager(e) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
switch (e.which) {
|
switch (e.which) {
|
||||||
|
|
@ -78,93 +92,82 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Public stuff ///////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Goes back to the last saved state, if available.
|
* Goes back to the last saved state, if available.
|
||||||
* @name undo
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
prev();
|
prev();
|
||||||
sketchable.handler(function(elem, data) {
|
instance.handler(function(elem, data) {
|
||||||
if (stack[stpos])
|
if (stack[stpos])
|
||||||
data.strokes = stack[stpos].strokes.slice();
|
data.strokes = stack[stpos].strokes.slice();
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Goes forward to the last saved state, if available.
|
* Goes forward to the last saved state, if available.
|
||||||
* @name redo
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
next();
|
next();
|
||||||
sketchable.handler(function(elem, data) {
|
instance.handler(function(elem, data) {
|
||||||
if (stack[stpos])
|
if (stack[stpos])
|
||||||
data.strokes = stack[stpos].strokes.slice();
|
data.strokes = stack[stpos].strokes.slice();
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Resets stack.
|
* Resets stack.
|
||||||
* @name reset
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.reset = function() {
|
this.reset = function() {
|
||||||
stack = [];
|
stack = [];
|
||||||
stpos = -1;
|
stpos = -1;
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Save state.
|
* Save current state.
|
||||||
* @name save
|
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.save = function() {
|
this.save = function() {
|
||||||
stpos++;
|
stpos++;
|
||||||
if (stpos < stack.length) stack.length = stpos;
|
if (stpos < stack.length) stack.length = stpos;
|
||||||
sketchable.handler(function(elem, data) {
|
instance.handler(function(elem, data) {
|
||||||
stack.push({ image: elem.toDataURL(), strokes: data.strokes.slice() });
|
stack.push({ image: elem.toDataURL(), strokes: data.strokes.slice() });
|
||||||
});
|
});
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Init instance.
|
* Init instance. Currently just (re)attach key event listeners.
|
||||||
* @name init
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.init = function() {
|
this.init = function() {
|
||||||
Event.remove(document, 'keypress', keyManager);
|
Event.remove(document, 'keypress', keyManager);
|
||||||
Event.add(document, 'keypress', keyManager);
|
Event.add(document, 'keypress', keyManager);
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Destroy instance.
|
* Destroy instance: reset state and remove key event listeners.
|
||||||
* @name destroy
|
* @return {MementoCanvas} Class instance.
|
||||||
* @memberOf MementoCanvas
|
|
||||||
*/
|
*/
|
||||||
this.destroy = function() {
|
this.destroy = function() {
|
||||||
Event.remove(document, 'keypress', keyManager);
|
Event.remove(document, 'keypress', keyManager);
|
||||||
this.reset();
|
return this.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind plugin extension ////////////////////////////////////////////////////
|
/**
|
||||||
var namespace = 'sketchable';
|
* Memento plugin constructor for jQuery Sketchable instances.
|
||||||
var availMethods = Sketchable.fn;
|
* @param {Object} sketchable - An Sketchable instance.
|
||||||
var defaults = Sketchable.fn.defaults;
|
* @memberof Sketchable#plugins
|
||||||
|
*/
|
||||||
function configure(sketchable, opts) {
|
Sketchable.prototype.plugins.memento = function(instance) {
|
||||||
var options = deepExtend({}, defaults, opts);
|
// Access the instance configuration.
|
||||||
// Actually this plugin is singleton, so exit early.
|
var config = instance.config();
|
||||||
if (!options.interactive) return opts;
|
|
||||||
|
|
||||||
var callbacks = {
|
var callbacks = {
|
||||||
init: function(elem, data) {
|
|
||||||
data.memento = new MementoCanvas(sketchable);
|
|
||||||
data.memento.save();
|
|
||||||
data.memento.init();
|
|
||||||
},
|
|
||||||
clear: function(elem, data) {
|
clear: function(elem, data) {
|
||||||
data.memento.reset();
|
data.memento.reset();
|
||||||
data.memento.save();
|
|
||||||
},
|
},
|
||||||
mouseup: function(elem, data, evt) {
|
mouseup: function(elem, data, evt) {
|
||||||
data.memento.save();
|
data.memento.save();
|
||||||
|
|
@ -176,60 +179,62 @@
|
||||||
|
|
||||||
// A helper function to override user-defined event listeners.
|
// A helper function to override user-defined event listeners.
|
||||||
function override(ev) {
|
function override(ev) {
|
||||||
if (options && options.events && typeof options.events[ev] === 'function') {
|
// Flag event override so that it doesn't get fired more than once.
|
||||||
var fn = options.events[ev];
|
if (config.options.$$bound) return;
|
||||||
options.events[ev] = function() {
|
config.options.$$bound = true;
|
||||||
|
|
||||||
|
if (config.options.events && typeof config.options.events[ev] === 'function') {
|
||||||
|
// User has defined this event, so wrap it.
|
||||||
|
var fn = config.options.events[ev];
|
||||||
|
config.options.events[ev] = function() {
|
||||||
// Exec original function first, then exec our callback.
|
// Exec original function first, then exec our callback.
|
||||||
var args = Array.prototype.slice.call(arguments, 0);
|
fn.apply(instance, arguments);
|
||||||
fn.apply(sketchable, args);
|
callbacks[ev].apply(instance, arguments);
|
||||||
callbacks[ev].apply(sketchable, args);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defaults.events[ev] = callbacks[ev];
|
// User has not defined this event, so attach our callback.
|
||||||
|
config.options.events[ev] = callbacks[ev];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event order matters.
|
// Note: the init event is used to create Sketchable instances,
|
||||||
// Init must go first, since it's called when instantiating the plugin.
|
// therefore it should NOT be overriden.
|
||||||
var events = 'init mouseup clear destroy'.split(' ');
|
var events = 'mouseup clear destroy'.split(' ');
|
||||||
for (var i = 0; i < events.length; i++) {
|
for (var i = 0; i < events.length; i++) {
|
||||||
override(events[i]);
|
override(events[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose public API for sketchable plugin.
|
// Expose public API: all Sketchable instances will have these methods.
|
||||||
deepExtend(availMethods, {
|
deepExtend(instance, {
|
||||||
|
/**
|
||||||
|
* Goes back to the previous CANVAS state, if available.
|
||||||
|
* @memberof Sketchable
|
||||||
|
*/
|
||||||
undo: function() {
|
undo: function() {
|
||||||
var elem = this.elem, data = dataBind(elem)[namespace];
|
var elem = this.elem, data = dataBind(elem)[namespace];
|
||||||
data.memento.undo();
|
data.memento.undo();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Goes forward to the previous CANVAS state, if available.
|
||||||
|
* @memberof Sketchable
|
||||||
|
*/
|
||||||
redo: function() {
|
redo: function() {
|
||||||
var elem = this.elem, data = dataBind(elem)[namespace];
|
var elem = this.elem, data = dataBind(elem)[namespace];
|
||||||
data.memento.redo();
|
data.memento.redo();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Save a snapshot of the current CANVAS status.
|
||||||
|
* @memberof Sketchable
|
||||||
|
*/
|
||||||
save: function() {
|
save: function() {
|
||||||
var elem = this.elem, data = dataBind(elem)[namespace];
|
var elem = this.elem, data = dataBind(elem)[namespace];
|
||||||
data.memento.save();
|
data.memento.save();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return options;
|
// Initialize plugin here.
|
||||||
};
|
config.memento = new MementoCanvas(instance);
|
||||||
|
config.memento.init().save();
|
||||||
/**
|
|
||||||
* Creates a new memento-capable sketchable object.
|
|
||||||
* @param {String|Object} method name of the method to invoke,
|
|
||||||
* or a configuration object.
|
|
||||||
* @return Sketchable
|
|
||||||
* @class
|
|
||||||
* @example
|
|
||||||
* $(selector).sketchable();
|
|
||||||
* $(selector).sketchable({interactive:false});
|
|
||||||
*/
|
|
||||||
var initfn = availMethods.init;
|
|
||||||
availMethods.init = function(opts) {
|
|
||||||
// Here `this` is a Sketchable instance.
|
|
||||||
var conf = configure(this, opts);
|
|
||||||
return initfn.call(this, conf);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
/**
|
|
||||||
* Data binding lib.
|
|
||||||
*/
|
|
||||||
(function(){
|
(function(){
|
||||||
var cache = [0],
|
var cache = [0], expando = 'data' + +(new Date);
|
||||||
expando = 'data' + +(new Date);
|
|
||||||
function data(elem) {
|
function data(elem) {
|
||||||
var cacheIndex = elem[expando],
|
var cacheIndex = elem[expando],
|
||||||
nextCacheIndex = cache.length;
|
nextCacheIndex = cache.length;
|
||||||
|
|
@ -13,36 +9,79 @@
|
||||||
}
|
}
|
||||||
return cache[cacheIndex];
|
return cache[cacheIndex];
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Add/Read private data to a DOM element.
|
||||||
|
* @global
|
||||||
|
* @method
|
||||||
|
* @param {Object} elem - DOM element to attach data to.
|
||||||
|
* @example
|
||||||
|
* var elem = document.getElementById('foo');
|
||||||
|
* // Attach private data to element:
|
||||||
|
* dataBind(elem)['some-name'] = { value: 42 };
|
||||||
|
* // Read private data from element:
|
||||||
|
* var dat = dataBind(elem)['some-name'];
|
||||||
|
*/
|
||||||
window.dataBind = data;
|
window.dataBind = data;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event manager.
|
* Event manager.
|
||||||
|
* @global
|
||||||
|
* @module Event
|
||||||
*/
|
*/
|
||||||
var Event = {
|
var Event = {
|
||||||
|
/**
|
||||||
|
* Add event to DOM element.
|
||||||
|
* @memberof module:Event
|
||||||
|
* @param {Object} elem - DOM element.
|
||||||
|
* @param {String} type - Event type.
|
||||||
|
* @param {Function} fn - Callback.
|
||||||
|
* @example
|
||||||
|
* Event.add(document.getElementById('foo'), 'click', function fooClick(evt) {
|
||||||
|
* // Element was clicked.
|
||||||
|
* });
|
||||||
|
*/
|
||||||
add: function(elem, type, fn) {
|
add: function(elem, type, fn) {
|
||||||
if (!elem) return false;
|
if (!elem) return false;
|
||||||
if (elem.addEventListener) { // W3C standard
|
if (elem.addEventListener) { // W3C standard
|
||||||
elem.addEventListener(type, fn, false);
|
elem.addEventListener(type, fn, false);
|
||||||
} else if (elem.attachEvent) { // IE versions
|
} else if (elem.attachEvent) { // Old IE versions
|
||||||
elem.attachEvent("on"+type, fn);
|
elem.attachEvent("on"+type, fn);
|
||||||
} else { // Really old browser
|
} else { // Really old browser
|
||||||
elem[type+fn] = function(){ fn(window.event); };
|
elem[type+fn] = function(){ fn(window.event); };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Remove event from DOM element.
|
||||||
|
* @memberof module:Event
|
||||||
|
* @param {Object} elem - DOM element.
|
||||||
|
* @param {String} type - Event type.
|
||||||
|
* @param {Function} fn - Callback.
|
||||||
|
* @example
|
||||||
|
* // Assuming elemen had the `fooClick` function (see previous example):
|
||||||
|
* Event.remove(document.getElementById('foo'), 'click', fooClick);
|
||||||
|
*/
|
||||||
remove: function(elem, type, fn) {
|
remove: function(elem, type, fn) {
|
||||||
if (!elem) return false;
|
if (!elem) return false;
|
||||||
if (elem.removeEventListener) { // W3C standard
|
if (elem.removeEventListener) { // W3C standard
|
||||||
elem.removeEventListener(type, fn, false);
|
elem.removeEventListener(type, fn, false);
|
||||||
} else if (elem.detachEvent) { // IE versions
|
} else if (elem.detachEvent) { // Old IE versions
|
||||||
elem.detachEvent("on"+type, fn);
|
elem.detachEvent("on"+type, fn);
|
||||||
} else { // Really old browser
|
} else { // Really old browser
|
||||||
elem[type+fn] = null;
|
elem[type+fn] = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Determine if an event is a "right click" event.
|
||||||
|
* @memberof module:Event
|
||||||
|
* @param {Object} ev - DOM event.
|
||||||
|
* @return {Boolean}
|
||||||
|
* @example
|
||||||
|
* // Assume this function is a click event listener.
|
||||||
|
* function clickHandler(evt) {
|
||||||
|
* alert(Event.isRightClick(evt));
|
||||||
|
* });
|
||||||
|
*/
|
||||||
isRightClick: function(ev) {
|
isRightClick: function(ev) {
|
||||||
if (!ev) ev = window.event;
|
if (!ev) ev = window.event;
|
||||||
if (ev.which) return ev.which === 3;
|
if (ev.which) return ev.which === 3;
|
||||||
|
|
@ -54,6 +93,19 @@ var Event = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handy method to (deep) extend an object.
|
* A handy method to (deep) extend an object.
|
||||||
|
* The input object is modified.
|
||||||
|
* @global
|
||||||
|
* @param {Object} myObj - Input object.
|
||||||
|
* @return {Object}
|
||||||
|
* @example
|
||||||
|
* var one = { foo:1, bar: { a:true, b:false } };
|
||||||
|
* var two = { bar: { a:false } };
|
||||||
|
* // In this case both `ext` and `one` will be the same object.
|
||||||
|
* var ext = deepExtend(one, two);
|
||||||
|
* // To create a fresh copy, pass in an empty object as first arg.
|
||||||
|
* var ext = deepExtend({}, one, two);
|
||||||
|
* // Now `ext` is `{ foo:1, bar: { a:false, b:false } }`
|
||||||
|
* // and `one` is left intact.
|
||||||
*/
|
*/
|
||||||
var deepExtend = function(myObj) {
|
var deepExtend = function(myObj) {
|
||||||
myObj = myObj || {};
|
myObj = myObj || {};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue