jquery.eraser.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /*
  2. * jQuery.eraser v0.4.2
  3. * makes any image or canvas erasable by the user, using touch or mouse input
  4. * https://github.com/boblemarin/jQuery.eraser
  5. *
  6. * Usage:
  7. *
  8. * $('#myImage').eraser(); // simple way
  9. *
  10. * $(#canvas').eraser( {
  11. * size: 20, // define brush size (default value is 40)
  12. * completeRatio: .65, // allows to call function when a erased ratio is reached (between 0 and 1, default is .7 )
  13. * completeFunction: myFunction // callback function when complete ratio is reached
  14. * } );
  15. *
  16. * $('#image').eraser( 'clear' ); // erases all canvas content
  17. *
  18. * $('#image').eraser( 'reset' ); // revert back to original content
  19. *
  20. * $('#image').eraser( 'size', 80 ); // change the eraser size
  21. *
  22. *
  23. * https://github.com/boblemarin/jQuery.eraser
  24. * http://minimal.be/lab/jQuery.eraser/
  25. *
  26. * Copyright (c) 2010 boblemarin emeric@minimal.be http://www.minimal.be
  27. *
  28. * Permission is hereby granted, free of charge, to any person
  29. * obtaining a copy of this software and associated documentation
  30. * files (the "Software"), to deal in the Software without
  31. * restriction, including without limitation the rights to use,
  32. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  33. * copies of the Software, and to permit persons to whom the
  34. * Software is furnished to do so, subject to the following
  35. * conditions:
  36. *
  37. * The above copyright notice and this permission notice shall be
  38. * included in all copies or substantial portions of the Software.
  39. *
  40. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  41. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  42. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  43. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  44. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  45. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  46. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  47. * OTHER DEALINGS IN THE SOFTWARE.
  48. * ËزļÒÔ° - www.sucaijiayuan.com
  49. */
  50. (function( $ ){
  51. var methods = {
  52. init : function( options ) {
  53. return this.each(function(){
  54. var $this = $(this),
  55. data = $this.data('eraser');
  56. if ( !data ) {
  57. var width = $this.width(),
  58. height = $this.height(),
  59. pos = $this.offset(),
  60. $canvas = $("<canvas/>"),
  61. canvas = $canvas.get(0),
  62. size = ( options && options.size )?options.size:40,
  63. completeRatio = ( options && options.completeRatio )?options.completeRatio:.7,
  64. completeFunction = ( options && options.completeFunction )?options.completeFunction:null,
  65. parts = [],
  66. colParts = Math.floor( width / size ),
  67. numParts = colParts * Math.floor( height / size ),
  68. n = numParts,
  69. ctx = canvas.getContext("2d");
  70. // replace target with canvas
  71. $this.after( $canvas );
  72. canvas.id = this.id;
  73. canvas.className = this.className;
  74. canvas.width = width;
  75. canvas.height = height;
  76. ctx.drawImage( this, 0, 0 );
  77. $this.remove();
  78. // prepare context for drawing operations
  79. ctx.globalCompositeOperation = "destination-out";
  80. ctx.strokeStyle = 'rgba(255,0,0,255)';
  81. ctx.lineWidth = size;
  82. ctx.lineCap = "round";
  83. // bind events
  84. $canvas.bind('mousedown.eraser', methods.mouseDown);
  85. $canvas.bind('touchstart.eraser', methods.touchStart);
  86. $canvas.bind('touchmove.eraser', methods.touchMove);
  87. $canvas.bind('touchend.eraser', methods.touchEnd);
  88. // reset parts
  89. while( n-- ) parts.push(1);
  90. // store values
  91. data = {
  92. posX:pos.left,
  93. posY:pos.top,
  94. touchDown: false,
  95. touchID:-999,
  96. touchX: 0,
  97. touchY: 0,
  98. ptouchX: 0,
  99. ptouchY: 0,
  100. canvas: $canvas,
  101. ctx: ctx,
  102. w:width,
  103. h:height,
  104. source: this,
  105. size: size,
  106. parts: parts,
  107. colParts: colParts,
  108. numParts: numParts,
  109. ratio: 0,
  110. complete: false,
  111. completeRatio: completeRatio,
  112. completeFunction: completeFunction
  113. };
  114. $canvas.data('eraser', data);
  115. // listen for resize event to update offset values
  116. $(window).resize( function() {
  117. var pos = $canvas.offset();
  118. data.posX = pos.left;
  119. data.posY = pos.top;
  120. });
  121. }
  122. });
  123. },
  124. touchStart: function( event ) {
  125. var $this = $(this),
  126. data = $this.data('eraser');
  127. if ( !data.touchDown ) {
  128. var t = event.originalEvent.changedTouches[0],
  129. tx = t.pageX - data.posX,
  130. ty = t.pageY - data.posY;
  131. methods.evaluatePoint( data, tx, ty );
  132. data.touchDown = true;
  133. data.touchID = t.identifier;
  134. data.touchX = tx;
  135. data.touchY = ty;
  136. event.preventDefault();
  137. }
  138. },
  139. touchMove: function( event ) {
  140. var $this = $(this),
  141. data = $this.data('eraser');
  142. if ( data.touchDown ) {
  143. var ta = event.originalEvent.changedTouches,
  144. n = ta.length;
  145. while( n-- )
  146. if ( ta[n].identifier == data.touchID ) {
  147. var tx = ta[n].pageX - data.posX,
  148. ty = ta[n].pageY - data.posY;
  149. methods.evaluatePoint( data, tx, ty );
  150. data.ctx.beginPath();
  151. data.ctx.moveTo( data.touchX, data.touchY );
  152. data.touchX = tx;
  153. data.touchY = ty;
  154. data.ctx.lineTo( data.touchX, data.touchY );
  155. data.ctx.stroke();
  156. event.preventDefault();
  157. break;
  158. }
  159. }
  160. },
  161. touchEnd: function( event ) {
  162. var $this = $(this),
  163. data = $this.data('eraser');
  164. if ( data.touchDown ) {
  165. var ta = event.originalEvent.changedTouches,
  166. n = ta.length;
  167. while( n-- )
  168. if ( ta[n].identifier == data.touchID ) {
  169. data.touchDown = false;
  170. event.preventDefault();
  171. break;
  172. }
  173. }
  174. },
  175. evaluatePoint: function( data, tx, ty ) {
  176. var p = Math.floor(tx/data.size) + Math.floor( ty / data.size ) * data.colParts;
  177. if ( p >= 0 && p < data.numParts ) {
  178. data.ratio += data.parts[p];
  179. data.parts[p] = 0;
  180. if ( !data.complete) {
  181. if ( data.ratio/data.numParts >= data.completeRatio ) {
  182. data.complete = true;
  183. if ( data.completeFunction != null ) data.completeFunction();
  184. }
  185. }
  186. }
  187. },
  188. mouseDown: function( event ) {
  189. var $this = $(this),
  190. data = $this.data('eraser'),
  191. tx = event.pageX - data.posX,
  192. ty = event.pageY - data.posY;
  193. methods.evaluatePoint( data, tx, ty );
  194. data.touchDown = true;
  195. data.touchX = tx;
  196. data.touchY = ty;
  197. data.ctx.beginPath();
  198. data.ctx.moveTo( data.touchX-1, data.touchY );
  199. data.ctx.lineTo( data.touchX, data.touchY );
  200. data.ctx.stroke();
  201. $this.bind('mousemove.eraser', methods.mouseMove);
  202. $(document).bind('mouseup.eraser', data, methods.mouseUp);
  203. event.preventDefault();
  204. },
  205. mouseMove: function( event ) {
  206. var $this = $(this),
  207. data = $this.data('eraser'),
  208. tx = event.pageX - data.posX,
  209. ty = event.pageY - data.posY;
  210. methods.evaluatePoint( data, tx, ty );
  211. data.ctx.beginPath();
  212. data.ctx.moveTo( data.touchX, data.touchY );
  213. data.touchX = tx;
  214. data.touchY = ty;
  215. data.ctx.lineTo( data.touchX, data.touchY );
  216. data.ctx.stroke();
  217. event.preventDefault();
  218. },
  219. mouseUp: function( event ) {
  220. var data = event.data,
  221. $this = data.canvas;
  222. data.touchDown = false;
  223. $this.unbind('mousemove.eraser');
  224. $(document).unbind('mouseup.eraser');
  225. event.preventDefault();
  226. },
  227. clear: function() {
  228. var $this = $(this),
  229. data = $this.data('eraser');
  230. if ( data )
  231. {
  232. data.ctx.clearRect( 0, 0, data.w, data.h );
  233. var n = data.numParts;
  234. while( n-- ) data.parts[n] = 0;
  235. data.ratio = data.numParts;
  236. data.complete = true;
  237. if ( data.completeFunction != null ) data.completeFunction();
  238. }
  239. },
  240. size: function(value) {
  241. var $this = $(this),
  242. data = $this.data('eraser');
  243. if ( data && value )
  244. {
  245. data.size = value;
  246. data.ctx.lineWidth = value;
  247. }
  248. },
  249. reset: function() {
  250. var $this = $(this),
  251. data = $this.data('eraser');
  252. if ( data )
  253. {
  254. data.ctx.globalCompositeOperation = "source-over";
  255. data.ctx.drawImage( data.source, 0, 0 );
  256. data.ctx.globalCompositeOperation = "destination-out";
  257. var n = data.numParts;
  258. while( n-- ) data.parts[n] = 1;
  259. data.ratio = 0;
  260. data.complete = false;
  261. }
  262. }
  263. };
  264. $.fn.eraser = function( method ) {
  265. if ( methods[method] ) {
  266. return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
  267. } else if ( typeof method === 'object' || ! method ) {
  268. return methods.init.apply( this, arguments );
  269. } else {
  270. $.error( 'Method ' + method + ' does not yet exist on jQuery.eraser' );
  271. }
  272. };
  273. })( jQuery );