skip to Main Content

I am facing an issue with an analog clock component. When I repeatedly click on the time adjustment bubble and simultaneously modify the cursor position, it moves away from its initial position, which should be between an orange circle and the clock’s content (the white part).

I attempted to resolve this problem by adjusting the marginLeft and marginTop properties, but this made the situation even worse.

let isDragging = false;
let offsetX, offsetY;

const draggable = document.querySelector('.clock div:nth-child(3)');
const clock = document.querySelector('.clock');
const clockRadius = clock.offsetWidth / 2;
const draggableRadius = draggable.offsetWidth / 2;
const hourDisplay = document.querySelector('.clock div p');

draggable.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.clientX - draggable.getBoundingClientRect().left;
    offsetY = e.clientY - draggable.getBoundingClientRect().top;
    draggable.style.cursor = 'grabbing';
});

document.addEventListener('mousemove', (e) => {
    if (!isDragging) return;

    const angle = Math.atan2(e.clientY - (clock.offsetTop + clockRadius), e.clientX - (clock.offsetLeft + clockRadius));          
    let hours = Math.floor((angle + Math.PI / 2) / (Math.PI / 6));
    hours = (hours + 12) % 12; // Adjust for 0-11 instead of 1-12
    const minuteSegment = ((angle + Math.PI / 2) / (Math.PI / 6 / 60)) % 60;
    const minutes = Math.floor(minuteSegment);

    // Calculate and display the hour and minutes based on the angle
    const formattedHour = hours < 10 ? `0${hours}` : `${hours}`;
    const formattedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
    hourDisplay.textContent = `${formattedHour}:${formattedMinutes}`;

    const x = Math.cos(angle) * (clockRadius - draggableRadius) + clockRadius - draggableRadius;
    const y = Math.sin(angle) * (clockRadius - draggableRadius) + clockRadius - draggableRadius;

    draggable.style.left = x - offsetX + 'px';
    draggable.style.top = y - offsetY + 'px';
});

document.addEventListener('mouseup', () => {
    isDragging = false;
    draggable.style.cursor = 'grab';
});

https://codepen.io/Mohamed-Mojtobai/pen/dywMymP

2

Answers


  1. Try the updated code below:

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
    
        // ...
    
        const x = Math.cos(angle) * (clockRadius - draggableRadius) + clockRadius - draggableRadius;
        const y = Math.sin(angle) * (clockRadius - draggableRadius) + clockRadius - draggableRadius;
    
        draggable.style.left = x + 'px';
        draggable.style.top = y + 'px';
    });
    
    Login or Signup to reply.
  2. I have adapted your code to make it work. The main idea is that the draggable is at a fixed distance from the center of the clock and the position of the draggable should only depend on the angle and not on the position of the mouse click (please look at the comments in the code).

    The position of the mouse click (e.clientX, e.clientY) is only used to compute the angle:

    const angle = Math.atan2(e.clientY - clockcenterY, e.clientX - clockcenterX);
    

    Demo:

    let isDragging = false;
    let offsetX, offsetY;
    
    const draggable = document.querySelector('.clock div:nth-child(3)');
    const clock = document.querySelector('.clock');
    const clockRadius = clock.offsetWidth / 2;
    // clock.getBoundingClientRect()
    const box = clock.getBoundingClientRect();
    const clockcenterX = (box.left + box.right) / 2;
    const clockcenterY = (box.top + box.bottom) / 2;
    const draggableRadius = draggable.offsetWidth / 2;
    const hourDisplay = document.querySelector('.clock div p');
    const radius = clockRadius - draggableRadius;
    // compute clock border width
    const clockBorderwidth = (clock.offsetWidth - clock.clientWidth) / 2;
    
    draggable.addEventListener('mousedown', (e) => {
      isDragging = true;
      offsetX = e.clientX - draggable.getBoundingClientRect().left;
      offsetY = e.clientY - draggable.getBoundingClientRect().top;
      draggable.style.cursor = 'grabbing';
    });
    
    document.addEventListener('mousemove', (e) => {
      if (!isDragging) return;
      // use clockcenterX, clockcenterY
      const angle = Math.atan2(e.clientY - clockcenterY, e.clientX - clockcenterX);
      let hours = Math.floor((angle + Math.PI / 2) / (Math.PI / 6));
      hours = (hours + 12) % 12; // Adjust for 0-11 instead of 1-12
    
      const minuteSegment = ((angle + Math.PI) / (Math.PI / 6 / 60)) % 60;
      const minutes = Math.floor(minuteSegment);
    
      // Calculate and display the hour and minutes based on the angle
      const formattedHour = hours < 10 ? `0${hours}` : `${hours}`;
      const formattedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
      hourDisplay.textContent = `${formattedHour}:${formattedMinutes}`;
    
      // the position of the draggable should only depend on the angle
      // (and not on the position of the mouse click)
      const x = Math.cos(angle) * radius + radius - clockBorderwidth;
      const y = Math.sin(angle) * radius + radius - clockBorderwidth;
      draggable.style.left = x + 'px';
      draggable.style.top = y + 'px';
    });
    
    document.addEventListener('mouseup', () => {
      isDragging = false;
      draggable.style.cursor = 'grab';
    });
    body {
      font-family: 'Nunito Sans';
      height: 100vh;
      margin: 0;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .clock {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 120px;
      height: 120px;
      border: 20px solid #f7ab57;
      border-radius: 50%;
      position: relative;
    }
    
    .clock div:nth-child(2) {
      width: 110px;
      height: 110px;
      border: 5px solid #dcdfdd;
      border-radius: 50%;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    
    .clock div:nth-child(3) {
      width: 20px;
      height: 20px;
      background-color: #4480c3;
      border-radius: 50%;
      position: absolute;
      margin-bottom: 140px;
      cursor: grab;
    }
    
    .clock div p {
      color: #2e1414;
      font-size: 48px;
    }
    <div class="clock">
      <div></div>
      <div></div>
      <div></div>
      <div>
        <p>00:00</p>
      </div>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search