board.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. /**
  2. * pass the id of the html element to put the drawing board into
  3. * and some options : {
  4. * controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default
  5. * instead of simple strings, you can pass an object to define a control opts
  6. * ie ['Color', { Navigation: { reset: false }}]
  7. * controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center"
  8. * background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default
  9. * color: pencil color ("#000000" by default)
  10. * size: pencil size (3 by default)
  11. * webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back
  12. * droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it,
  13. * errorMessage: html string to put in the board's element on browsers that don't support canvas.
  14. * }
  15. */
  16. DrawingBoard.Board = function(id, opts) {
  17. this.opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts);
  18. this.ev = new DrawingBoard.Utils.MicroEvent();
  19. this.id = id;
  20. this.$el = $(document.getElementById(id));
  21. if (!this.$el.length)
  22. return false;
  23. var tpl = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';
  24. if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
  25. else tpl = '<div class="drawing-board-controls"></div>' + tpl;
  26. this.$el.addClass('drawing-board').append(tpl);
  27. this.dom = {
  28. $canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
  29. $canvas: this.$el.find('.drawing-board-canvas'),
  30. $cursor: this.$el.find('.drawing-board-cursor'),
  31. $controls: this.$el.find('.drawing-board-controls')
  32. };
  33. $.each(['left', 'right', 'center'], $.proxy(function(n, val) {
  34. if (this.opts.controlsPosition.indexOf(val) > -1) {
  35. this.dom.$controls.attr('data-align', val);
  36. return false;
  37. }
  38. }, this));
  39. this.canvas = this.dom.$canvas.get(0);
  40. this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
  41. this.color = this.opts.color;
  42. if (!this.ctx) {
  43. if (this.opts.errorMessage)
  44. this.$el.html(this.opts.errorMessage);
  45. return false;
  46. }
  47. this.storage = this._getStorage();
  48. this.initHistory();
  49. //init default board values before controls are added (mostly pencil color and size)
  50. this.reset({ webStorage: false, history: false, background: false });
  51. //init controls (they will need the default board values to work like pencil color and size)
  52. this.initControls();
  53. //set board's size after the controls div is added
  54. this.resize();
  55. //reset the board to take all resized space
  56. this.reset({ webStorage: false, history: true, background: true });
  57. this.restoreWebStorage();
  58. this.initDropEvents();
  59. this.initDrawEvents();
  60. };
  61. DrawingBoard.Board.defaultOpts = {
  62. controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
  63. controlsPosition: "top left",
  64. color: "#000000",
  65. size: 5,
  66. //background: "#fff",说明:不去除的情况背景色为白色,非透明
  67. eraserColor: "background",
  68. webStorage: 'session',
  69. droppable: false,
  70. enlargeYourContainer: false,
  71. errorMessage: "<p>您的浏览器版本不支持该功能。<a href=\"http://windows.microsoft.com/zh-cn/internet-explorer/download-ie\" target=\"_blank\">点击升级</a></p>"
  72. };
  73. DrawingBoard.Board.prototype = {
  74. /**
  75. * Canvas reset/resize methods: put back the canvas to its default values
  76. *
  77. * depending on options, can set color, size, background back to default values
  78. * and store the reseted canvas in webstorage and history queue
  79. *
  80. * resize values depend on the `enlargeYourContainer` option
  81. */
  82. reset: function(opts) {
  83. opts = $.extend({
  84. color: this.opts.color,
  85. size: this.opts.size,
  86. webStorage: true,
  87. history: true,
  88. background: false
  89. }, opts);
  90. this.setMode('pencil');
  91. if (opts.background) this.resetBackground(this.opts.background, false);
  92. if (opts.color) this.setColor(opts.color);
  93. if (opts.size) this.ctx.lineWidth = opts.size;
  94. this.ctx.lineCap = "round";
  95. this.ctx.lineJoin = "round";
  96. // this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
  97. if (opts.webStorage) this.saveWebStorage();
  98. if (opts.history) this.saveHistory();
  99. this.blankCanvas = this.getImg();
  100. this.ev.trigger('board:reset', opts);
  101. },
  102. resetBackground: function(background, historize) {
  103. background = background || this.opts.background;
  104. historize = typeof historize !== "undefined" ? historize : true;
  105. var bgIsColor = DrawingBoard.Utils.isColor(background);
  106. var prevMode = this.getMode();
  107. this.setMode('pencil');
  108. this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
  109. if (bgIsColor) {
  110. this.ctx.fillStyle = background;
  111. this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
  112. } else if (background)
  113. this.setImg(background);
  114. this.setMode(prevMode);
  115. if (historize) this.saveHistory();
  116. },
  117. resize: function() {
  118. this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length));
  119. var canvasWidth, canvasHeight;
  120. var widths = [
  121. this.$el.width(),
  122. DrawingBoard.Utils.boxBorderWidth(this.$el),
  123. DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true)
  124. ];
  125. var heights = [
  126. this.$el.height(),
  127. DrawingBoard.Utils.boxBorderHeight(this.$el),
  128. this.dom.$controls.height(),
  129. DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true),
  130. DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true)
  131. ];
  132. var that = this;
  133. var sum = function(values, multiplier) { //make the sum of all array values
  134. multiplier = multiplier || 1;
  135. var res = values[0];
  136. for (var i = 1; i < values.length; i++) {
  137. res = res + (values[i]*multiplier);
  138. }
  139. return res;
  140. };
  141. var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one
  142. if (this.opts.enlargeYourContainer) {
  143. canvasWidth = this.$el.width();
  144. canvasHeight = this.$el.height();
  145. this.$el.width( sum(widths) );
  146. this.$el.height( sum(heights) );
  147. } else {
  148. canvasWidth = sub(widths);
  149. canvasHeight = sub(heights);
  150. }
  151. this.dom.$canvasWrapper.css('width', canvasWidth + 'px');
  152. this.dom.$canvasWrapper.css('height', canvasHeight + 'px');
  153. this.dom.$canvas.css('width', canvasWidth + 'px');
  154. this.dom.$canvas.css('height', canvasHeight + 'px');
  155. this.canvas.width = canvasWidth;
  156. this.canvas.height = canvasHeight;
  157. },
  158. /**
  159. * Controls:
  160. * the drawing board can has various UI elements to control it.
  161. * one control is represented by a class in the namespace DrawingBoard.Control
  162. * it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization.
  163. *
  164. */
  165. initControls: function() {
  166. this.controls = [];
  167. if (!this.opts.controls.length || !DrawingBoard.Control) return false;
  168. for (var i = 0; i < this.opts.controls.length; i++) {
  169. var c = null;
  170. if (typeof this.opts.controls[i] == "string")
  171. c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this);
  172. else if (typeof this.opts.controls[i] == "object") {
  173. for (var controlName in this.opts.controls[i]) break;
  174. c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]);
  175. }
  176. if (c) {
  177. this.addControl(c);
  178. }
  179. }
  180. },
  181. //add a new control or an existing one at the position you want in the UI
  182. //to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }"
  183. //the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at
  184. addControl: function(control, optsOrPos, pos) {
  185. if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control))
  186. return false;
  187. var opts = typeof optsOrPos == "object" ? optsOrPos : {};
  188. pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null);
  189. if (typeof control == "string")
  190. control = new window['DrawingBoard']['Control'][control](this, opts);
  191. if (pos)
  192. this.dom.$controls.children().eq(pos).before(control.$el);
  193. else
  194. this.dom.$controls.append(control.$el);
  195. if (!this.controls)
  196. this.controls = [];
  197. this.controls.push(control);
  198. this.dom.$controls.removeClass('drawing-board-controls-hidden');
  199. },
  200. /**
  201. * History methods: undo and redo drawed lines
  202. */
  203. initHistory: function() {
  204. this.history = {
  205. values: [],
  206. position: 0
  207. };
  208. },
  209. saveHistory: function () {
  210. while (this.history.values.length > 30) {
  211. this.history.values.shift();
  212. this.history.position--;
  213. }
  214. if (this.history.position !== 0 && this.history.position < this.history.values.length) {
  215. this.history.values = this.history.values.slice(0, this.history.position);
  216. this.history.position++;
  217. } else {
  218. this.history.position = this.history.values.length+1;
  219. }
  220. this.history.values.push(this.getImg());
  221. this.ev.trigger('historyNavigation', this.history.position);
  222. },
  223. _goThroughHistory: function(goForth) {
  224. if ((goForth && this.history.position == this.history.values.length) ||
  225. (!goForth && this.history.position == 1))
  226. return;
  227. var pos = goForth ? this.history.position+1 : this.history.position-1;
  228. if (this.history.values.length && this.history.values[pos-1] !== undefined) {
  229. this.history.position = pos;
  230. this.setImg(this.history.values[pos-1]);
  231. }
  232. this.ev.trigger('historyNavigation', pos);
  233. this.saveWebStorage();
  234. },
  235. goBackInHistory: function() {
  236. this._goThroughHistory(false);
  237. },
  238. goForthInHistory: function() {
  239. this._goThroughHistory(true);
  240. },
  241. /**
  242. * Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download
  243. */
  244. setImg: function(src) {
  245. var ctx = this.ctx;
  246. var img = new Image();
  247. var oldGCO = ctx.globalCompositeOperation;
  248. img.onload = function() {
  249. ctx.globalCompositeOperation = "source-over";
  250. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width);
  251. ctx.drawImage(img, 0, 0);
  252. ctx.globalCompositeOperation = oldGCO;
  253. };
  254. img.src = src;
  255. },
  256. getImg: function() {
  257. return this.canvas.toDataURL("image/png");
  258. },
  259. downloadImg: function() {
  260. var img = this.getImg();
  261. img = img.replace("image/png", "image/octet-stream");
  262. window.location.href = img;
  263. },
  264. uploadImg: function() {
  265. var myimg = this.getImg();
  266. $.ajax({
  267. type : "POST",
  268. url : "SealAction.do?date=" + new Date()
  269. + "&task=saveSealPng",
  270. data : {img:myimg},
  271. timeout : 10000,
  272. async : false,
  273. success : function(msg) {
  274. alert(msg);
  275. }
  276. });
  277. },
  278. /**
  279. * WebStorage handling : save and restore to local or session storage
  280. */
  281. saveWebStorage: function() {
  282. if (window[this.storage]) {
  283. window[this.storage].setItem('drawing-board-' + this.id, this.getImg());
  284. this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg());
  285. }
  286. },
  287. restoreWebStorage: function() {
  288. if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
  289. //初始化画布时默认的图案,注释
  290. //this.setImg(window[this.storage].getItem('drawing-board-' + this.id));
  291. this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id));
  292. }
  293. },
  294. clearWebStorage: function() {
  295. if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
  296. window[this.storage].removeItem('drawing-board-' + this.id);
  297. this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1));
  298. }
  299. },
  300. _getStorage: function() {
  301. if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false;
  302. return this.opts.webStorage + 'Storage';
  303. },
  304. /**
  305. * Drop an image on the canvas to draw on it
  306. */
  307. initDropEvents: function() {
  308. if (!this.opts.droppable)
  309. return false;
  310. this.dom.$canvas.on('dragover dragenter drop', function(e) {
  311. e.stopPropagation();
  312. e.preventDefault();
  313. });
  314. this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this));
  315. },
  316. _onCanvasDrop: function(e) {
  317. e = e.originalEvent ? e.originalEvent : e;
  318. var files = e.dataTransfer.files;
  319. if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader)
  320. return false;
  321. var fr = new FileReader();
  322. fr.readAsDataURL(files[0]);
  323. fr.onload = $.proxy(function(ev) {
  324. this.setImg(ev.target.result);
  325. this.ev.trigger('board:imageDropped', ev.target.result);
  326. this.ev.trigger('board:userAction');
  327. this.saveHistory();
  328. }, this);
  329. },
  330. /**
  331. * set and get current drawing mode
  332. *
  333. * possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can)
  334. */
  335. setMode: function(newMode, silent) {
  336. silent = silent || false;
  337. newMode = newMode || 'pencil';
  338. this.ev.unbind('board:startDrawing', $.proxy(this.fill, this));
  339. if (this.opts.eraserColor === "transparent")
  340. this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over";
  341. else {
  342. if (newMode === "eraser") {
  343. if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background))
  344. this.ctx.strokeStyle = this.opts.background;
  345. else if (DrawingBoard.Utils.isColor(this.opts.eraserColor))
  346. this.ctx.strokeStyle = this.opts.eraserColor;
  347. } else if (!this.mode || this.mode === "eraser") {
  348. this.ctx.strokeStyle = this.color;
  349. }
  350. if (newMode === "filler")
  351. this.ev.bind('board:startDrawing', $.proxy(this.fill, this));
  352. }
  353. this.mode = newMode;
  354. if (!silent)
  355. this.ev.trigger('board:mode', this.mode);
  356. },
  357. getMode: function() {
  358. return this.mode || "pencil";
  359. },
  360. setColor: function(color) {
  361. var that = this;
  362. color = color || this.color;
  363. if (!DrawingBoard.Utils.isColor(color))
  364. return false;
  365. this.color = color;
  366. if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") {
  367. var setStrokeStyle = function(mode) {
  368. if (mode !== "eraser")
  369. that.strokeStyle = that.color;
  370. that.ev.unbind('board:mode', setStrokeStyle);
  371. };
  372. this.ev.bind('board:mode', setStrokeStyle);
  373. } else
  374. this.ctx.strokeStyle = this.color;
  375. },
  376. /**
  377. * Fills an area with the current stroke color.
  378. */
  379. fill: function(e) {
  380. if (this.getImg() === this.blankCanvas) {
  381. this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
  382. this.ctx.fillStyle = this.color;
  383. this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
  384. return;
  385. }
  386. var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
  387. // constants identifying pixels components
  388. var INDEX = 0, X = 1, Y = 2, COLOR = 3;
  389. // target color components
  390. var stroke = this.ctx.strokeStyle;
  391. var r = parseInt(stroke.substr(1, 2), 16);
  392. var g = parseInt(stroke.substr(3, 2), 16);
  393. var b = parseInt(stroke.substr(5, 2), 16);
  394. // starting point
  395. var start = DrawingBoard.Utils.pixelAt(img, parseInt( e.coords.x, 10), parseInt( e.coords.y, 10));
  396. // no need to continue if starting and target colors are the same
  397. if (start[COLOR] === DrawingBoard.Utils.RGBToInt(r, g, b))
  398. return;
  399. // pixels to evaluate
  400. var queue = [start];
  401. // loop vars
  402. var pixel, x, y;
  403. var maxX = img.width - 1;
  404. var maxY = img.height - 1;
  405. while ((pixel = queue.pop())) {
  406. if (pixel[COLOR] === start[COLOR]) {
  407. img.data[pixel[INDEX]] = r;
  408. img.data[pixel[INDEX] + 1] = g;
  409. img.data[pixel[INDEX] + 2] = b;
  410. if (pixel[X] > 0) // west
  411. queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y]));
  412. if (pixel[X] < maxX) // east
  413. queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y]));
  414. if (pixel[Y] > 0) // north
  415. queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1));
  416. if (pixel[Y] < maxY) // south
  417. queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1));
  418. }
  419. }
  420. this.ctx.putImageData(img, 0, 0);
  421. },
  422. /**
  423. * Drawing handling, with mouse or touch
  424. */
  425. initDrawEvents: function() {
  426. this.isDrawing = false;
  427. this.isMouseHovering = false;
  428. this.coords = {};
  429. this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 };
  430. this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) {
  431. this._onInputStart(e, this._getInputCoords(e) );
  432. }, this));
  433. this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) {
  434. this._onInputMove(e, this._getInputCoords(e) );
  435. }, this));
  436. this.dom.$canvas.on('mousemove', $.proxy(function(e) {
  437. }, this));
  438. this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) {
  439. this._onInputStop(e, this._getInputCoords(e) );
  440. }, this));
  441. this.dom.$canvas.on('mouseover', $.proxy(function(e) {
  442. this._onMouseOver(e, this._getInputCoords(e) );
  443. }, this));
  444. this.dom.$canvas.on('mouseout', $.proxy(function(e) {
  445. this._onMouseOut(e, this._getInputCoords(e) );
  446. }, this));
  447. $('body').on('mouseup touchend', $.proxy(function(e) {
  448. this.isDrawing = false;
  449. }, this));
  450. if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) );
  451. },
  452. draw: function() {
  453. //if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil
  454. //todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported
  455. //we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad.
  456. if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) {
  457. this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' });
  458. var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) });
  459. this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform });
  460. this.dom.$cursor.removeClass('drawing-board-utils-hidden');
  461. } else {
  462. this.dom.$cursor.addClass('drawing-board-utils-hidden');
  463. }
  464. if (this.isDrawing) {
  465. var currentMid = this._getMidInputCoords(this.coords.current);
  466. this.ctx.beginPath();
  467. this.ctx.moveTo(currentMid.x, currentMid.y);
  468. this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y);
  469. this.ctx.stroke();
  470. this.coords.old = this.coords.current;
  471. this.coords.oldMid = currentMid;
  472. }
  473. if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) );
  474. },
  475. _onInputStart: function(e, coords) {
  476. this.coords.current = this.coords.old = coords;
  477. this.coords.oldMid = this._getMidInputCoords(coords);
  478. this.isDrawing = true;
  479. if (!window.requestAnimationFrame) this.draw();
  480. this.ev.trigger('board:startDrawing', {e: e, coords: coords});
  481. e.preventDefault();
  482. },
  483. _onInputMove: function(e, coords) {
  484. this.coords.current = coords;
  485. this.ev.trigger('board:drawing', {e: e, coords: coords});
  486. if (!window.requestAnimationFrame) this.draw();
  487. e.preventDefault();
  488. },
  489. _onInputStop: function(e, coords) {
  490. if (this.isDrawing && (!e.touches || e.touches.length === 0)) {
  491. this.isDrawing = false;
  492. this.saveWebStorage();
  493. this.saveHistory();
  494. this.ev.trigger('board:stopDrawing', {e: e, coords: coords});
  495. this.ev.trigger('board:userAction');
  496. e.preventDefault();
  497. }
  498. },
  499. _onMouseOver: function(e, coords) {
  500. this.isMouseHovering = true;
  501. this.coords.old = this._getInputCoords(e);
  502. this.coords.oldMid = this._getMidInputCoords(this.coords.old);
  503. this.ev.trigger('board:mouseOver', {e: e, coords: coords});
  504. },
  505. _onMouseOut: function(e, coords) {
  506. this.isMouseHovering = false;
  507. this.ev.trigger('board:mouseOut', {e: e, coords: coords});
  508. },
  509. _getInputCoords: function(e) {
  510. e = e.originalEvent ? e.originalEvent : e;
  511. var x, y;
  512. if (e.touches && e.touches.length == 1) {
  513. x = e.touches[0].pageX;
  514. y = e.touches[0].pageY;
  515. } else {
  516. x = e.pageX;
  517. y = e.pageY;
  518. }
  519. return {
  520. x: x - this.dom.$canvas.offset().left,
  521. y: y - this.dom.$canvas.offset().top
  522. };
  523. },
  524. _getMidInputCoords: function(coords) {
  525. return {
  526. x: this.coords.old.x + coords.x>>1,
  527. y: this.coords.old.y + coords.y>>1
  528. };
  529. }
  530. };