-
Notifications
You must be signed in to change notification settings - Fork 108
Expand file tree
/
Copy pathsketch.coffee
More file actions
227 lines (208 loc) · 8.12 KB
/
sketch.coffee
File metadata and controls
227 lines (208 loc) · 8.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# # Sketch.js (v0.0.1)
#
# **Sketch.js** is a simple jQuery plugin for creating drawable canvases
# using HTML5 Canvas. It supports multiple browsers including mobile
# devices (albeit with performance penalties).
(($)->
# ### jQuery('#mycanvas').sketch(options)
#
# Given an ID selector for a `<canvas>` element, initialize the specified
# canvas as a drawing canvas. See below for the options that may be passed in.
$.fn.sketch = (key, args...)->
$.error('Sketch.js can only be called on one element at a time.') if this.length > 1
sketch = this.data('sketch')
# If a canvas has already been initialized as a sketchpad, calling
# `.sketch()` will return the Sketch instance (see documentation below)
# for the canvas. If you pass a single string argument (such as `'color'`)
# it will return the value of any set instance variables. If you pass
# a string argument followed by a value, it will set an instance variable
# (e.g. `.sketch('color','#f00')`.
if typeof(key) == 'string' && sketch
if sketch[key]
if typeof(sketch[key]) == 'function'
sketch[key].apply sketch, args
else if args.length == 0
sketch[key]
else if args.length == 1
sketch[key] = args[0]
else
$.error('Sketch.js did not recognize the given command.')
else if sketch
sketch
else
this.data('sketch', new Sketch(this.get(0), key))
this
# ## Sketch
#
# The Sketch class represents an activated drawing canvas. It holds the
# state, all relevant data, and all methods related to the plugin.
class Sketch
# ### new Sketch(el, opts)
#
# Initialize the Sketch class with a canvas DOM element and any specified
# options. The available options are:
#
# * `toolLinks`: If `true`, automatically turn links with href of `#mycanvas`
# into tool action links. See below for a description of the available
# tool links.
# * `defaultTool`: Defaults to `marker`, the tool is any of the extensible
# tools that the canvas should default to.
# * `defaultColor`: The default drawing color. Defaults to black.
# * `defaultSize`: The default stroke size. Defaults to 5.
constructor: (el, opts)->
@el = el
@canvas = $(el)
@context = el.getContext '2d'
@options = $.extend {
toolLinks: true
defaultTool: 'marker'
defaultColor: '#000000'
defaultSize: 5
}, opts
@painting = false
@color = @options.defaultColor
@size = @options.defaultSize
@tool = @options.defaultTool
@actions = []
@action = []
@canvas.bind 'click mousedown mouseup mousemove mouseleave mouseout touchstart touchmove touchend touchcancel', @onEvent
# ### Tool Links
#
# Tool links automatically bind `a` tags that have an `href` attribute
# of `#mycanvas` (mycanvas being the ID of your `<canvas>` element to
# perform actions on the canvas.
if @options.toolLinks
$('body').delegate "a[href=\"##{@canvas.attr('id')}\"]", 'click', (e)->
$this = $(this)
$canvas = $($this.attr('href'))
sketch = $canvas.data('sketch')
# Tool links are keyed off of HTML5 `data` attributes. The following
# attributes are supported:
#
# * `data-tool`: Change the current tool to the specified value.
# * `data-color`: Change the draw color to the specified value.
# * `data-size`: Change the stroke size to the specified value.
# * `data-download`: Trigger a sketch download in the specified format.
for key in ['color', 'size', 'tool']
if $this.attr("data-#{key}")
sketch.set key, $(this).attr("data-#{key}")
if $(this).attr('data-download')
sketch.download $(this).attr('data-download')
false
# ### sketch.download(format)
#
# Cause the browser to open up a new window with the Data URL of the current
# canvas. The `format` parameter can be either `png` or `jpeg`.
download: (format)->
format or= "png"
format = "jpeg" if format == "jpg"
mime = "image/#{format}"
window.open @el.toDataURL(mime)
# ### sketch.set(key, value)
#
# *Internal method.* Sets an arbitrary instance variable on the Sketch instance
# and triggers a `changevalue` event so that any appropriate bindings can take
# place.
set: (key, value)->
this[key] = value
@canvas.trigger("sketch.change#{key}", value)
# ### sketch.startPainting()
#
# *Internal method.* Called when a mouse or touch event is triggered
# that begins a paint stroke.
startPainting: ->
@painting = true
@action = {
tool: @tool
color: @color
size: parseFloat(@size)
events: []
}
# ### sketch.stopPainting()
#
# *Internal method.* Called when the mouse is released or leaves the canvas.
stopPainting: ->
@actions.push @action if @action
@painting = false
@action = null
@redraw()
# ### sketch.onEvent(e)
#
# *Internal method.* Universal event handler for the canvas. Any mouse or
# touch related events are passed through this handler before being passed
# on to the individual tools.
onEvent: (e)->
if e.originalEvent && e.originalEvent.targetTouches
if e.type == 'touchend'
e.pageX = e.originalEvent.pageX
e.pageY = e.originalEvent.pageY
else
e.pageX = e.originalEvent.targetTouches[0].pageX
e.pageY = e.originalEvent.targetTouches[0].pageY
$.sketch.tools[$(this).data('sketch').tool].onEvent.call($(this).data('sketch'), e)
e.preventDefault()
false
# ### sketch.redraw()
#
# *Internal method.* Redraw the sketchpad from scratch using the aggregated
# actions that have been stored as well as the action in progress if it has
# something renderable.
redraw: ->
@el.width = @canvas.width()
@context = @el.getContext '2d'
sketch = this
$.each @actions, ->
if this.tool
$.sketch.tools[this.tool].draw.call sketch, this
$.sketch.tools[@action.tool].draw.call sketch, @action if @painting && @action
# # Tools
#
# Sketch.js is built with a pluggable, extensible tool foundation. Each tool works
# by accepting and manipulating events registered on the sketch using an `onEvent`
# method and then building up **actions** that, when passed to the `draw` method,
# will render the tool's effect to the canvas. The tool methods are executed with
# the Sketch instance as `this`.
#
# Tools can be added simply by adding a new key to the `$.sketch.tools` object.
$.sketch = { tools: {} }
# ## marker
#
# The marker is the most basic drawing tool. It will draw a stroke of the current
# width and current color wherever the user drags his or her mouse.
$.sketch.tools.marker =
onEvent: (e)->
switch e.type
when 'mousedown', 'touchstart'
@startPainting()
when 'mouseup', 'mouseout', 'mouseleave', 'touchend', 'touchcancel'
@stopPainting()
if @painting
@action.events.push
x: e.pageX - @canvas.offset().left
y: e.pageY - @canvas.offset().top
event: e.type
@redraw()
draw: (action)->
@context.lineJoin = "round"
@context.lineCap = "round"
@context.beginPath()
@context.moveTo action.events[0].x, action.events[0].y
for event in action.events
@context.lineTo event.x, event.y
previous = event
@context.strokeStyle = action.color
@context.lineWidth = action.size
@context.stroke()
# ## eraser
#
# The eraser does just what you'd expect: removes any of the existing sketch.
$.sketch.tools.eraser =
onEvent: (e)->
$.sketch.tools.marker.onEvent.call this, e
draw: (action)->
oldcomposite = @context.globalCompositeOperation
@context.globalCompositeOperation = "destination-out"
action.color = "rgba(0,0,0,1)"
$.sketch.tools.marker.draw.call this, action
@context.globalCompositeOperation = oldcomposite
)(jQuery)