├── README.md
├── example.html
├── image_save.php
├── jqscribble.extrabrushes.js
├── jqscribble.jquery.json
└── jquery.jqscribble.js
/README.md:
--------------------------------------------------------------------------------
1 | jqScribble
2 | ==========
3 |
4 | jqScribble is a jquery plugin that will allow you to draw on an HTML5 canvas element. It works with standard mouse input and also touch input. It is designed to be extremely extensible, allowing for custom brushes and image saving. I have also provided with this plugin a sample PHP file that will demonstrate turning drawn images into actual images.
5 |
6 | Usage
7 | -----
8 | To use the jqScribble plugin first select a jquery element to attach to scribble canvas to. Then specify any options you wish.
9 | ```js
10 | $('#test').jqScribble(options);
11 | ```
12 | Available Options
13 | -----------------
14 | * `width` (int) The width of the Canvas element if not specified then the width of the parent is used
15 | - DEFAULT - 300
16 | * `height` (int) The height of the Canvas element if not specified then the height of the parent is used.
17 | - DEFAULT - 250
18 | * `backgroundImage` (string) An image to add to the background of the canvas.
19 | - DEFAULT - false
20 | * `backgroundImageX` (int) The X offset in the canvas to put the specified background image
21 | - DEFAULT - 0
22 | * `backgroundImageY` (int) The Y offset in the canvas to put the specified background image
23 | - DEFAULT - 0
24 | * `backgroundColor` (string) The hex color value to set the background as.
25 | - DEFAULT - #ffffff
26 | * `saveMimeType` (string) If the image is saved the mime type that will be used.
27 | - DEFAULT - image/png
28 | * `saveFunction` (function) The function to use when saving the drawing.
29 | - DEFAULT - BasicCanvasSave
30 | * `brush` (jqScribbleBrush) The brush to used when drawing on the Canvas.
31 | - DEFAULT - BasicBrush
32 | * `brushSize` (int) The size of the brush that is used.
33 | - DEFAULT - 2
34 | * `brushColor` (string) The color of the brush stroke.
35 | - DEFAULT - #000
36 | * `fillOnClear` (bool) Controls whether or not the canvas will be filled with color upon execution of clear().
37 | - DEFAULT - true
38 |
39 | Exposed Attributes
40 | ------------------
41 | You can access the canvas DOM element of a jqScribble instance by calling
42 | ```js
43 | $('...').data('jqScribble').canvas
44 | ```
45 | You can check if the jqScribble instance has been drawn on by checking
46 | ```js
47 | $('...').data('jqScribble').blank
48 | ```
49 | To get the brush object that jqScribble is currently using
50 | ```js
51 | $('...').data('jqScribble').brush
52 | ```
53 |
54 | Creating New Brushes
55 | --------------------
56 | New brushes should inherit from the jqScribbleBrush object as follows:
57 | ```js
58 | NewBrush.prototype = new jqScribbleBrush.
59 | NewBrush(){...}
60 | ```
61 | They should also implement the following methods:
62 | ```js
63 | strokeBegin(x, y)
64 | strokeMove(x, y)
65 | ```
66 | and can optionally implement
67 | ```js
68 | strokeEnd()
69 | ```
70 | Image Saving
71 | ------------
72 | A save function will be passed the image data of the canvas, provided the canvas is not empty.
73 | ```js
74 | function mySave(imageData)
75 | ```
76 | The specified _save function will not be called until the canvas is not empty_ and you call the jqScribble save function.
77 | ```js
78 | $('...').data('jqScribble').save()
79 | ```
80 | You can also specify a save function at the time of saving by calling save with a function parameter.
81 | ```js
82 | $('...').data('jqScribble').save(function(imageData){...});
83 | ```
84 | The save method is chainable with other jqScribble methods.
85 |
86 | Updating jqScribble Options
87 | ---------------------------
88 | Updates can be passed to the jqScribble instance by calling
89 | ```js
90 | $('...').data('jqScribble').update(options)
91 | ```
92 | Where options are any of the original options specified above.
93 | The update method is chainable with other jqScribble methods.
94 |
95 | Clearing The Canvas
96 | -------------------
97 | To reset the canvas call
98 | ```js
99 | $('...').data('jqScribble').clear()
100 | ```
101 | The clear method is chainable with other jqScribble methods.
102 |
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jqScribble example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
38 |
39 |
40 |
59 |
60 |
69 |
93 |
94 |
--------------------------------------------------------------------------------
/image_save.php:
--------------------------------------------------------------------------------
1 | ";
11 |
--------------------------------------------------------------------------------
/jqscribble.extrabrushes.js:
--------------------------------------------------------------------------------
1 | //This is a custom brush that will draw small lines based off the stroke path.
2 | LineBrush.prototype = new jqScribbleBrush;
3 | function LineBrush()
4 | {
5 | LineBrush.prototype.strokeBegin = function(x, y)
6 | {
7 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
8 | this.lineRadius = 7;
9 | };
10 |
11 | LineBrush.prototype.strokeMove = function(x, y)
12 | {
13 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
14 |
15 | this.context.moveTo(x-this.lineRadius, y-this.lineRadius);
16 | this.context.lineTo(x+this.lineRadius, y+this.lineRadius);
17 |
18 | this.context.strokeStyle = this.brushColor;
19 | this.context.stroke();
20 | };
21 | };
22 | //This is a custom brush that will draw small crosses based off the stroke path.
23 | CrossBrush.prototype = new jqScribbleBrush;
24 | function CrossBrush()
25 | {
26 | CrossBrush.prototype.strokeBegin = function(x, y)
27 | {
28 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
29 | this.lineRadius = 7;
30 | };
31 |
32 | CrossBrush.prototype.strokeMove = function(x, y)
33 | {
34 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
35 |
36 | this.context.moveTo(x-this.lineRadius, y-this.lineRadius);
37 | this.context.lineTo(x+this.lineRadius, y+this.lineRadius);
38 |
39 | this.context.moveTo(x-this.lineRadius, y+this.lineRadius);
40 | this.context.lineTo(x+this.lineRadius, y-this.lineRadius);
41 |
42 | this.context.strokeStyle = this.brushColor;
43 | this.context.stroke();
44 | };
45 | };
46 |
47 | //This is a custom brush that will draw dots.
48 | DotBrush.prototype = new jqScribbleBrush;
49 | function DotBrush()
50 | {
51 | DotBrush.prototype.strokeBegin = function(x, y)
52 | {
53 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
54 | this.lineRadius = 1;
55 | };
56 |
57 | DotBrush.prototype.strokeMove = function(x, y)
58 | {
59 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
60 |
61 | this.context.moveTo(x-this.lineRadius, y+this.lineRadius);
62 | this.context.lineTo(x+this.lineRadius, y-this.lineRadius);
63 |
64 | this.context.strokeStyle = this.brushColor;
65 | this.context.stroke();
66 | };
67 | };
68 |
69 | //This is a custom brush that will draw circles.
70 | CircleBrush.prototype = new jqScribbleBrush;
71 | function CircleBrush()
72 | {
73 | CircleBrush.prototype.strokeBegin = function(x, y)
74 | {
75 | //For custom brushes make sure to call the parent brush methods
76 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
77 | this.prevX = x;
78 | this.prevY = y;
79 | this.lineRadius = 20;
80 | };
81 |
82 | CircleBrush.prototype.strokeMove = function(x, y)
83 | {
84 | //For custom brushes make sure to call the parent brush methods
85 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
86 |
87 | this.context.beginPath();
88 | this.context.arc(x, y, this.lineRadius, 0, 2 * Math.PI, false);
89 |
90 | this.context.strokeStyle = this.brushColor;
91 | this.context.stroke();
92 | };
93 | };
94 |
95 | //This is a custom brush that will draw semicircles.
96 | SemiCircleBrush.prototype = new jqScribbleBrush;
97 | function SemiCircleBrush()
98 | {
99 | SemiCircleBrush.prototype.strokeBegin = function(x, y)
100 | {
101 | //For custom brushes make sure to call the parent brush methods
102 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
103 | this.prevX = x;
104 | this.prevY = y;
105 | this.lineRadius = 20;
106 | };
107 |
108 | SemiCircleBrush.prototype.strokeMove = function(x, y)
109 | {
110 | //For custom brushes make sure to call the parent brush methods
111 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
112 |
113 | this.context.beginPath();
114 | this.context.arc(x, y, this.lineRadius, 0, Math.PI, false);
115 |
116 | this.context.strokeStyle = this.brushColor;
117 | this.context.stroke();
118 | };
119 | };
120 |
121 | //This is a custom brush that will draw rectangles.
122 | RectangleBrush.prototype = new jqScribbleBrush;
123 | function RectangleBrush()
124 | {
125 | RectangleBrush.prototype.strokeBegin = function(x, y)
126 | {
127 | //For custom brushes make sure to call the parent brush methods
128 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
129 | this.prevX = x;
130 | this.prevY = y;
131 | this.lineRadius = 20;
132 | };
133 |
134 | RectangleBrush.prototype.strokeMove = function(x, y)
135 | {
136 | //For custom brushes make sure to call the parent brush methods
137 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
138 |
139 | this.context.beginPath();
140 | this.context.rect(x, y, this.lineRadius, this.lineRadius);
141 |
142 | this.context.strokeStyle = this.brushColor;
143 | this.context.stroke();
144 | };
145 | };
--------------------------------------------------------------------------------
/jqscribble.jquery.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jqscribble",
3 | "version": "2.0.0",
4 | "title": "jqScribble - A touch enabled canvas drawing tool",
5 | "author": {
6 | "name": "Jim Saunders",
7 | "url": "http://jimsaunders.net"
8 | },
9 | "licenses": [
10 | {"type": "MIT", "url": "http://opensource.org/licenses/MIT"}
11 | ],
12 | "dependencies": {
13 | "jquery": "1.5 - 1.9"
14 | },
15 | "description": "A small canvas drawing plugin. It lets you draw on a canvas, add an image to a canvas, and save your drawing as an image. A simple PHP file is included in the repo to demonstrate how you might save the image server side. It is touch enabled so you can draw using a finger on touch enabled devices as well as the mouse on standard devices.",
16 | "keywords": ["touch", "draw", "canvas", "scribble", "drawing", "paint", "brush", "mobile"],
17 | "demo": "http://jimdoescode.github.com/jqScribble",
18 | "homepage": "https://github.com/jimdoescode/jqScribble"
19 | }
--------------------------------------------------------------------------------
/jquery.jqscribble.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011 by Jim Saunders
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | function jqScribbleBrush()
23 | {
24 | jqScribbleBrush.prototype._init = function(context, brushSize, brushColor)
25 | {
26 | this.context = context;
27 | this.context.globalCompositeOperation = 'source-over';
28 | this.brushSize = brushSize;
29 | this.brushColor = brushColor;
30 | this.drawn = false;
31 | this.active = false;
32 | };
33 |
34 | //For custom brushes override this method and perform
35 | //any action to prepare the brush for drawing.
36 | jqScribbleBrush.prototype.strokeBegin = function(x, y)
37 | {
38 | this.active = true;
39 | this.context.beginPath();
40 | this.context.lineWidth = this.brushSize;
41 | };
42 |
43 | //For custom brushes override this method and perform
44 | //any action that the brush does while drawing.
45 | jqScribbleBrush.prototype.strokeMove = function(x, y){this.drawn = this.active;};
46 |
47 | //For custom brushes override this method to perform
48 | //any action to reset the brush once drawing is complete
49 | jqScribbleBrush.prototype.strokeEnd = function()
50 | {
51 | this.active = false;
52 | if(this.drawn)
53 | {
54 | this.drawn = false;
55 | return true;
56 | }
57 | return false;
58 | };
59 | }
60 | //All brushes should inherit from the Brush interface
61 | BasicBrush.prototype = new jqScribbleBrush;
62 | function BasicBrush()
63 | {
64 | BasicBrush.prototype.strokeBegin = function(x, y)
65 | {
66 | //For custom brushes make sure to call the parent brush methods
67 | jqScribbleBrush.prototype.strokeBegin.call(this, x, y);
68 | this.prevX = x;
69 | this.prevY = y;
70 | };
71 |
72 | BasicBrush.prototype.strokeMove = function(x, y)
73 | {
74 | //For custom brushes make sure to call the parent brush methods
75 | jqScribbleBrush.prototype.strokeMove.call(this, x, y);
76 |
77 | this.context.moveTo(this.prevX, this.prevY);
78 | this.context.lineTo(x, y);
79 |
80 | this.context.strokeStyle = this.brushColor;
81 | this.context.stroke();
82 |
83 | this.prevX = x;
84 | this.prevY = y;
85 | };
86 | }
87 |
88 | function BasicCanvasSave(imageData){window.open(imageData,'jqScribble Image');}
89 |
90 | (function($)
91 | {
92 | //These are the default settings if none are specified.
93 | var settings = {
94 | width: 300,
95 | height: 250,
96 | backgroundImage: false,
97 | backgroundImageX: 0,
98 | backgroundImageY: 0,
99 | backgroundImageWidth: null,
100 | backgroundImageHeight: null,
101 | backgroundColor: "#ffffff",
102 | saveMimeType: "image/png",
103 | saveFunction: BasicCanvasSave,
104 | brush: BasicBrush,
105 | brushSize: 2,
106 | brushColor: "rgb(0,0,0)",
107 | fillOnClear: true
108 | };
109 |
110 | function addImage(context)
111 | {
112 | var img = new Image();
113 | img.src = settings.backgroundImage;
114 | img.crossOrigin = "Anonymous";
115 |
116 | img.onload = function(){
117 | if (settings.backgroundImageHeight && settings.backgroundImageWidth){
118 | context.drawImage(
119 | img,
120 | settings.backgroundImageX,
121 | settings.backgroundImageY,
122 | settings.backgroundImageWidth,
123 | settings.backgroundImageHeight
124 | );
125 | } else {
126 | context.drawImage(
127 | img,
128 | settings.backgroundImageX,
129 | settings.backgroundImageY
130 | );
131 | }
132 | }
133 | }
134 |
135 | var jqScribble = function(elm, options)
136 | {
137 | var $elm = $(elm);
138 | var self = this;
139 | var noparent = $elm.is('canvas');
140 | var canvas = noparent ? $elm[0] : document.createElement('canvas');
141 | var context = canvas.getContext('2d');
142 | var width = $elm.innerWidth();
143 | var height = $elm.innerHeight();
144 |
145 | $.extend(settings, options);
146 |
147 | if(noparent)
148 | {
149 | width = $elm.parent().width();
150 | height = $elm.parent().height();
151 | }
152 | else $elm.append(canvas);
153 |
154 | if(width < 2)width = settings.width;
155 | if(height < 2)height = settings.height;
156 |
157 | self.blank = true;
158 | self.canvas = canvas;
159 | self.canvas.width = width;
160 | self.canvas.height = height;
161 | self.clear();
162 |
163 | if(settings.backgroundImage)
164 | {
165 | addImage(context);
166 | self.blank = false;
167 | }
168 |
169 | self.brush = new settings.brush();
170 | self.brush._init(context, settings.brushSize, settings.brushColor);
171 |
172 | if(self.brush.strokeBegin && self.brush.strokeMove && self.brush.strokeEnd)
173 | {
174 | //Have to add touch events the old fashioned way since
175 | //jquery removes that stuff from the event object.
176 | canvas.addEventListener('touchstart', function(e)
177 | {
178 | var o = $elm.offset();
179 | e.preventDefault();
180 | if(e.touches.length > 0)self.brush.strokeBegin(e.touches[0].pageX-o.left, e.touches[0].pageY-o.top);
181 |
182 | }, false);
183 |
184 | canvas.addEventListener('touchmove', function(e)
185 | {
186 | var o = $elm.offset();
187 | e.preventDefault();
188 | if(e.touches.length > 0 && self.brush.active)self.brush.strokeMove(e.touches[0].pageX-o.left, e.touches[0].pageY-o.top);
189 |
190 | }, false);
191 |
192 | canvas.addEventListener('touchend', function(e)
193 | {
194 | e.preventDefault();
195 | if(e.touches.length == 0)self.blank = !self.brush.strokeEnd() && self.blank;
196 |
197 | }, false);
198 |
199 | $(canvas).bind({
200 | mousedown: function(e)
201 | {
202 | var o = $elm.offset();
203 | self.brush.strokeBegin(e.pageX-o.left, e.pageY-o.top);
204 | },
205 | mousemove: function(e)
206 | {
207 | var o = $elm.offset();
208 | if(self.brush.active)self.brush.strokeMove(e.pageX-o.left, e.pageY-o.top);
209 | },
210 | mouseup: function(e){self.blank = !self.brush.strokeEnd() && self.blank;},
211 | mouseout: function(e){self.blank = !self.brush.strokeEnd() && self.blank;}
212 | });
213 | }
214 | };
215 |
216 | jqScribble.prototype = {
217 |
218 | clear: function()
219 | {
220 | var context = this.canvas.getContext('2d');
221 | var width = this.canvas.width;
222 | var height = this.canvas.height;
223 | context.clearRect(0, 0, width, height);
224 | if (settings.fillOnClear) {
225 | context.fillStyle = settings.backgroundColor;
226 | context.fillRect(0, 0, width, height);
227 | }
228 | this.blank = true;
229 | return this;
230 | },
231 |
232 | save: function(newSave)
233 | {
234 | var saveFunction = settings.saveFunction;
235 | if(typeof newSave === 'function')saveFunction = newSave;
236 |
237 | if(!this.blank)saveFunction(this.canvas.toDataURL(settings.saveMimeType));
238 | return this;
239 | },
240 |
241 | update: function(options, reset)
242 | {
243 | var newBg = !!options.backgroundColor;
244 | var newImg = !!options.backgroundImage;
245 | var newWidth = !!options.width;
246 | var newHeight = !!options.height;
247 | var newBrush = !!options.brush;
248 |
249 | $.extend(settings, options);
250 |
251 | var context = this.canvas.getContext("2d");
252 |
253 | if(newBrush)this.brush = new settings.brush();
254 | this.brush._init(context, settings.brushSize, settings.brushColor);
255 |
256 | if(newWidth)this.canvas.width = settings.width;
257 | if(newHeight)this.canvas.height = settings.height;
258 | if(newBg || newImg || newWidth || newHeight || reset)this.clear();
259 | if(newImg)
260 | {
261 | addImage(context);
262 | this.blank = false;
263 | }
264 | return this;
265 | }
266 | };
267 |
268 | $.fn.jqScribble = function(options)
269 | {
270 | return this.each(function()
271 | {
272 | if(!$.data(this, 'jqScribble'))$.data(this, 'jqScribble', new jqScribble(this, options));
273 | });
274 | };
275 |
276 |
277 | $(document).ready(function()
278 | {
279 | $('#file').change(function(e){
280 | var file = e.target.files[0],
281 | imageType = /image.*/;
282 |
283 | if (!file.type.match(imageType))
284 | return;
285 |
286 | var reader = new FileReader();
287 | reader.onload = fileOnload;
288 | reader.readAsDataURL(file);
289 | });
290 |
291 | function fileOnload(e) {
292 | var $img = $('
', { src: e.target.result });
293 | var canvas = $('#test')[0];
294 | var context = canvas.getContext('2d');
295 |
296 | $img.load(function() {
297 | context.drawImage(this, 0, 0);
298 | });
299 | }
300 | });
301 |
302 | })(jQuery);
303 |
--------------------------------------------------------------------------------