How to use JavaScript to create a spinning wheel that stops automatically at the center of a randomly selected slice

Building on the concept of creating a spinning image with CSS and jQuery, I am looking to take it a step further. In my project, a seven-part wheel spins and the correct values are displayed when it stops spinning. However, I have a specific requirement - I want the wheel to always stop at the center of each section. Currently, the wheel stops randomly, sometimes not on any content. To achieve this, I believe some calculations are needed to determine the center point of each section. How can I incorporate this math into the script to ensure the wheel stops at the center every time?

This is what I have managed to create so far...

<html>
<style>
  .roulette_center {
      position: absolute;
      top: 255px;
      left: 258px;
      cursor: pointer;
    
  }
  .roulette_center:hover {
    transform: scale(1.01);
    
  }

  .roulette_wheel{
    touch-action:auto;pointer-events:painted
  }
</style>
<body>
<div style="z-index: 0; position: relative">
  <span  style="position: absolute; top: 0px; left: 360px; font-weight:700;font-size:24px;" id="score">&nbsp;</span>
  <img class="roulette_wheel" src="https://www.dropbox.com/s/6kp3fmtp72aj3vy/wellness-wheel.png?raw=1" />
  <img class="roulette_center" src="https://www.dropbox.com/s/52x8iyb1nvkjm0w/wheelCenter.png?raw=1" onclick="roulette_spin(this)" onfocus="transform()">
  <div style="position: absolute; top: 30px; left: 350px; background-color: red; width: 1px; height: 60px;"></div>

</div>

<script>
  var force = 0;
  var angle = 0;
  var rota = 1;
  var inertia = 0.985;
  var minForce = 15;
  var randForce = 15;
  var rouletteElem = document.getElementsByClassName('roulette_wheel')[0];
  var scoreElem = document.getElementById('score');

  var values = [
    "Spititual", "Emotional", "Intellectual", "Physical", "Social", "Environmental", "Financial"
  ].reverse();

  function roulette_spin(btn) {
    // set initial force randomly
    force = Math.floor(Math.random() * randForce) + minForce;
    requestAnimationFrame(doAnimation);
  }

  function doAnimation() {
    // new angle is previous angle + force modulo 360 (so that it stays between 0 and 360)
    angle = (angle + force) % 360;
    // decay force according to inertia parameter
    force *= inertia;
    rouletteElem.style.transform = 'rotate(' + angle + 'deg)';
    // stop animation if force is too low
    if (force < 0.05) {
      // score roughly estimated
      scoreElem.innerHTML = values[Math.floor(((angle / 360) * values.length) - 0.5)];
      return;
    }
    requestAnimationFrame(doAnimation);
  }
</script>
</body>
</html>

Answer №1

Here you will find a unique solution for achieving elastic-like snapping to the center of the stopping slice.

This approach utilizes an attractor concept to adjust the rotation speed based on a target (the center of the closest slice). This methodology involves vanilla JavaScript and does not rely on CSS transitions.

The strength of the force pulling towards the attractor increases as you get closer to it. The force is calculated as the inverse of the distance to the attractor, with safeguards in place to prevent extreme values when the distance is close to zero (or exactly zero).

This solution is appealing because it simulates the necessary behavior through simple artificial physics (representing wheel inertia and a magnetic effect beneath the wheel slices), allowing the physics to naturally settle after an initial random push.

You have the flexibility to experiment with the physical parameters and stopping conditions, but caution is advised as numerical instabilities can arise easily.

UPDATE:

  • Controls have been added to the code snippet to facilitate experimentation and comprehension of the physical settings (slider boundaries set to "reasonable" limits)
  • A bug has been fixed where clicking multiple times on the center button spawned multiple animation handlers concurrently (notably when repeatedly clicking without waiting for the wheel to stop)
  • The entire functionality now resides within a self-called function to prevent polluting the global scope with variables (the HTML attribute onclick="roulette_spin(this)" has been removed, and
    document.getElementsByClassName('roulette_center')[0].addEventListener('click', roulette_spin);
    has been added to bind the localized handler (roulette_spin) to the wheel center)

UPDATE 2:

  • Detection capability for clicked slice has been implemented

  • If the wheel spins without a specific slice being clicked, it will halt according to the initial velocity and inertia, snapping to the center of a random slice

  • When a slice is chosen while the wheel is spinning, the speed will be adjusted so that the wheel halts very close to the targeted slice; ensuring that forcing the attractor to the clicked slice results in a stop on that particular slice. For mathematicians out there: the current formula might lack precision, and any enhancements are welcome to calculate an accurate speed correction for stopping. Despite its imperfections, it delivers satisfactory results...

  • In instances where the wheel is stationary, selecting a slice will animate the wheel towards the chosen slice. This feature is notable for high friction (low inertia) scenarios, where the wheel swiftly adjusts position and decelerates smoothly. It emulates a rotating menu - potentially aligning with the original poster's objectives.

