user1406440
user1406440

Reputation: 1645

Rotating div onClick - cycle through various degrees (wheel of fortune)

The effect I'm trying to achieve is upon clicking a button a circle made up of 8 triangles spins around. When it stops the triangle at the top of the circle is selected. Then a fancybox overlay relative to the selected segment is opened (this last part doesn't work yet). The order the segments are selected appears random to the user but it can be preset as I've done in the CSS (currently: 3rd > 6th > 1st > 8th > 2nd > 5th > 7th > 4th).

I've created a codepen to show my current progress on this which you can see by visiting the link below as its quite hard to explain.

http://codepen.io/moy/pen/DrbAL

On clicking the 'Spin' button the circle rotates 3 times (1080 degrees) + an additional 270 degrees so the 3rd list-item is positioned at the top of the div - this works fine.

My 1st question is: On the 2nd click of the button, the circle needs to rotate 3 times again and then a bit more to position the next selected list-item/segment (6) at the top of the div and so on for each of the remaining items. Is there a way to do this without having to add 1080 to the amount the circle has already rotated?? So the value is always 1080deg + the additional amount the circle needs to rotate to position the relevant segment at the top of the div. Otherwise the last item's value is going to be something like 10000 degrees! If this has to be done I guess I'll have to live with it and get my maths hat on.

The 2nd question is: Once the circle has finished rotating, how can I display the related fancybox overlay? The ID's are currently the title of each overlay but could easily be overlay-1, overlay-2, etc to match the classes of spin-1, spin-2 set on the spinning wheel after each spin.

Thanks, hope someone can help!

Upvotes: 2

Views: 4411

Answers (1)

JFK
JFK

Reputation: 41143

I found this question very interesting because I worked on a similar project some time ago so this is my input. First, I would suggest you to use the Julian Shapiro's velocity plugin for your animations, which will give you some of the following advantages :

  • Less and cleaner code without the clutter (for both css and javascript)
  • Javascript animations that are (arguable) faster than CSS3 animations. Read this for more
  • You don't have to toggle css classes back and forth to achieve your animations (and you don't have to manually pre-set and calculate how many degrees to spin within your css rules)

With velocity, spinning your wheel can be done as simple as :

$(".wheel").velocity({
    rotateZ : "-" + {degree} + "deg" // "-" minus to spin counterclockwise, remove otherwise
});

The velocity plugin has a full set of API options that you can explore here. Of course, you need an algorithm to calculate the new value of the degree variable on every click.

Now, if I understood correctly you have two questions :

  • How to calculate the amount of degrees to spin to wheel and the stop in the next wanted position.
  • How to determine what was the selected segment (on top of the wheel) regardless whether it was preset or randomly selected and then launch the corresponding fancybox.

I will start answering the second question with the following algorithm :

_target = (_deg - (360 * parseInt(_deg / 360))) / 45;

where _target is the javascript index of the selected segment and _deg the amount of degrees we had spun the wheel from the previous position to the current.

How it works :

Following your example above, the initial state of your wheel is segment No. 1 (Commercial Awareness) and from there you need to move to No. 3 (Team Work), so it means spinning the wheel 3 times (1080 degrees) + 2 more segments of 45 degrees each (90 degrees) = 1170 degrees rotation.

so, applying that value to the algorithm :

_target = (1170 - (360 * parseInt(1170 / 360))) / 45;
_target = (1170 - (360 * parseInt(3.25))) / 45;
_target = (1170 - (360 * 3)) / 45;
_target = (1170 - (1080)) / 45;
_target = (90) / 45;
_target = 2;

If you consider that in javascript the first element of a group has index=0, the second is index=1, the third is index=2, etc. _target = 2 actually points to the third segment (Team Work). This algorithm works regardless how many degrees you have spun the wheel.

Then firing fancybox is simple:

$(".fancybox").eq(_target).trigger("click");

the code above assumes you have initialized fancybox somewhere in your script like

