Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 segment color?

I'm using the code in this JSFiddle to create a Wheel of Fortune-type game. What I am trying to do is to let the users choose their own colors instead of generating random colors.

I added this to the HTML code:

<select id="colour" name="colour" class="colour">
    <option value=""></option>
    <option value="db0000">Red</option>
    <option value="171515">Black</option>
    <option value="008c0a">Green</option>
</select>

I only edited this part of the Javascript:

 for (i = 0; i < 1; i++) {
     digit[i] = colors[Math.round(Math.random() * 1)];
     //color = color+digit[i];
     color = document.getElementById("colour").value;
     //color = color+digit[i];     
 }

I added color = document.getElementById("colour").value;.

The issue is that the colors do not work properly. It will create the first and second segments with the chosen color properly, but from the third segment, it will add an extra black segment in the wheel.

I almost changed every number in the Javascript to pinpoint the issue and still cannot figure this out.

EDIT:

To make sure I'm not causing any confusion, what I am trying to do is for every new segment added to the wheel, I need to choose a new color.

like image 938
user3806613 Avatar asked Nov 29 '14 05:11

user3806613


People also ask

What is color in html5?

Tip: The 17 standard colors are: aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, orange, purple, red, silver, teal, white, and yellow. All browsers support these 147 colors. Most browsers that are available today.

How do you change the color of one HTML statement?

Put your text in a <span> tag and give it a class name "multicolortext". Then, you need the CSS background-image property to add a gradient background to your text with its "linear-gradient" value, where you put the names of your preferred colors.


1 Answers

This code is an excellent example of why the prototype pattern is much better than the functional pattern. This plugin is defined as a de facto factory function which declares a motley assortment of function-local state vars (startAngle, arc, pplArray, etc.) which are closured by the various methods that implement the plugin, and on top of that, some of the methods are defined as expressionized function literals on a function-local (methods) using the object literal syntax, one method is defined as an expressionized function literal and assigned to a one-off function-local (var rotateWheel) that is hoisted to the top of the scope and closured by various other methods, and the remainder (genHex(), stopRotateWheel(), etc.) are defined as function statements that are hoisted to the top of the scope and closured by various other methods as well. What a mess!

Furthermore, not all of the closure vars really need to be closures, as they don't actually need to maintain state between method calls; arc is a good example of this.

Yet another criticism that can be made here is that some state is actually stored in the DOM tree, which is absurd and unnecessary. I'm referring specifically to params.participants, which is a jQuery selector that locates a <ul> element somewhere in the DOM (doesn't really matter where), whose children <li> elements are iterated to build up the wheel segment text from their innerHTML. This data does not need to be stored on the DOM; it could be stored easily in an array on a class instance.

And then of course there's the classic criticism of the functional pattern that it causes new function objects to be defined for every invocation of the constructor / factory function ($.fn.spinwheel() in this case), whereas the prototype pattern would involve a single definition of each function on the class prototype when the class is being defined.

Anyway, I rewrote the entire plugin using the prototype pattern. The widget is now defined as a global constructor function on window.SpinWheel (of course, it could be moved into a namespace if desired). All instance methods are defined using a uniform syntax by assigning expressionized function literals to the class prototype, SpinWheel.prototype.

In terms of state data, there is a little bit of default config data defined statically on the constructor function (basically I took the $.fn.spinwheel.default_options object in the original code and assigned it to SpinWheel.config, but fixed a spelling error and removed params.participants), then the config object that is passed as an argument to the constructor function is captured (along with the $canvas jQuery wrapper of the canvas node, and its context object) on the instance itself as this.config. Finally, actual mutable instance state is stored as properties on the instance, such as this.segmentTexts (the text for each segment), this.colors (the currently selected segment colors), and this.curAngle (the current angle of the wheel). That's it, really just three kinds of data: static default (fallback) config, instance config, and instance attributes. All methods are uniform in their style of definition, and have identical access to all object data.

The jQuery plugin is now just a thin wrapper around the SpinWheel class; basically, it instantiates it, attaches it to the target canvas (not really necessary unless external code wants to access it after creation), and initializes it. The plugin interface is the same as with the original code, but with the new code, you can also instantiate SpinWheel independently of the jQuery plugin framework, if you wanted to (although, of course, it should be said, the implementation still depends on jQuery being loaded).

Also, just for the heck of it, I added Delete, Clear, and Reset buttons to demonstrate more control over the wheel. You can now delete segments by regex match against the text, clear the entire wheel so you can build it from scratch, and reset it to the initial configuration (although if you haven't configured colors via the initial configuration, they'll be generated randomly again, which will certainly be different from what they were in the initial display; but you can configure initial colors if desired).

Here's the new 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>&nbsp;</span></p>
    </div>

    <div style="clear:both"></div>

</div>

Here's the new IIFE that defines the SpinWheel class:

(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);

And here's the thin wrapper that provides the jQuery plugin interface:

(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);

The instantiation-by-jQuery-plugin code doesn't change (except I've renamed the primary config parameter):

$(document).ready(function() {
    $('.canvas').spinwheel({'segmentTexts':['♈','♉','♊','♋','♌','♍','♎','♏','♐','♑','♒','♓']});
});

Demo:

http://jsfiddle.net/kYvzd/212/

Let me know if you have any questions.

like image 106
bgoldst Avatar answered Oct 20 '22 12:10

bgoldst