skip to Main Content

I had a menu design idea that involves a central cursor that points to indicate what menu item is currently being selected. I achieved this using some JavaScript code that takes a list of fixed angles that represent the buttons and then calculates whether to rotate clockwise or anti-clockwise based on whichever would require less degrees to reach. The issue I encountered however was that upon going from these 2 angles (222° to 115° and vice versa) the arrow would not take the fastest route and spin the opposite direction.

After doing some research what I believe to actually be happening is the cursor is resetting to 0° so that it completes the circle before then making its way to the selected angle from the zero point thus causing it to go the wrong direction. I read a few answers on here and did some general research on the rotate function as a whole but have been unable to find a case that is similar enough to mine nor intuit a way in-which I could fix it myself (web development as a whole is new to me). I would ideally like for it to ignore the rule that compels it to complete the circle and instead just point to the supplied angle though I am aware that is likely impossible to do with css’s rotate tool, so if there is anything that would do the same to that effect it would be much appreciated. I made a functional example to showcase my issue in a jsfiddle. https://jsfiddle.net/u3w4vz1n/latest/

const angles = { // Dictionary of where we want our static angle positions to be
  button1: 290,
  button2: 222,
  button3: 115,
  button4: 75,
  button5: 0
}

var cursor = document.getElementById("cursor")
var currentAngle = 0;

$(".button").hover(function() { // When mouse enters button area

  let next_angle = angles[this.id];
  let turn_radius = 0;
  let clockwise = (Math.abs(next_angle - currentAngle) < 180) ? true : false; // Calculates which direction is less degrees from source and returns boolean

  if (clockwise) {
    turn_radius = (next_angle - currentAngle);
  } else {
    turn_radius = "-" + (360 - Math.abs(next_angle - currentAngle));
  }
  cursor.style.rotate = turn_radius + "deg";
});
.button {
  display: block;
  z-index: -1;
  position: absolute;
  border: 4px solid;
  border-radius: 45%;
  text-decoration: none;
  text-align: center;
  font: bold;
  font-size: medium;
  background-color: white;
}

#button1 {
  width: 30%;
  height: 15%;
  top: 40%;
  left: 15%;
  transform: translate(-40%, -15%);
}

#button2 {
  width: 30%;
  height: 15%;
  top: 75%;
  left: 25%;
  transform: translate(-75%, -25%);
}

#button3 {
  width: 30%;
  height: 15%;
  top: 80%;
  left: 85%;
  transform: translate(-80%, -85%);
}

#button4 {
  width: 30%;
  height: 15%;
  top: 40%;
  left: 75%;
  transform: translate(-40%, -75%);
}

#button5 {
  width: 30%;
  height: 15%;
  top: 10%;
  left: 35%;
  transform: translate(-10%, -35%);
}

#cursor {
  width: 100px;
  height: 100px;
  position: absolute;
  top: 40%;
  left: 40%;
  transition: all 0.25s ease-out;
  -moz-transition: all 0.25s ease-out;
  -ms-transition: all 0.25s ease-out;
  -o-transition: all 0.25s ease-out;
  -webkit-transition: all 0.25s ease-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<!DOCTYPE html>
<html>

<head>
  <title>Main Menu</title>
  <link rel="stylesheet" href="index.css">
</head>

<body style="background-color: white;">
  <img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.sVcpgS1I_6kEJvuOecuuUgHaHa%26pid%3DApi&f=1&ipt=00bebfbdf248c4677c3ad5a9f6910bdfbd564aa401d634006f527981942f13d6&ipo=images" id="cursor">

  <a href="#" id="button1" class="button">Button1</a>
  <a href="#" id="button2" class="button">Button2</a>
  <a href="#" id="button3" class="button">Button3</a>
  <a href="#" id="button4" class="button">Button4</a>
  <a href="#" id="button5" class="button">Button5</a>

  <script src="menu_select.js"></script>
</body>

</html>

2