$(".fancybox").fancybox({ // API options });

Now, for the first question I see 3 possible algorithms that you can set in a separated function to call after button click :

Sequential :

Returns a patterned order (advances 3 segments each click) like 1,4,7,2,5,8,3,6 ... then loops. Every segment is shown at least once before the loop so :

function ordSequential() {
    // order : 1,4,7,2,5,8,3,6 ... then loops
    return _deg = _deg + (45*3) + 1080;
};

it assumes an initial value of _deg = 0. Notice you can always change the factor 3 to alter the pattern.

So having the amount of degrees we will spin the wheel, we can use velocity to :

  • reset all segments' opacity to 1
  • spin the wheel
  • set selected segment's opacity to 0.4
  • fire the selected segment's corresponding fancybox

the full code

// sequential order
// advances 3 segments each click
var _target, _deg = 0;
function ordSequential() {
    // order : 1,4,7,2,5,8,3,6 ... then loops
    return _deg = _deg + (45*3) + 1080;
};
jQuery(document).ready(function ($) {
    $(".skills-wheel .btn").on("click", function (e) {
        // select algorithm sequential, random or preset :
        ordSequential();
        _target = (_deg - (360 * parseInt(_deg / 360))) / 45;
        // start animation
        // reset opacity of all segments to 1
        $(".fancybox").parent("li").velocity({
            opacity: 1
        }, {
            duration: 100,
            complete: function () {
                $(".wheel").velocity({
                    rotateZ: "-" + _deg + "deg"
                }, {
                    duration: 3000,
                    complete: function (elements) {
                        // after spinning animation is completed, set opacity of target segment's parent
                        $(".fancybox").parent("li").eq(_target).velocity({
                            opacity: 0.4
                        }, {
                            duration: 100,
                            // after opacity is completed, fire targeted segment in fancybox
                            complete: function () {
                                $(".fancybox").eq(_target).trigger("click");
                            } // third animation completed
                        }); // nested velocity 2
                    } // second animation completed
                }); // nested velocity 1
            } // first animation completed
        }); // velocity
        return false;
    }); // click

    // initialize fancybox
    $(".fancybox").fancybox({
        maxWidth: "85%"
    });
}); // ready

See JSFIDDLE

Notice the use of nested* velocity functions inside its complete callback

* EDIT : if you are using Velocity's UI pack, you could use the function .RunSequence() to make nested animation sequences much more manageable. See updated JSFIDDLE using this method. Make sure you use the latest version of Velocity.js and UI pack (v1.1.0 as today [Oct 8, 2014]) http://cdnjs.com/libraries/velocity

Preset :

Returns a preset order as in your example above

3rd > 6th > 1st > 8th > 2nd > 5th > 7th > 4th

The code is basically the same but the algorithm a little bit more complex. First we need to preset the order within an array along with other support variables:

var presetPos = [3, 6, 1, 8, 2, 5, 7, 4],
    presetInit = true,
    spin_count = 0;

Notice the presetPos array contains the actual segment number and not the javascript index. Also notice the use of the flag presetInit, which tells if the wheel is in its initial state (we'll switch it to false after the first spin)

The sauce of the preset algorithm is to calculate the difference (in degrees) between the current and the next segment in the array so we will know how many degrees the wheel need to spin :

function ordPreset() {
    // initial state?
    if (presetInit) {
        presetInit = false;
        return _deg = _deg + ((presetPos[spin_count] - 1) * 45) + 1080;
    } else {
        var _current = presetPos[spin_count];
        var _next = presetPos[spin_count > 6 ? 0 : spin_count + 1];
        var _diff = _next - _current;
        spin_count = spin_count > 6 ? 0 : ++spin_count;
        return _deg = _deg + (_diff * 45) + 1080;
    };
};

See JSFIDDLE

Random :

Returns a random order in the 0-7 range.

The random algorithm is pretty simple. See this for reference.

function ordRandom() {
    return _deg = _deg + ((Math.floor(Math.random() * (8 - 1 + 1)) + 0) * 45) + 1080;
};

However the logic inside the click method is a little bit more complex. In your ideal world the selected segment should be completely random but never select a segment that has been used before. Once all 8 have been selected that would be the end (the button will stop spinning the wheel).

The are several factors to consider : First, we need an array to tell what segments have never been used.

var _index = [0,1,2,3,4,5,6,7];

Notice this array is about the index of each segment.

Additionally we need a flag that let us loop (using while) through the random selection until we find one index that has never been used.

var _repeatRandom = true;

Then, it's important to remove the random selected index from the array using the .splice() method to make sure we won't use it again.

the code for the click event :

$(".skills-wheel .btn").on("click", function (e) {
    // select algorithm sequential, random or preset :
    // if random order, don't repeat the same
    _repeatRandom = _index.length == 0 ? false : true;
    // loop until find one that has never been used
    while (_repeatRandom) {
        ordRandom();
        _target = (_deg - (360 * parseInt(_deg / 360))) / 45;
        var _inArray = $.inArray(_target, _index);
        if (_inArray > -1) {
            // target is in the array
            _repeatRandom = false; // break while loop
            _index.splice(_inArray, 1); // remove segment from array so it won't be repeated

            // start animation
            // reset opacity of all segments to 1
            $(".fancybox").parent("li").velocity({
                opacity : 1
            }, {
                duration : 100,
                complete : function () {
                    $(".wheel").velocity({
                        rotateZ : "-" + _deg + "deg"
                    }, {
                        // addtional settings and callback
                        duration : 3000,
                        complete : function (elements) {
                            // after spinning animation is completed, set opacity of target segment's parent
                            $(".fancybox").parent("li").eq(_target).velocity({
                                opacity : 0.4
                            }, {
                                duration : 100,
                                // after opacity is completed, fire targeted segment in fancybox
                                complete : function () {
                                    $(".fancybox").eq(_target).trigger("click");
                                } // third animation completed
                            }); // nested velocity 2
                        } // second animation completed
                    }); // nested velocity 1
                } // first animation completed
            }); // velocity

        }; // if
    }; // while
    return false;
}); // click

See JSFIDDLE

LAST NOTES :

  • I removed the unnecessary css selectors and transform effects (.wheel.spin-1, .wheel.spin-2, .wheel.spin-3 , etc.)
  • there are many settings (like duration of the animations, etc.) you may want to adjust to your preferences and needs.

Upvotes: 7

Related Questions