Administrator
2022-09-14 58d006e05dcf2a20d0ec5367dd03d66a61db6849
提交 | 用户 | 时间
58d006 1 /*!jQuery Knob*/
A 2 /**
3  * Downward compatible, touchable dial
4  *
5  * Version: 1.2.11
6  * Requires: jQuery v1.7+
7  *
8  * Copyright (c) 2012 Anthony Terrien
9  * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
10  *
11  * Thanks to vor, eskimoblood, spiffistan, FabrizioC
12  */
13 (function (factory) {
14     if (typeof define === 'function' && define.amd) {
15         // AMD. Register as an anonymous module.
16         define(['jquery'], factory);
17     } else {
18         // Browser globals
19         factory(jQuery);
20     }
21 }(function ($) {
22
23     /**
24      * Kontrol library
25      */
26     "use strict";
27
28     /**
29      * Definition of globals and core
30      */
31     var k = {}, // kontrol
32         max = Math.max,
33         min = Math.min;
34
35     k.c = {};
36     k.c.d = $(document);
37     k.c.t = function (e) {
38         return e.originalEvent.touches.length - 1;
39     };
40
41     /**
42      * Kontrol Object
43      *
44      * Definition of an abstract UI control
45      *
46      * Each concrete component must call this one.
47      * <code>
48      * k.o.call(this);
49      * </code>
50      */
51     k.o = function () {
52         var s = this;
53
54         this.o = null; // array of options
55         this.$ = null; // jQuery wrapped element
56         this.i = null; // mixed HTMLInputElement or array of HTMLInputElement
57         this.g = null; // deprecated 2D graphics context for 'pre-rendering'
58         this.v = null; // value ; mixed array or integer
59         this.cv = null; // change value ; not commited value
60         this.x = 0; // canvas x position
61         this.y = 0; // canvas y position
62         this.w = 0; // canvas width
63         this.h = 0; // canvas height
64         this.$c = null; // jQuery canvas element
65         this.c = null; // rendered canvas context
66         this.t = 0; // touches index
67         this.isInit = false;
68         this.fgColor = null; // main color
69         this.pColor = null; // previous color
70         this.dH = null; // draw hook
71         this.cH = null; // change hook
72         this.eH = null; // cancel hook
73         this.rH = null; // release hook
74         this.scale = 1; // scale factor
75         this.relative = false;
76         this.relativeWidth = false;
77         this.relativeHeight = false;
78         this.$div = null; // component div
79
80         this.run = function () {
81             var cf = function (e, conf) {
82                 var k;
83                 for (k in conf) {
84                     s.o[k] = conf[k];
85                 }
86                 s._carve().init();
87                 s._configure()
88                  ._draw();
89             };
90
91             if (this.$.data('kontroled')) return;
92             this.$.data('kontroled', true);
93
94             this.extend();
95             this.o = $.extend({
96                     // Config
97                     min: this.$.data('min') !== undefined ? this.$.data('min') : 0,
98                     max: this.$.data('max') !== undefined ? this.$.data('max') : 100,
99                     stopper: true,
100                     readOnly: this.$.data('readonly') || (this.$.attr('readonly') === 'readonly'),
101
102                     // UI
103                     cursor: this.$.data('cursor') === true && 30
104                             || this.$.data('cursor') || 0,
105                     thickness: this.$.data('thickness')
106                                && Math.max(Math.min(this.$.data('thickness'), 1), 0.01)
107                                || 0.35,
108                     lineCap: this.$.data('linecap') || 'butt',
109                     width: this.$.data('width') || 200,
110                     height: this.$.data('height') || 200,
111                     displayInput: this.$.data('displayinput') == null || this.$.data('displayinput'),
112                     displayPrevious: this.$.data('displayprevious'),
113                     fgColor: this.$.data('fgcolor') || '#87CEEB',
114                     inputColor: this.$.data('inputcolor'),
115                     font: this.$.data('font') || 'Arial',
116                     fontWeight: this.$.data('font-weight') || 'bold',
117                     inline: false,
118                     step: this.$.data('step') || 1,
119                     rotation: this.$.data('rotation'),
120
121                     // Hooks
122                     draw: null, // function () {}
123                     change: null, // function (value) {}
124                     cancel: null, // function () {}
125                     release: null, // function (value) {}
126
127                     // Output formatting, allows to add unit: %, ms ...
128                     format: function(v) {
129                         return v;
130                     },
131                     parse: function (v) {
132                         return parseFloat(v);
133                     }
134                 }, this.o
135             );
136
137             // finalize options
138             this.o.flip = this.o.rotation === 'anticlockwise' || this.o.rotation === 'acw';
139             if (!this.o.inputColor) {
140                 this.o.inputColor = this.o.fgColor;
141             }
142
143             // routing value
144             if (this.$.is('fieldset')) {
145
146                 // fieldset = array of integer
147                 this.v = {};
148                 this.i = this.$.find('input');
149                 this.i.each(function(k) {
150                     var $this = $(this);
151                     s.i[k] = $this;
152                     s.v[k] = s.o.parse($this.val());
153
154                     $this.bind(
155                         'change blur',
156                         function () {
157                             var val = {};
158                             val[k] = $this.val();
159                             s.val(s._validate(val));
160                         }
161                     );
162                 });
163                 this.$.find('legend').remove();
164             } else {
165
166                 // input = integer
167                 this.i = this.$;
168                 this.v = this.o.parse(this.$.val());
169                 this.v === '' && (this.v = this.o.min);
170                 this.$.bind(
171                     'change blur',
172                     function () {
173                         s.val(s._validate(s.o.parse(s.$.val())));
174                     }
175                 );
176
177             }
178
179             !this.o.displayInput && this.$.hide();
180
181             // adds needed DOM elements (canvas, div)
182             this.$c = $(document.createElement('canvas')).attr({
183                 width: this.o.width,
184                 height: this.o.height
185             });
186
187             // wraps all elements in a div
188             // add to DOM before Canvas init is triggered
189             this.$div = $('<div style="'
190                 + (this.o.inline ? 'display:inline;' : '')
191                 + 'width:' + this.o.width + 'px;height:' + this.o.height + 'px;'
192                 + '"></div>');
193
194             this.$.wrap(this.$div).before(this.$c);
195             this.$div = this.$.parent();
196
197             if (typeof G_vmlCanvasManager !== 'undefined') {
198                 G_vmlCanvasManager.initElement(this.$c[0]);
199             }
200
201             this.c = this.$c[0].getContext ? this.$c[0].getContext('2d') : null;
202
203             if (!this.c) {
204                 throw {
205                     name:        "CanvasNotSupportedException",
206                     message:     "Canvas not supported. Please use excanvas on IE8.0.",
207                     toString:    function(){return this.name + ": " + this.message}
208                 }
209             }
210
211             // hdpi support
212             this.scale = (window.devicePixelRatio || 1) / (
213                             this.c.webkitBackingStorePixelRatio ||
214                             this.c.mozBackingStorePixelRatio ||
215                             this.c.msBackingStorePixelRatio ||
216                             this.c.oBackingStorePixelRatio ||
217                             this.c.backingStorePixelRatio || 1
218                          );
219
220             // detects relative width / height
221             this.relativeWidth =  this.o.width % 1 !== 0
222                                   && this.o.width.indexOf('%');
223             this.relativeHeight = this.o.height % 1 !== 0
224                                   && this.o.height.indexOf('%');
225             this.relative = this.relativeWidth || this.relativeHeight;
226
227             // computes size and carves the component
228             this._carve();
229
230             // prepares props for transaction
231             if (this.v instanceof Object) {
232                 this.cv = {};
233                 this.copy(this.v, this.cv);
234             } else {
235                 this.cv = this.v;
236             }
237
238             // binds configure event
239             this.$
240                 .bind("configure", cf)
241                 .parent()
242                 .bind("configure", cf);
243
244             // finalize init
245             this._listen()
246                 ._configure()
247                 ._xy()
248                 .init();
249
250             this.isInit = true;
251
252             this.$.val(this.o.format(this.v));
253             this._draw();
254
255             return this;
256         };
257
258         this._carve = function() {
259             if (this.relative) {
260                 var w = this.relativeWidth ?
261                         this.$div.parent().width() *
262                         parseInt(this.o.width) / 100
263                         : this.$div.parent().width(),
264                     h = this.relativeHeight ?
265                         this.$div.parent().height() *
266                         parseInt(this.o.height) / 100
267                         : this.$div.parent().height();
268
269                 // apply relative
270                 this.w = this.h = Math.min(w, h);
271             } else {
272                 this.w = this.o.width;
273                 this.h = this.o.height;
274             }
275
276             // finalize div
277             this.$div.css({
278                 'width': this.w + 'px',
279                 'height': this.h + 'px'
280             });
281
282             // finalize canvas with computed width
283             this.$c.attr({
284                 width: this.w,
285                 height: this.h
286             });
287
288             // scaling
289             if (this.scale !== 1) {
290                 this.$c[0].width = this.$c[0].width * this.scale;
291                 this.$c[0].height = this.$c[0].height * this.scale;
292                 this.$c.width(this.w);
293                 this.$c.height(this.h);
294             }
295
296             return this;
297         }
298
299         this._draw = function () {
300
301             // canvas pre-rendering
302             var d = true;
303
304             s.g = s.c;
305
306             s.clear();
307
308             s.dH && (d = s.dH());
309
310             d !== false && s.draw();
311         };
312
313         this._touch = function (e) {
314             var touchMove = function (e) {
315                 var v = s.xy2val(
316                             e.originalEvent.touches[s.t].pageX,
317                             e.originalEvent.touches[s.t].pageY
318                         );
319
320                 if (v == s.cv) return;
321
322                 if (s.cH && s.cH(v) === false) return;
323
324                 s.change(s._validate(v));
325                 s._draw();
326             };
327
328             // get touches index
329             this.t = k.c.t(e);
330
331             // First touch
332             touchMove(e);
333
334             // Touch events listeners
335             k.c.d
336                 .bind("touchmove.k", touchMove)
337                 .bind(
338                     "touchend.k",
339                     function () {
340                         k.c.d.unbind('touchmove.k touchend.k');
341                         s.val(s.cv);
342                     }
343                 );
344
345             return this;
346         };
347
348         this._mouse = function (e) {
349             var mouseMove = function (e) {
350                 var v = s.xy2val(e.pageX, e.pageY);
351
352                 if (v == s.cv) return;
353
354                 if (s.cH && (s.cH(v) === false)) return;
355
356                 s.change(s._validate(v));
357                 s._draw();
358             };
359
360             // First click
361             mouseMove(e);
362
363             // Mouse events listeners
364             k.c.d
365                 .bind("mousemove.k", mouseMove)
366                 .bind(
367                     // Escape key cancel current change
368                     "keyup.k",
369                     function (e) {
370                         if (e.keyCode === 27) {
371                             k.c.d.unbind("mouseup.k mousemove.k keyup.k");
372
373                             if (s.eH && s.eH() === false)
374                                 return;
375
376                             s.cancel();
377                         }
378                     }
379                 )
380                 .bind(
381                     "mouseup.k",
382                     function (e) {
383                         k.c.d.unbind('mousemove.k mouseup.k keyup.k');
384                         s.val(s.cv);
385                     }
386                 );
387
388             return this;
389         };
390
391         this._xy = function () {
392             var o = this.$c.offset();
393             this.x = o.left;
394             this.y = o.top;
395
396             return this;
397         };
398
399         this._listen = function () {
400             if (!this.o.readOnly) {
401                 this.$c
402                     .bind(
403                         "mousedown",
404                         function (e) {
405                             e.preventDefault();
406                             s._xy()._mouse(e);
407                         }
408                     )
409                     .bind(
410                         "touchstart",
411                         function (e) {
412                             e.preventDefault();
413                             s._xy()._touch(e);
414                         }
415                     );
416
417                 this.listen();
418             } else {
419                 this.$.attr('readonly', 'readonly');
420             }
421
422             if (this.relative) {
423                 $(window).resize(function() {
424                     s._carve().init();
425                     s._draw();
426                 });
427             }
428
429             return this;
430         };
431
432         this._configure = function () {
433
434             // Hooks
435             if (this.o.draw) this.dH = this.o.draw;
436             if (this.o.change) this.cH = this.o.change;
437             if (this.o.cancel) this.eH = this.o.cancel;
438             if (this.o.release) this.rH = this.o.release;
439
440             if (this.o.displayPrevious) {
441                 this.pColor = this.h2rgba(this.o.fgColor, "0.4");
442                 this.fgColor = this.h2rgba(this.o.fgColor, "0.6");
443             } else {
444                 this.fgColor = this.o.fgColor;
445             }
446
447             return this;
448         };
449
450         this._clear = function () {
451             this.$c[0].width = this.$c[0].width;
452         };
453
454         this._validate = function (v) {
455             var val = (~~ (((v < 0) ? -0.5 : 0.5) + (v/this.o.step))) * this.o.step;
456             return Math.round(val * 100) / 100;
457         };
458
459         // Abstract methods
460         this.listen = function () {}; // on start, one time
461         this.extend = function () {}; // each time configure triggered
462         this.init = function () {}; // each time configure triggered
463         this.change = function (v) {}; // on change
464         this.val = function (v) {}; // on release
465         this.xy2val = function (x, y) {}; //
466         this.draw = function () {}; // on change / on release
467         this.clear = function () { this._clear(); };
468
469         // Utils
470         this.h2rgba = function (h, a) {
471             var rgb;
472             h = h.substring(1,7)
473             rgb = [
474                 parseInt(h.substring(0,2), 16),
475                 parseInt(h.substring(2,4), 16),
476                 parseInt(h.substring(4,6), 16)
477             ];
478
479             return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")";
480         };
481
482         this.copy = function (f, t) {
483             for (var i in f) {
484                 t[i] = f[i];
485             }
486         };
487     };
488
489
490     /**
491      * k.Dial
492      */
493     k.Dial = function () {
494         k.o.call(this);
495
496         this.startAngle = null;
497         this.xy = null;
498         this.radius = null;
499         this.lineWidth = null;
500         this.cursorExt = null;
501         this.w2 = null;
502         this.PI2 = 2*Math.PI;
503
504         this.extend = function () {
505             this.o = $.extend({
506                 bgColor: this.$.data('bgcolor') || '#EEEEEE',
507                 angleOffset: this.$.data('angleoffset') || 0,
508                 angleArc: this.$.data('anglearc') || 360,
509                 inline: true
510             }, this.o);
511         };
512
513         this.val = function (v, triggerRelease) {
514             if (null != v) {
515
516                 // reverse format
517                 v = this.o.parse(v);
518
519                 if (triggerRelease !== false
520                     && v != this.v
521                     && this.rH
522                     && this.rH(v) === false) { return; }
523
524                 this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v;
525                 this.v = this.cv;
526                 this.$.val(this.o.format(this.v));
527                 this._draw();
528             } else {
529                 return this.v;
530             }
531         };
532
533         this.xy2val = function (x, y) {
534             var a, ret;
535
536             a = Math.atan2(
537                         x - (this.x + this.w2),
538                         - (y - this.y - this.w2)
539                     ) - this.angleOffset;
540
541             if (this.o.flip) {
542                 a = this.angleArc - a - this.PI2;
543             }
544
545             if (this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) {
546
547                 // if isset angleArc option, set to min if .5 under min
548                 a = 0;
549             } else if (a < 0) {
550                 a += this.PI2;
551             }
552
553             ret = (a * (this.o.max - this.o.min) / this.angleArc) + this.o.min;
554
555             this.o.stopper && (ret = max(min(ret, this.o.max), this.o.min));
556
557             return ret;
558         };
559
560         this.listen = function () {
561
562             // bind MouseWheel
563             var s = this, mwTimerStop,
564                 mwTimerRelease,
565                 mw = function (e) {
566                     e.preventDefault();
567
568                     var ori = e.originalEvent,
569                         deltaX = ori.detail || ori.wheelDeltaX,
570                         deltaY = ori.detail || ori.wheelDeltaY,
571                         v = s._validate(s.o.parse(s.$.val()))
572                             + (
573                                 deltaX > 0 || deltaY > 0
574                                 ? s.o.step
575                                 : deltaX < 0 || deltaY < 0 ? -s.o.step : 0
576                               );
577
578                     v = max(min(v, s.o.max), s.o.min);
579
580                     s.val(v, false);
581
582                     if (s.rH) {
583                         // Handle mousewheel stop
584                         clearTimeout(mwTimerStop);
585                         mwTimerStop = setTimeout(function () {
586                             s.rH(v);
587                             mwTimerStop = null;
588                         }, 100);
589
590                         // Handle mousewheel releases
591                         if (!mwTimerRelease) {
592                             mwTimerRelease = setTimeout(function () {
593                                 if (mwTimerStop)
594                                     s.rH(v);
595                                 mwTimerRelease = null;
596                             }, 200);
597                         }
598                     }
599                 },
600                 kval,
601                 to,
602                 m = 1,
603                 kv = {
604                     37: -s.o.step,
605                     38: s.o.step,
606                     39: s.o.step,
607                     40: -s.o.step
608                 };
609
610             this.$
611                 .bind(
612                     "keydown",
613                     function (e) {
614                         var kc = e.keyCode;
615
616                         // numpad support
617                         if (kc >= 96 && kc <= 105) {
618                             kc = e.keyCode = kc - 48;
619                         }
620
621                         kval = parseInt(String.fromCharCode(kc));
622
623                         if (isNaN(kval)) {
624                             (kc !== 13)                     // enter
625                             && kc !== 8                     // bs
626                             && kc !== 9                     // tab
627                             && kc !== 189                   // -
628                             && (kc !== 190
629                                 || s.$.val().match(/\./))   // . allowed once
630                             && e.preventDefault();
631
632                             // arrows
633                             if ($.inArray(kc,[37,38,39,40]) > -1) {
634                                 e.preventDefault();
635
636                                 var v = s.o.parse(s.$.val()) + kv[kc] * m;
637                                 s.o.stopper && (v = max(min(v, s.o.max), s.o.min));
638
639                                 s.change(s._validate(v));
640                                 s._draw();
641
642                                 // long time keydown speed-up
643                                 to = window.setTimeout(function () {
644                                     m *= 2;
645                                 }, 30);
646                             }
647                         }
648                     }
649                 )
650                 .bind(
651                     "keyup",
652                     function (e) {
653                         if (isNaN(kval)) {
654                             if (to) {
655                                 window.clearTimeout(to);
656                                 to = null;
657                                 m = 1;
658                                 s.val(s.$.val());
659                             }
660                         } else {
661                             // kval postcond
662                             (s.$.val() > s.o.max && s.$.val(s.o.max))
663                             || (s.$.val() < s.o.min && s.$.val(s.o.min));
664                         }
665                     }
666                 );
667
668             this.$c.bind("mousewheel DOMMouseScroll", mw);
669             this.$.bind("mousewheel DOMMouseScroll", mw)
670         };
671
672         this.init = function () {
673             if (this.v < this.o.min
674                 || this.v > this.o.max) { this.v = this.o.min; }
675
676             this.$.val(this.v);
677             this.w2 = this.w / 2;
678             this.cursorExt = this.o.cursor / 100;
679             this.xy = this.w2 * this.scale;
680             this.lineWidth = this.xy * this.o.thickness;
681             this.lineCap = this.o.lineCap;
682             this.radius = this.xy - this.lineWidth / 2;
683
684             this.o.angleOffset
685             && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset);
686
687             this.o.angleArc
688             && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc);
689
690             // deg to rad
691             this.angleOffset = this.o.angleOffset * Math.PI / 180;
692             this.angleArc = this.o.angleArc * Math.PI / 180;
693
694             // compute start and end angles
695             this.startAngle = 1.5 * Math.PI + this.angleOffset;
696             this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc;
697
698             var s = max(
699                 String(Math.abs(this.o.max)).length,
700                 String(Math.abs(this.o.min)).length,
701                 2
702             ) + 2;
703
704             this.o.displayInput
705                 && this.i.css({
706                         'width' : ((this.w / 2 + 4) >> 0) + 'px',
707                         'height' : ((this.w / 3) >> 0) + 'px',
708                         'position' : 'absolute',
709                         'vertical-align' : 'middle',
710                         'margin-top' : ((this.w / 3) >> 0) + 'px',
711                         'margin-left' : '-' + ((this.w * 3 / 4 + 2) >> 0) + 'px',
712                         'border' : 0,
713                         'background' : 'none',
714                         'font' : this.o.fontWeight + ' ' + ((this.w / s) >> 0) + 'px ' + this.o.font,
715                         'text-align' : 'center',
716                         'color' : this.o.inputColor || this.o.fgColor,
717                         'padding' : '0px',
718                         '-webkit-appearance': 'none'
719                         }) || this.i.css({
720                             'width': '0px',
721                             'visibility': 'hidden'
722                         });
723         };
724
725         this.change = function (v) {
726             this.cv = v;
727             this.$.val(this.o.format(v));
728         };
729
730         this.angle = function (v) {
731             return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min);
732         };
733
734         this.arc = function (v) {
735           var sa, ea;
736           v = this.angle(v);
737           if (this.o.flip) {
738               sa = this.endAngle + 0.00001;
739               ea = sa - v - 0.00001;
740           } else {
741               sa = this.startAngle - 0.00001;
742               ea = sa + v + 0.00001;
743           }
744           this.o.cursor
745               && (sa = ea - this.cursorExt)
746               && (ea = ea + this.cursorExt);
747
748           return {
749               s: sa,
750               e: ea,
751               d: this.o.flip && !this.o.cursor
752           };
753         };
754
755         this.draw = function () {
756             var c = this.g,                 // context
757                 a = this.arc(this.cv),      // Arc
758                 pa,                         // Previous arc
759                 r = 1;
760
761             c.lineWidth = this.lineWidth;
762             c.lineCap = this.lineCap;
763
764             if (this.o.bgColor !== "none") {
765                 c.beginPath();
766                     c.strokeStyle = this.o.bgColor;
767                     c.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true);
768                 c.stroke();
769             }
770
771             if (this.o.displayPrevious) {
772                 pa = this.arc(this.v);
773                 c.beginPath();
774                 c.strokeStyle = this.pColor;
775                 c.arc(this.xy, this.xy, this.radius, pa.s, pa.e, pa.d);
776                 c.stroke();
777                 r = this.cv == this.v;
778             }
779
780             c.beginPath();
781             c.strokeStyle = r ? this.o.fgColor : this.fgColor ;
782             c.arc(this.xy, this.xy, this.radius, a.s, a.e, a.d);
783             c.stroke();
784         };
785
786         this.cancel = function () {
787             this.val(this.v);
788         };
789     };
790
791     $.fn.dial = $.fn.knob = function (o) {
792         return this.each(
793             function () {
794                 var d = new k.Dial();
795                 d.o = o;
796                 d.$ = $(this);
797                 d.run();
798             }
799         ).parent();
800     };
801
802 }));