Answers


  1. This problem is caused because every time you update the angle it starts with 0 degrees instead of the shortest path. Update your JS function as below:

    let delta = nextAngle - currentAngle;
    
    // Normalizing delta so that it takes the shortest path
    if (delta > 180) {
        delta -= 360;
    } else if (delta < -180) {
        delta += 360;
    }
    
    currentAngle += delta;
    
    $(cursor).css('transform', 'rotate(' + currentAngle + 'deg)');
    
    Login or Signup to reply.
  2. The rotation is clockwise. This means if we either going to a higher degree (clockwise) or lower degree (counterclockwise). So if we’re at degree x and need to go to y we either go to y + 360, y - 360 or y so that the difference is smaller than 180° (half circle). For example, from 350° to 10° we actually need to go to 370° so that diff would be only 20° rather than -340° had we gone straight to 10°.

    See also Rotating a line to look at the mouse with linear interpolation causes jumping

    const angles = {
      button1: 290,
      button2: 222,
      button3: 115,
      button4: 75,
      button5: 0
    }
    
    function getTargetRotation(current, target) {
      while (target - current > 180) {
        target -= 360;
      }
      while (target - current < -180) {
        target += 360;
      }
      return target;
    }
    
    document.querySelectorAll(".button").forEach(function(btn) {
      btn.innerText += " = " + angles[btn.id] + "°"
    })
    
    var cursor = document.getElementById("cursor")
    var currentAngle = 0;
    
    $(".button").hover(function() { // When mouse enters button area
    
      let nextAngle = angles[this.id];
    
      nextAngle = getTargetRotation(currentAngle, nextAngle)
      currentAngle = nextAngle
    
      cursor.style.rotate = nextAngle + "deg";
    });
    .button {
      display: block;
      z-index: -1;
      position: absolute;
      border: 4px solid;
      border-radius: 45%;
      text-decoration: none;
      text-align: center;
      font: bold;
      font-size: medium;
      background-color: white;
    }
    
    #button1 {
      width: 30%;
      height: 15%;
      top: 40%;
      left: 15%;
      transform: translate(-40%, -15%);
    }
    
    #button2 {
      width: 30%;
      height: 15%;
      top: 75%;
      left: 25%;
      transform: translate(-75%, -25%);
    }
    
    #button3 {
      width: 30%;
      height: 15%;
      top: 80%;
      left: 85%;
      transform: translate(-80%, -85%);
    }
    
    #button4 {
      width: 30%;
      height: 15%;
      top: 40%;
      left: 75%;
      transform: translate(-40%, -75%);
    }
    
    #button5 {
      width: 30%;
      height: 15%;
      top: 10%;
      left: 35%;
      transform: translate(-10%, -35%);
    }
    
    #cursor {
      width: 100px;
      height: 100px;
      position: absolute;
      top: 40%;
      left: 40%;
      transition: all 0.25s ease-out;
      -moz-transition: all 0.25s ease-out;
      -ms-transition: all 0.25s ease-out;
      -o-transition: all 0.25s ease-out;
      -webkit-transition: all 0.25s ease-out;
    }
    
    body {
      overflow: hidden;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <!DOCTYPE html>
    <html>
    
    <head>
      <title>Main Menu</title>
      <link rel="stylesheet" href="index.css">
    </head>
    
    <body style="background-color: white;">
      <img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.sVcpgS1I_6kEJvuOecuuUgHaHa%26pid%3DApi&f=1&ipt=00bebfbdf248c4677c3ad5a9f6910bdfbd564aa401d634006f527981942f13d6&ipo=images" id="cursor">
    
      <a href="#" id="button1" class="button">Button1</a>
      <a href="#" id="button2" class="button">Button2</a>
      <a href="#" id="button3" class="button">Button3</a>
      <a href="#" id="button4" class="button">Button4</a>
      <a href="#" id="button5" class="button">Button5</a>
    
      <script src="menu_select.js"></script>
    </body>
    
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search