Questo codice è un eccellente esempio del motivo per cui il modello di prototipo è molto più meglio del modello funzionale. Questo plugin è definito come una funzione di fabbrica de facto che dichiara un assortimento eterogeneo di vars di stato locale (startAngle
, arc
, pplArray
, ecc.) Che vengono chiusi dai vari metodi che implementano il plug-in e, per di più, alcuni dei metodi sono definiti come valori letterali delle funzioni espressionati su una funzione locale (methods
) utilizzando la sintassi letterale dell'oggetto, un metodo è definito come una funzione letterale espressionizzata e assegnato a una funzione singola locale (var rotateWheel
) che viene issato in cima allo scope e chiuso da vari altri metodi, mentre il resto (genHex()
, stopRotateWheel()
, ecc.) viene definito come istruzioni di funzione che vengono issate in cima allo scope e chiuse da vari ot anche i suoi metodi. Che casino!
Inoltre, non tutte le chiusure di chiusura devono effettivamente essere chiuse, in quanto non devono effettivamente mantenere lo stato tra le chiamate di metodo; arc
è un buon esempio di questo.
Ancora un'altra critica che può essere fatta qui è che qualche stato è effettivamente memorizzato nell'albero DOM, che è assurdo e non necessario. Mi riferisco specificamente a params.participants
, che è un selettore jQuery che individua un elemento <ul>
da qualche parte nel DOM (non importa dove), i cui figli <li>
elementi vengono iterati per creare il testo del segmento di ruota dal loro innerHTML. Questi dati non devono essere memorizzati sul DOM; potrebbe essere memorizzato facilmente in un array su un'istanza di classe.
E poi ovviamente c'è la critica classica del modello funzionale che causa la definizione di nuovi oggetti funzione per ogni invocazione della funzione costruttore/fabbrica ($.fn.spinwheel()
in questo caso), mentre il modello prototipo implicherebbe una singola definizione di ogni funzione sul prototipo di classe quando viene definita la classe.
In ogni caso, ho riscritto l'intero plug-in utilizzando il modello del prototipo.Il widget è ora definito come una funzione di costruzione globale su window.SpinWheel
(ovviamente, potrebbe essere spostato in uno spazio dei nomi se lo si desidera). Tutti i metodi di istanza sono definiti utilizzando una sintassi uniforme assegnando valori letterali delle funzioni espressionati al prototipo di classe, SpinWheel.prototype
.
In termini di dati di stato, c'è un po 'di dati di configurazione di default definiti staticamente sulla funzione di costruzione (fondamentalmente ho preso l'oggetto $.fn.spinwheel.default_options
nel codice originale ed assegnato al SpinWheel.config
, ma fissato un errore di ortografia e rimosso params.participants
), quindi l'oggetto di configurazione passato come argomento alla funzione di costruzione viene acquisito (insieme al wrapper jQuery del nodo dell'area di disegno e al relativo oggetto di contesto) sull'istanza stessa come this.config
. Infine, lo stato dell'istanza mutabile effettiva viene memorizzato come proprietà nell'istanza, ad esempio this.segmentTexts
(il testo per ogni segmento), this.colors
(i colori del segmento attualmente selezionati) e this.curAngle
(l'angolo corrente della ruota). Ecco, in realtà solo tre tipi di dati: configurazione predefinita di default (fallback), configurazione di istanza e attributi di istanza. Tutti i metodi sono uniformi nel loro stile di definizione e hanno accesso identico a tutti i dati dell'oggetto.
Il plug-in jQuery è ora solo un involucro sottile attorno alla classe SpinWheel
; in pratica, lo istanzia, lo collega alla tela di destinazione (non è veramente necessario a meno che il codice esterno non voglia accedervi dopo la creazione) e lo inizializzi. L'interfaccia del plugin è la stessa del codice originale, ma con il nuovo codice è anche possibile creare un'istanza dello SpinWheel
indipendentemente dal framework del plugin jQuery, se lo si desidera (sebbene, ovviamente, si debba dire che l'implementazione dipende ancora da jQuery in fase di caricamento).
Inoltre, solo per il gusto di farlo, ho aggiunto Delete
, Clear
, e Reset
pulsanti per dimostrare un maggiore controllo sul volante. Ora puoi cancellare segmenti con la corrispondenza regolare rispetto al testo, cancellare l'intera ruota in modo da poterla creare da zero e reimpostarla nella configurazione iniziale (anche se se non hai configurato i colori tramite la configurazione iniziale, verranno generati di nuovo in modo casuale, che sarà sicuramente diverso da quello che erano nella visualizzazione iniziale, ma è possibile configurare i colori iniziali se lo si desidera).
Ecco il nuovo HTML:
<div id="main">
<div id="left-column">
<form class="iform" action="#" method="get">
<label for="joiner"></label>
<input id="joiner" name="joiner" class="joiner" placeholder="Please Enter your name" />
<select id="colorer" name="colorer" class="colorer">
<option value="">auto</option>
<option value="db0000">Red</option>
<option value="171515">Black</option>
<option value="008c0a">Green</option>
</select>
<button class="add">Add</button>
<button class="delete">Delete</button>
<button class="spin-trigger">Spin</button>
<button class="clear">Clear</button>
<button class="reset">Reset</button>
</form>
<canvas class="canvas" width="500" height="500"></canvas>
</div>
<div id="right-column">
<p class="winner">The Winner is ... <span> </span></p>
</div>
<div style="clear:both"></div>
</div>
Ecco il nuovo IIFE che definisce la classe SpinWheel
:
(function($) {
// define main SpinWheel constructor function
var SpinWheel = function($canvas, config) {
// validate config
// 1: reject invalids
for (configKey in config) {
if (!config.hasOwnProperty(configKey)) continue;
if (!SpinWheel.config.hasOwnProperty(configKey)) throw 'error: invalid config key "'+configKey+'" in SpinWheel instantiation.';
} // end for
// 2: check for requireds
var requiredParams = ['segmentTexts'];
for (var i = 0; i < requiredParams.length; ++i) {
var requiredParam = requiredParams[i];
if (!config.hasOwnProperty(requiredParam)) throw 'error: missing required config key \''+requiredParam+'\' in SpinWheel instantiation.';
} // end for
// store the per-instance config on the "this" object
this.config = config;
// capture the canvas jQuery object and init the canvas context
// note: there should only ever be one SpinWheel instantiated per canvas, and there's only one canvas manipulated by a single SpinWheel instance
this.$canvas = $canvas;
this.canvasCtx = this.$canvas[0].getContext("2d");
// initialize the segments, colors, and curAngle -- wrap this in a function for reusability
this.reset();
}; // end SpinWheel()
// SpinWheel statics
/* --- please look at the index.html source in order to understand what they do ---
* outerRadius : the big circle border
* innerRadius : the inner circle border
* textRadius : How far the the text on the wheel locate from the center point
* spinTrigger : the element that trigger the spin action
* wheelBorderColor : what is the wheel border color
* wheelBorderWidth : what is the "thickness" of the border of the wheel
* wheelTextFont : what is the style of the text on the wheel
* wheelTextColor : what is the color of the tet on the wheel
* wheelTextShadow : what is the shadow for the text on the wheel
* resultTextFont : it is not being used currently
* arrowColor : what is the color of the arrow on the top
* joiner : usually a form input where user can put in their name
* addTrigger : what element will trigger the add participant
* winnerDiv : the element you want to display the winner
*/
SpinWheel.config = {
'segmentTexts':['1','2','3','4','5','6'],
'colors':[], // we'll allow omitted config colors; will just generate unspecifieds randomly on-the-fly whenever the wheel is reset
'outerRadius':200,
'innerRadius':3,
'textRadius':160,
'spinTrigger':'.spin-trigger',
'wheelBorderColor':'black',
'wheelBorderWidth':3,
'wheelTextFont': 'bold 15px sans-serif',
'wheelTextColor':'black',
'wheelTextShadowColor':'rgb(220,220,220)',
'resultTextFont':'bold 30px sans-serif',
'arrowColor':'black',
'addTrigger':'.add',
'deleteTrigger':'.delete',
'clearTrigger':'.clear',
'resetTrigger':'.reset',
'joiner':'.joiner',
'colorer':'.colorer',
'winnerDiv':'.winner'
};
// SpinWheel instance methods
SpinWheel.prototype.getConfig = function(key) {
if (this.config.hasOwnProperty(key)) return this.config[key]; // per-instance config
if (SpinWheel.config.hasOwnProperty(key)) return SpinWheel.config[key]; // default static config
throw 'error: invalid config key "'+key+'" requested from SpinWheel::getConfig().';
} // end SpinWheel::getConfig()
SpinWheel.prototype.init = function() {
this.setup();
this.drawWheel();
}; // end SpinWheel::init()
SpinWheel.prototype.setup = function() {
var thisClosure = this; // necessary to allow callback functions to access the SpinWheel instance
$(this.getConfig('spinTrigger')).bind('click', function(ev) { (function(ev, target) {
ev.preventDefault();
this.spin();
}).call(thisClosure, ev, this); });
$(this.getConfig('addTrigger')).bind('click', function(ev) { (function(ev, target) {
ev.preventDefault();
//var item = $('<li/>').append($(this.getConfig('joiner')).val());
//$(params.paricipants).append(item);
var $joiner = $(this.getConfig('joiner'));
var text = $joiner.val();
if (text) { // don't add a segment with empty text
var $color = $(this.getConfig('colorer'));
var color = $color.find('option:selected').text();
this.add(text, color);
this.drawWheel();
} // end if
}).call(thisClosure, ev, this); });
$(this.getConfig('deleteTrigger')).bind('click', function(ev) { (function(ev, target) {
ev.preventDefault();
var $joiner = $(this.getConfig('joiner')); // reuse joiner input box
var text = $joiner.val();
if (text) { // don't delete with empty pattern
this.del(new RegExp(text));
this.drawWheel();
} // end if
}).call(thisClosure, ev, this); });
$(this.getConfig('clearTrigger')).bind('click', function(ev) { (function(ev, target) {
ev.preventDefault();
this.clear();
this.drawWheel();
}).call(thisClosure, ev, this); });
$(this.getConfig('resetTrigger')).bind('click', function(ev) { (function(ev, target) {
ev.preventDefault();
this.reset();
this.drawWheel();
}).call(thisClosure, ev, this); });
}; // end SpinWheel::setup()
SpinWheel.prototype.clear = function() {
// clear primary wheel state data
this.segmentTexts = [];
this.colors = [];
this.curAngle = 0;
// also, in case there was a spin in-progress, stop it
this.stopRotateWheel();
}; // end SpinWheel::clear()
SpinWheel.prototype.reset = function() {
// precomputations
var segmentTexts = this.getConfig('segmentTexts');
var colors = this.getConfig('colors');
// copy the configured segment texts to an instance attribute; this distinction is necessary, since we allow the user to manipulate the segments after initial configuration/resetting
this.segmentTexts = segmentTexts.slice();
// generate initial colors
this.colors = [];
for (var i = 0; i < this.segmentTexts.length; ++i)
this.colors.push(colors.length > i ? colors[i] : this.genHexColor());
// initialize curAngle, which must always exist and track the current angle of the wheel
this.curAngle = 0;
// also, in case there was a spin in-progress, stop it
this.stopRotateWheel();
}; // end SpinWheel::reset()
SpinWheel.prototype.add = function(text, color) {
// translate color 'auto' to a generated color
// also take anything invalid as auto
if (!color || color === 'auto')
color = this.genHexColor();
// we just store the text of each segment on the segmentTexts array
this.segmentTexts.push(text);
this.colors.push(color);
}; // end SpinWheel::add()
SpinWheel.prototype.del = function(pattern) {
for (var i = 0; i < this.segmentTexts.length; ++i) {
if (this.segmentTexts[i].match(pattern)) {
this.segmentTexts.splice(i, 1);
if (this.colors.length > i) this.colors.splice(i, 1); // colors array can be short
--i;
} // end if
} // end for
}; // end SpinWheel::del()
SpinWheel.prototype.spin = function() {
// the following are per-spin ad hoc state vars that are initialized for each spin, thus there's no point in storing values for them on the config struct
this.spinAngleStart = Math.random()*10 + 10;
this.spinTimeTotal = Math.random()*3 + 4*1000;
this.spinTime = 0;
this.rotateWheel();
}; // end SpinWheel::spin()
SpinWheel.prototype.genHexColor = function() {
var hexDigits = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
// 6 digits in a hex color spec
var hexColor = '#';
for (var i = 0; i < 6; ++i)
hexColor = hexColor+hexDigits[Math.round(Math.random()*15)];
return hexColor;
}; // end SpinWheel::genHexColor()
SpinWheel.prototype.rotateWheel = function() {
// advance time
this.spinTime += 30;
// check for completion
if (this.spinTime >= this.spinTimeTotal) {
this.finishSpin();
return;
} // end if
// advance angle
var x = this.spinAngleStart - this.easeOut(this.spinTime, 0, this.spinAngleStart, this.spinTimeTotal);
this.curAngle += (x*Math.PI/180);
// redraw
this.drawWheel();
// schedule next rotation
this.spinTimeout = setTimeout(this.rotateWheel.bind(this), 30);
}; // end SpinWheel::rotateWheel()
SpinWheel.prototype.finishSpin = function() {
// stop the rotation timeout chain
this.stopRotateWheel();
// precomputations
var segmentNum = this.segmentTexts.length;
var arc = 2*Math.PI/segmentNum;
// hit segment calc
var degrees = this.curAngle*180/Math.PI + 90;
var arcd = arc*180/Math.PI;
var index = Math.floor((360 - degrees%360)/arcd);
// update the display
this.canvasCtx.save();
this.canvasCtx.font = this.getConfig('resultTextFont');
var text = this.segmentTexts[index];
$(this.getConfig('winnerDiv')).html(text).show();
//canvasCtx.fillText(text, 250 - canvasCtx.measureText(text).width/2, 250 + 10);
this.canvasCtx.restore();
}; // end SpinWheel::finishSpin()
SpinWheel.prototype.stopRotateWheel = function() {
// clear any existing timeout
if (this.spinTimeout) {
clearTimeout(this.spinTimeout);
this.spinTimeout = null;
} // end if
}; // end SpinWheel::stopRotateWheel()
SpinWheel.prototype.drawArrow = function() {
this.canvasCtx.fillStyle = this.getConfig('arrowColor');
var outerRadius = this.getConfig('outerRadius');
this.canvasCtx.beginPath();
this.canvasCtx.moveTo(250-4, 250-(outerRadius+15));
this.canvasCtx.lineTo(250+4, 250-(outerRadius+15));
this.canvasCtx.lineTo(250+4, 250-(outerRadius-15));
this.canvasCtx.lineTo(250+9, 250-(outerRadius-15));
this.canvasCtx.lineTo(250+0, 250-(outerRadius-23));
this.canvasCtx.lineTo(250-9, 250-(outerRadius-15));
this.canvasCtx.lineTo(250-4, 250-(outerRadius-15));
this.canvasCtx.lineTo(250-4, 250-(outerRadius+15));
this.canvasCtx.fill();
}; // end SpinWheel::drawArrow()
SpinWheel.prototype.drawWheel = function() {
// precomputations
var outerRadius = this.getConfig('outerRadius');
var innerRadius = this.getConfig('innerRadius');
var textRadius = this.getConfig('textRadius');
var segmentNum = this.segmentTexts.length;
var arc = 2*Math.PI/segmentNum;
// init canvas
this.canvasCtx.strokeStyle = this.getConfig('wheelBorderColor');
this.canvasCtx.lineWidth = this.getConfig('wheelBorderWidth');
this.canvasCtx.font = this.getConfig('wheelTextFont');
this.canvasCtx.clearRect(0,0,500,500);
// draw each segment
for (var i = 0; i < segmentNum; ++i) {
var text = this.segmentTexts[i];
var angle = this.curAngle + i*arc;
this.canvasCtx.fillStyle = this.colors[i];
this.canvasCtx.beginPath();
// ** arc(centerX, centerY, radius, startingAngle, endingAngle, antiClockwise);
this.canvasCtx.arc(250, 250, outerRadius, angle, angle + arc, false);
this.canvasCtx.arc(250, 250, innerRadius, angle + arc, angle, true);
this.canvasCtx.stroke();
this.canvasCtx.fill();
this.canvasCtx.save();
this.canvasCtx.shadowOffsetX = -1;
this.canvasCtx.shadowOffsetY = -1;
this.canvasCtx.shadowBlur = 1;
this.canvasCtx.shadowColor = this.getConfig('wheelTextShadowColor');
this.canvasCtx.fillStyle = this.getConfig('wheelTextColor');
this.canvasCtx.translate(250 + Math.cos(angle + arc/2)*textRadius, 250 + Math.sin(angle + arc/2)*textRadius);
this.canvasCtx.rotate(angle + arc/2 + Math.PI/2);
this.canvasCtx.fillText(text, -this.canvasCtx.measureText(text).width/2, 0);
this.canvasCtx.restore();
this.canvasCtx.closePath();
} // end for
this.drawArrow();
}; // end SpinWheel::drawWheel()
SpinWheel.prototype.easeOut = function(t,b,c,d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}; // end SpinWheel::easeOut()
// export the class
window.SpinWheel = SpinWheel;
})(jQuery);
Ed ecco l'involucro sottile che fornisce l'interfaccia plugin per jQuery:
(function($) {
// spinwheel() jQuery plugin loader
$.fn.spinwheel = function(config) {
var $canvas = this; // the "this" object is the jQuery object that wraps the canvas HTML DOM object
// create a new SpinWheel instance and store it on the canvas DOM object, which is attached to the DOM tree, so it will be accessible by external code
var spinWheel = new SpinWheel($canvas, config);
$canvas[0].spinWheel = spinWheel;
// initialize it
spinWheel.init();
}; // end $.fn.spinwheel()
})(jQuery);
Il codice di istanziazione per jQuery-plugin non cambia (eccetto Ho rinominato il parametro di configurazione primario):
$(document).ready(function() {
$('.canvas').spinwheel({'segmentTexts':['♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓']});
});
Demo:
http://jsfiddle.net/kYvzd/212/
fatemi sapere se avete domande.
Hey Ho aggiornato alcuni di essi fammi sapere se vuoi collaborare a questo http://jsfiddle.net/kYvzd/173/ – Rafael
@Rafael, ciò che hai fatto cambia il colore dell'intera ruota! ma ho bisogno di cambiare il colore dei segmenti/colonne .. quindi per ogni nuovo segmento aggiunto alla ruota, ho bisogno di scegliere un nuovo colore. – user3806613
Capisco ma non sono riuscito a identificare dove era definito il colore del segmento della ruota. Per favore mostrami dov'è. – Rafael