]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | /** |
2 | * Farbtastic Color Picker 1.2 | |
3 | * © 2008 Steven Wittens | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation; either version 2 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, write to the Free Software | |
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | */ | |
19 | ||
20 | (function ($){ | |
21 | jQuery.fn.farbtastic = function (callback) { | |
22 | $.farbtastic(this, callback); | |
23 | return this; | |
24 | }; | |
25 | ||
26 | jQuery.farbtastic = function (container, callback) { | |
27 | var container = $(container).get(0); | |
28 | return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback)); | |
29 | } | |
30 | ||
31 | jQuery._farbtastic = function (container, callback) { | |
32 | // Store farbtastic object | |
33 | var fb = this; | |
34 | ||
35 | // Insert markup | |
36 | $(container).html('<div class="farbtastic"><div class="color"></div><div class="wheel"></div><div class="overlay"></div><div class="h-marker marker"></div><div class="sl-marker marker"></div></div>'); | |
37 | var e = $('.farbtastic', container); | |
38 | fb.wheel = $('.wheel', container).get(0); | |
39 | // Dimensions | |
40 | fb.radius = 84; | |
41 | fb.square = 100; | |
42 | fb.width = 194; | |
43 | ||
44 | // Fix background PNGs in IE6 | |
45 | if (navigator.appVersion.match(/MSIE [0-6]\./)) { | |
46 | $('*', e).each(function () { | |
47 | if (this.currentStyle.backgroundImage != 'none') { | |
48 | var image = this.currentStyle.backgroundImage; | |
49 | image = this.currentStyle.backgroundImage.substring(5, image.length - 2); | |
50 | $(this).css({ | |
51 | 'backgroundImage': 'none', | |
52 | 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" | |
53 | }); | |
54 | } | |
55 | }); | |
56 | } | |
57 | ||
58 | /** | |
59 | * Link to the given element(s) or callback. | |
60 | */ | |
61 | fb.linkTo = function (callback) { | |
62 | // Unbind previous nodes | |
63 | if (typeof fb.callback == 'object') { | |
64 | $(fb.callback).unbind('keyup', fb.updateValue); | |
65 | } | |
66 | ||
67 | // Reset color | |
68 | fb.color = null; | |
69 | ||
70 | // Bind callback or elements | |
71 | if (typeof callback == 'function') { | |
72 | fb.callback = callback; | |
73 | } | |
74 | else if (typeof callback == 'object' || typeof callback == 'string') { | |
75 | fb.callback = $(callback); | |
76 | fb.callback.bind('keyup', fb.updateValue); | |
77 | if (fb.callback.get(0).value) { | |
78 | fb.setColor(fb.callback.get(0).value); | |
79 | } | |
80 | } | |
81 | return this; | |
82 | } | |
83 | fb.updateValue = function (event) { | |
84 | if (this.value && this.value != fb.color) { | |
85 | fb.setColor(this.value); | |
86 | } | |
87 | } | |
88 | ||
89 | /** | |
90 | * Change color with HTML syntax #123456 | |
91 | */ | |
92 | fb.setColor = function (color) { | |
93 | var unpack = fb.unpack(color); | |
94 | if (fb.color != color && unpack) { | |
95 | fb.color = color; | |
96 | fb.rgb = unpack; | |
97 | fb.hsl = fb.RGBToHSL(fb.rgb); | |
98 | fb.updateDisplay(); | |
99 | } | |
100 | return this; | |
101 | } | |
102 | ||
103 | /** | |
104 | * Change color with HSL triplet [0..1, 0..1, 0..1] | |
105 | */ | |
106 | fb.setHSL = function (hsl) { | |
107 | fb.hsl = hsl; | |
108 | fb.rgb = fb.HSLToRGB(hsl); | |
109 | fb.color = fb.pack(fb.rgb); | |
110 | fb.updateDisplay(); | |
111 | return this; | |
112 | } | |
113 | ||
114 | ///////////////////////////////////////////////////// | |
115 | ||
116 | /** | |
117 | * Retrieve the coordinates of the given event relative to the center | |
118 | * of the widget. | |
119 | */ | |
120 | fb.widgetCoords = function (event) { | |
121 | var x, y; | |
122 | var el = event.target || event.srcElement; | |
123 | var reference = fb.wheel; | |
124 | ||
125 | if (typeof event.offsetX != 'undefined') { | |
126 | // Use offset coordinates and find common offsetParent | |
127 | var pos = { x: event.offsetX, y: event.offsetY }; | |
128 | ||
129 | // Send the coordinates upwards through the offsetParent chain. | |
130 | var e = el; | |
131 | while (e) { | |
132 | e.mouseX = pos.x; | |
133 | e.mouseY = pos.y; | |
134 | pos.x += e.offsetLeft; | |
135 | pos.y += e.offsetTop; | |
136 | e = e.offsetParent; | |
137 | } | |
138 | ||
139 | // Look for the coordinates starting from the wheel widget. | |
140 | var e = reference; | |
141 | var offset = { x: 0, y: 0 } | |
142 | while (e) { | |
143 | if (typeof e.mouseX != 'undefined') { | |
144 | x = e.mouseX - offset.x; | |
145 | y = e.mouseY - offset.y; | |
146 | break; | |
147 | } | |
148 | offset.x += e.offsetLeft; | |
149 | offset.y += e.offsetTop; | |
150 | e = e.offsetParent; | |
151 | } | |
152 | ||
153 | // Reset stored coordinates | |
154 | e = el; | |
155 | while (e) { | |
156 | e.mouseX = undefined; | |
157 | e.mouseY = undefined; | |
158 | e = e.offsetParent; | |
159 | } | |
160 | } | |
161 | else { | |
162 | // Use absolute coordinates | |
163 | var pos = fb.absolutePosition(reference); | |
164 | x = (event.pageX || 0*(event.clientX + $('html').get(0).scrollLeft)) - pos.x; | |
165 | y = (event.pageY || 0*(event.clientY + $('html').get(0).scrollTop)) - pos.y; | |
166 | } | |
167 | // Subtract distance to middle | |
168 | return { x: x - fb.width / 2, y: y - fb.width / 2 }; | |
169 | } | |
170 | ||
171 | /** | |
172 | * Mousedown handler | |
173 | */ | |
174 | fb.mousedown = function (event) { | |
175 | // Capture mouse | |
176 | if (!document.dragging) { | |
177 | $(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup); | |
178 | document.dragging = true; | |
179 | } | |
180 | ||
181 | // Check which area is being dragged | |
182 | var pos = fb.widgetCoords(event); | |
183 | fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square; | |
184 | ||
185 | // Process | |
186 | fb.mousemove(event); | |
187 | return false; | |
188 | } | |
189 | ||
190 | /** | |
191 | * Mousemove handler | |
192 | */ | |
193 | fb.mousemove = function (event) { | |
194 | // Get coordinates relative to color picker center | |
195 | var pos = fb.widgetCoords(event); | |
196 | ||
197 | // Set new HSL parameters | |
198 | if (fb.circleDrag) { | |
199 | var hue = Math.atan2(pos.x, -pos.y) / 6.28; | |
200 | if (hue < 0) hue += 1; | |
201 | fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]); | |
202 | } | |
203 | else { | |
204 | var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5)); | |
205 | var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5)); | |
206 | fb.setHSL([fb.hsl[0], sat, lum]); | |
207 | } | |
208 | return false; | |
209 | } | |
210 | ||
211 | /** | |
212 | * Mouseup handler | |
213 | */ | |
214 | fb.mouseup = function () { | |
215 | // Uncapture mouse | |
216 | $(document).unbind('mousemove', fb.mousemove); | |
217 | $(document).unbind('mouseup', fb.mouseup); | |
218 | document.dragging = false; | |
219 | } | |
220 | ||
221 | /** | |
222 | * Update the markers and styles | |
223 | */ | |
224 | fb.updateDisplay = function () { | |
225 | // Markers | |
226 | var angle = fb.hsl[0] * 6.28; | |
227 | $('.h-marker', e).css({ | |
228 | left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px', | |
229 | top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px' | |
230 | }); | |
231 | ||
232 | $('.sl-marker', e).css({ | |
233 | left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px', | |
234 | top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px' | |
235 | }); | |
236 | ||
237 | // Saturation/Luminance gradient | |
238 | $('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5]))); | |
239 | ||
240 | // Linked elements or callback | |
241 | if (typeof fb.callback == 'object') { | |
242 | // Set background/foreground color | |
243 | $(fb.callback).css({ | |
244 | backgroundColor: fb.color, | |
245 | color: fb.hsl[2] > 0.5 ? '#000' : '#fff' | |
246 | }); | |
247 | ||
248 | // Change linked value | |
249 | $(fb.callback).each(function() { | |
250 | if (this.value && this.value != fb.color) { | |
251 | this.value = fb.color; | |
252 | } | |
253 | }); | |
254 | } | |
255 | else if (typeof fb.callback == 'function') { | |
256 | fb.callback.call(fb, fb.color); | |
257 | } | |
258 | } | |
259 | ||
260 | /** | |
261 | * Get absolute position of element | |
262 | */ | |
263 | fb.absolutePosition = function (el) { | |
264 | var r = { x: el.offsetLeft, y: el.offsetTop }; | |
265 | // Resolve relative to offsetParent | |
266 | if (el.offsetParent) { | |
267 | var tmp = fb.absolutePosition(el.offsetParent); | |
268 | r.x += tmp.x; | |
269 | r.y += tmp.y; | |
270 | } | |
271 | return r; | |
272 | }; | |
273 | ||
274 | /* Various color utility functions */ | |
275 | fb.pack = function (rgb) { | |
276 | var r = Math.round(rgb[0] * 255); | |
277 | var g = Math.round(rgb[1] * 255); | |
278 | var b = Math.round(rgb[2] * 255); | |
279 | return '#' + (r < 16 ? '0' : '') + r.toString(16) + | |
280 | (g < 16 ? '0' : '') + g.toString(16) + | |
281 | (b < 16 ? '0' : '') + b.toString(16); | |
282 | } | |
283 | ||
284 | fb.unpack = function (color) { | |
285 | if (color.length == 7) { | |
286 | return [parseInt('0x' + color.substring(1, 3)) / 255, | |
287 | parseInt('0x' + color.substring(3, 5)) / 255, | |
288 | parseInt('0x' + color.substring(5, 7)) / 255]; | |
289 | } | |
290 | else if (color.length == 4) { | |
291 | return [parseInt('0x' + color.substring(1, 2)) / 15, | |
292 | parseInt('0x' + color.substring(2, 3)) / 15, | |
293 | parseInt('0x' + color.substring(3, 4)) / 15]; | |
294 | } | |
295 | } | |
296 | ||
297 | fb.HSLToRGB = function (hsl) { | |
298 | var m1, m2, r, g, b; | |
299 | var h = hsl[0], s = hsl[1], l = hsl[2]; | |
300 | m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s; | |
301 | m1 = l * 2 - m2; | |
302 | return [this.hueToRGB(m1, m2, h+0.33333), | |
303 | this.hueToRGB(m1, m2, h), | |
304 | this.hueToRGB(m1, m2, h-0.33333)]; | |
305 | } | |
306 | ||
307 | fb.hueToRGB = function (m1, m2, h) { | |
308 | h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h); | |
309 | if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; | |
310 | if (h * 2 < 1) return m2; | |
311 | if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6; | |
312 | return m1; | |
313 | } | |
314 | ||
315 | fb.RGBToHSL = function (rgb) { | |
316 | var min, max, delta, h, s, l; | |
317 | var r = rgb[0], g = rgb[1], b = rgb[2]; | |
318 | min = Math.min(r, Math.min(g, b)); | |
319 | max = Math.max(r, Math.max(g, b)); | |
320 | delta = max - min; | |
321 | l = (min + max) / 2; | |
322 | s = 0; | |
323 | if (l > 0 && l < 1) { | |
324 | s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l)); | |
325 | } | |
326 | h = 0; | |
327 | if (delta > 0) { | |
328 | if (max == r && max != g) h += (g - b) / delta; | |
329 | if (max == g && max != b) h += (2 + (b - r) / delta); | |
330 | if (max == b && max != r) h += (4 + (r - g) / delta); | |
331 | h /= 6; | |
332 | } | |
333 | return [h, s, l]; | |
334 | } | |
335 | ||
336 | // Install mousedown handler (the others are set on the document on-demand) | |
337 | $('*', e).mousedown(fb.mousedown); | |
338 | ||
339 | // Init color | |
340 | fb.setColor('#000000'); | |
341 | ||
342 | // Set linked elements/callback | |
343 | if (callback) { | |
344 | fb.linkTo(callback); | |
345 | } | |
346 | } | |
347 | })(jQuery); |