Wednesday, January 18, 2012

a simple jquery slot machine effect

Update! This is now a proper github project with a lot of details filled in... More Information Here!


For work they wanted a "juicy" (I've been promoting use of that term for pleasingly physical or generally pleasant animations) way of showing a randomly generated amount of virtual currency assigned to the user. I suggested a slot machine-effect, and it came out really nicely, a widget with a very high "coolness to code complexity" ratio.

Here's another version of what I quickly came up with, click to spin!


The final product for work was a bit different, a single little rotating slot with graphical icons and more polish,  (including a nice fade in behind once the final amount was displayed). But for messing about purposes, I simplified the display, threw in a few extra windows, and made the results A B C D or F.

You can see the html, js, and css here.

There's very little too it, really: each slot consists of an outer class "slots" that provides the border and has a hidden overflow. Inside each slot is a (very tall) "wrapper", and then the js code adds a bunch of divs of class "slot", each as wide as the wrapper and each containing a random letter.  When it's time to let things "spin", we just animate the wrapper's margin-top property to a multiple (7) of the negative height of a single slot. Some of the secret sauce is the easing, "easeOutElastic" from the jQuery easing module... I wrote about that easing stuff earlier.

If the user clicks again, we repeat the process, adding in extra divs of class slot, and moving an increased seven times the height from where it was before. There are some little tricks going on here: we add more divs than we actually need because the elastic animation overshoots a bit (and of course, it's not really circular like a real slot machine would be), it took a while to realize that you should just move a small # of slots, and I added some randomness to the length of time of the animation, so it didn't all move in lockstep. It would be easy to tweak the timing so slots 2 and 3 started after 1, for a kind of 1....2...3! effect. (Another thing I added for this demo was the use of the "stop()" command... otherwise repeated spin clicks would have to wait until the earlier spins were fully settled. stop() has some arguments I missed when I first learned about it, they didn't come in handy here, but let you control if the element/animation queue finishes the pending animation(s) or just stops cold to start the new one. (Actually it turns out I needed to stop() the animation, including the parameter to jump to the final position, before reading the old position to set the new goal.)

Stuff like this is a great argument for jQuery/CSS UI engineers to err on the side of building things from scratch rather than relying on gluing together pre-existing bits. If I had only used other people's slideshow modules, rather than realizing it was a simple matter of wrapper divs, overflow:hidden, and animating margins, I wouldn't know enough to put this together... but it's really quite simple when you treat jQuery and CSS as the empowering technologies that they are.

BONUS PROTIP: This animation read the old margin-top in order to set the new one
jqo.css("margin-top"); //jqo is the jQuery object
That returns a value like "100px". To get rid of the "px" and treat that as an integer, parseInt works very well, it's designed to read numbers at the start of a string and then stop when it encounters non-digits:
var marginTop = parseInt(jqo.css("margin-top"), 10);
(The 10 after makes sure that we keep things in base 10, if the number string started with, say, a "0", strange  things might result.)
Of course after you've done your math you need to append the "px" again, but you know that already, right?

20 comments:

  1. Hey there! Love this effect. Wondering if it could be easily modified to land on preset numbers? Thanks!

    ReplyDelete
  2. I see the 'time' value that gets passed to the animation portion of the code, but no matter how high I make it, doesn't seem to change the length of time the reels spin.....

    ReplyDelete
  3. How do you use images in the slots.

    Exemple please...

    ReplyDelete
  4. Very interesting!

    Do you have any thoughts on how to use differing 'var opts' for each of the "reels?"

    So, for example...
    Reel 1 could have ['A','B','C','D','F'];
    Reel 2 could have ['G','H','I','J'];
    and Reel 3 could have ['1','2','3','4','5','6','7','8'];

    ReplyDelete
  5. And for winner script plz ? if(document.getElementById('slots_a') == document.getElementById('slots_n') ?

    ReplyDelete
  6. Interesting code but also need the code for the winner, you have any idea on how to make it happen?

    ReplyDelete
  7. Hi, is it possible to make a specific combination ? For example, if the user plays between 12 and 14 PM, he will win all the time. Thanks

    ReplyDelete
  8. Interesting theory about this combination, but I think that to win at slot machine only good luck could help.

    ReplyDelete
  9. I have some difficulty to understand the magic.
    While trying to change the values to a relative unit (vw instead of pixels for font-size) I run into the problem that the spinning doesn't always stop at the right position. I tried and tried but no, I couldn't make it happen.

    (temporary) example at http://tst.armut.cash/

    JS:
    function addSlots(jqo) {
    for (var i = 0; i < 75; i++){ /* Orig: 15 = 3 slots à 5 entries ? */
    var ctr = Math.floor(Math.random()*opts.length);
    jqo.append(""+opts[ctr]+"");


    }
    }

    function moveSlots(jqo) {
    var time = 7500;
    time += Math.round(Math.random()*1000);
    jqo.stop(true,true);

    var marginTop = parseInt(jqo.css("margin-top"), 10)

    marginTop -= (7 * 100) /* Orig: 7 * 100 */

    jqo.animate(
    {"margin-top":marginTop+"px"},
    {'duration' : time, 'easing' : "easeOutElastic"});

    }

    CSS:
    .slots {
    height: 6.3vw; /* Fensterhöhe der einzelnen slots */
    width: 53.3vw;
    font-size: 30px; /* fall back */
    font-size: 4.3vw;
    padding-left: 1.4vw;
    overflow:hidden;
    border:1px solid black;
    float:left;
    }

    .slots .wrapper {
    margin-top: 0.2vw;
    }

    .slots .slot {
    /*height: 3.3vw;*/ /* Höhe der auch unsichtbaren Zeilen */
    text-align:left
    }

    @media screen and (max-width: 767px) {

    .slots {
    height: 9.5vw; /* Fensterhöhe der einzelnen slots */
    width: 80vw;
    font-size: 45px; /* fall back */
    font-size: 6.5vw;
    padding-left: 1.4vw;
    overflow:hidden;
    border:2px solid black;
    float:left;
    }

    .slots .wrapper {
    margin-top: 0.3vw;
    }

    .slots .slot{
    /*height: 5vw;*/ /* Höhe der auch unsichtbaren Zeilen */
    text-align:left
    }

    Any hints on what magic wand to wave or what values to change?

    ReplyDelete
    Replies
    1. Did you have any luck with that? Your example page seems to work?

      Delete
  10. This comment has been removed by the author.

    ReplyDelete
  11. See this update! http://kirkdev.blogspot.com/2015/09/ezslots.html

    ReplyDelete
  12. Hi, Very cool plugin.

    Thanks for the update. I was wondering with the image array version, How could you load all the array at the beginning and stick with the array. So that if you had 20 pictures you would just have those 20 pics to play with. Ideally I'd prefer not to use [this.howManySymbolsToAppend = 1; //how many symbols each spin adds]. I would rather have a mechanism that makes every images appear at least once before showing an image twice. I don't know if that make sense though.

    Thank you Kirk Is

    ReplyDelete
    Replies
    1. Yeah, that would be a moderately complex change to how pieces are added on... I'm not sure there's enough demand for it for me to work on it, but I'd look at a pull request for it :-)

      Delete
  13. Hi, Very nice plugin :)

    How can I set all slots to zero when page is loaded for the first time?

    Thanks.

    ReplyDelete
    Replies
    1. https://github.com/kirkjerk/ezslots -- I updated things here so there's a "startingSet" argument.

      Delete
  14. This comment has been removed by a blog administrator.

    ReplyDelete
  15. This comment has been removed by a blog administrator.

    ReplyDelete