(For better visibility, view the snippet in full-page mode. Note: the current state does not support multiple clicks on a slice during a spin, leading to a failure. Once a slice is clicked, wait for the wheel to come to a complete stop. Although rectifying this issue was rewarding, I've invested substantial time into this response due to the lockdown restrictions :D)

Answer №2

After some experimentation, I decided to let CSS handle the rotation transition by setting a randomized angle of rotation based on a floored random value. The result is that the animation stops precisely at the center of the pin. It's now up to you to determine where it landed and assign a score.

var force = 0;
var angle = 0;
var rota = 1;
var inertia = 0.985;
var minForce = 15;
var randForce = 15;
var rouletteElem = document.getElementsByClassName('roulette_wheel')[0];
var scoreElem = document.getElementById('score');

var values = [
  "Spititual", "Emotional", "Intellectual", "Physical", "Social", "Environmental", "Financial"
].reverse();

function roulette_spin(btn) {
  var additionalRotation = Math.floor(Math.random() * values.length) * 360
  angle += additionalRotation;
  rouletteElem.style.transform = 'rotate(' + angle + 'deg)';
}
.roulette_center:hover {
  transform: scale(1.01);
}
.roulette_wheel {
  transition: ease-in-out 3s transform;
}
<div style="z-index: 0; position: relative">
  <span style="position: absolute; top: 0px; left: 360px; font-weight:700;font-size:24px;" id="score">&nbsp;</span>
  <img class="roulette_wheel" src="https://www.dropbox.com/s/6kp3fmtp72aj3vy/wellness-wheel.png?raw=1" />
  <img class="roulette_center" style="position:absolute;left:2px;cursor:pointer;" src="https://www.dropbox.com/s/52x8iyb1nvkjm0w/wheelCenter.png?raw=1" onclick="roulette_spin(this)" onfocus="transform()">
  <div style="position: absolute; top: 30px; left: 350px; background-color: red; width: 1px; height: 60px;"></div>

</div>

Answer №3

To ensure your angle adjustments remain within your current function while utilizing your scoring system effectively, you can determine the closest 1/7th position relative to your current angle at any given moment. This allows you to gauge how near or far you are from a 1/7th segment at any point in time. Assuming your graphic is configured to commence at the top 1/7th and in the middle of the initial section at the top, you can utilize the angle_difference method to manipulate your angle variable accordingly. For instance, you could gradually approach the nearest 1/7th segment when force < 1.0 by dividing the angle_difference into incremental steps instead of immediately adjusting your new angle with (angle + force) % 360.

Here's an illustrative code snippet:

if (force < 1.0) {
    closest_seventh = Math.round(7*(angle/360))*(1/7);
    closest_seventh_angle = 360 * closest_seventh;
    angle_difference = abs(angle - closest_seventh);
    angle_step = angle_difference / 10; // arbitrarily chosen number of steps
    if (angle - closest_seventh < 0) {
        angle = angle - angle_step;
    } else {
        angle = angle + angle_step;
    }
    force = force - (0.5 / 10);
    if (force < 0.05) {
      // approximate score calculation
      scoreElem.innerHTML = values[Math.floor(((angle / 360) * values.length) - 0.5)];
      return;
    }
    requestAnimationFrame(doAnimation);
        

You can customize the arbitrary values as needed to achieve the desired animation effect, but this concept encompasses:

  1. Awaits until force diminishes to a level before scoring
  2. Determines the closest 1/7th segment once force drops below 1.0
  3. Evaluates the distance to that segment
  4. Calculates the degree increment required for the image to rotate towards the target
  5. Adjusts both force and angle based on the specified number of steps (determined by you), leading to rotation until force reaches below 0.5 and a score is attained.

Some minor tweaks may be necessary when altering the force value for cases where Force > 1.0, but this approach offers a fresh perspective on reaching the central area of your wheel segments.

UPDATE

I overlooked one line of code involving multiplying the closest 1/7th value by 360 to identify the target angle for positioning within the pie piece's center, which has now been incorporated into the code.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using CSS3 translate will result in children becoming relatively positioned

I am facing an issue with a sidebar that contains 2 divs. <div class="sectionsContainer clearfix"><-- Sidebar --> <div class="leftSection pull-left"> <p>Maor</p> </div> <div class="rightSection pu ...

jQuery Each function malfunctioning in Internet Explorer 9

I'm currently working on a piece of code to implement validation for a form using an on click function. $(this).parent().find('input[type=text]:required').each(function() { alert('each'); if($(this).val() == &a ...

Rails: sending a controller object back to the AJAX requester

When I make an AJAX call to a Rails controller, retrieve a record, and then pass that record back to the AJAX function, I encounter a strange issue. Here is my AJAX request written in CoffeScript: jQuery -> $.ajax type: 'GET', url: ...

Having trouble loading script files with JSDOM in NodeJS?

I'm currently experimenting with loading an HTML page in jsdom to generate graphs, but I'm facing challenges in getting the JavaScript to execute. Here's the HTML page I'm trying to load, which simply renders a basic graph: <html&g ...

Adjusting the color of a value in justGage requires a few simple steps to

Is it possible to modify the color and design of the text in the Value parameter of justGage after creating the gauge? My goal is to change the text color to blue with an underline to give it a link-like appearance. Appreciate your assistance. ...

magnify within a div with a set width

Looking for a way to make a div both zoomable and scrollable within a fixed-width container using transform: scale(aScaleRatio) for zooming in/out. Check out this demo for achieving the zoom effect. However, the inner div seems to have trouble aligning to ...

Transform a Linear Gradient in CSS into a Stylish Bootstrap Button in HTML

I've crafted an American football field using only CSS. In my Codepen sandbox, you can find the code (I'm utilizing ReactJS). What I'm aiming to achieve is to convert each -webkit-linear-gradient into a clickable button that logs its positi ...

Attempting to retrieve div measurements in vh/vw units rather than pixels

I'm currently working on an app where I want the divs to be resizable. In order to achieve this, I need to store the user's changes in my database. My main concern is how can I convert the units provided in pixels (px) to a different unit of mea ...

The Android WebView experiencing issues with the functionality of the Hamburger Menu

I'm facing an unusual issue with the Android WebView in my app. The hamburger menu on my website works perfectly fine on my mobile browser, but when I try to open it in the Android WebView, it does not fully expand to show the menu items. I have trie ...

Vertical alignment can be a challenge with Bootstrap flex items

I’m currently facing an issue with centering the elements within the <main> tag. There seems to be a mysterious gap between the <h1> and <h3> elements, and I can’t seem to pinpoint the root cause of this spacing anomaly. Any insights ...

What could be causing the issue with hammer.js swiping functionality on iPhones?

Looking for assistance from those experienced with hammer.js, as I'm facing an issue with swiping/scrolling on iPhones. I have been developing a web app using 8thwall and hammer.js for swipe and scroll functionality. While testing on my Samsung Galax ...

Techniques for positioning text beside an image in a floating block

Despite experimenting with various display settings such as block and inline for text, I am still facing issues. Please take a look at my website: ...

Is there a way to restrict jQuery Backstretch to only display on the homepage?

I am facing an issue with the jQuery function "Backstretch" on my blog. I only want it to be displayed on the homepage but the black background image seems to blend in with the slideshow on other pages. Can someone suggest a way to restrict the slideshow ...

Smooth Slide Show Scrolling with Mouse Wheel Within a Container

My slick carousel with vertical scroll is causing issues as it scrolls the entire page. I am specifically looking to have it scroll within a particular div, such as the product section div. Any suggestions on how to achieve this? Html Code: <s ...

How do I go about replacing text in the CKEditor?

Hey there, I've developed a custom plugin for CKEditor to convert selected text to either uppercase or lowercase. Below is the implementation: var elementNode = editor.getSelection().getStartElement(); var selectedText = editor.getSelection().g ...

The relative pathway is functional for displaying images in the CSS file, however, it does not work in the

Okay, I am going to rewrite this... I am currently working on a project that consists of two layers. The top layer involves HTML5 animations such as rollovers and slides, while the bottom layer features kaleidoscope animation using images and CSS. The is ...

Ways to modify the color of text when it exceeds an element's boundaries

I'm trying to figure out how to change the text color when it hovers over a black circle HTML element that is fixed to the window. I've attempted using mix-blend-mode: difference;, but I can't seem to get it to work. Ideally, I want the text ...

Divs experiencing unexpected wrapping of text

I've encountered an unusual issue while working on a library I'm developing. The problem can be traced back to the following code snippet: <html> <body> <div style="position: absolute; left: 200px;"> ...

When hovering over .nav-item, the nav-link color remains unchanged

Is there a way to modify the code below to change the color of a navbar item to red when it is hovered over? .nav-item.active .nav-link{ color: blue !important; } .nav-item.hover .nav-link{ color: red !important; } ...

Sliding list in Jquery Mobile

I'm attempting to create a list using jQueryMobile similar to the one found in the Twitter application. Here's a video demonstrating what I'm aiming for: http://www.youtube.com/watch?v=l7gTNpPTChM However, I am facing two issues: 1) Each ...