Analog Clock Time Adjustment Bubble Deviation Dilemma

Recently, I encountered an issue with an analog clock component. Every time I click on the time adjustment bubble repeatedly while also moving the cursor position, it tends to drift away from its original placement, which should ideally be between an orange circle and the clock's main content (the white part).

In an attempt to address this problem, I made adjustments to the marginLeft and marginTop properties, but unfortunately, this only exacerbated the situation.

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

Answer №1

I have modified the code to ensure its functionality. The key concept is that the draggable element remains at a fixed distance from the center of the clock, and its position is determined solely by the angle rather than the mouse click location (refer to the comments in the code for more clarity).

The coordinates of the mouse click (e.clientX, e.clientY) are utilized exclusively for calculating the angle:

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

Check out the live demo of this implementation:

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>

Answer №2

Check out the revised code snippet 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';
});

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

Calculate the total value of a specific field within an array of objects

When pulling data from a csv file and assigning it to an object array named SmartPostShipments [], calculating the total number of elements in the array using the .length property is straightforward. However, I also need to calculate the sum of each field ...

Customizing Bootstrap tooltip appearances with CSS

After successfully setting up Bootstrap 4.5.0, I decided to experiment with customizing the styles of tooltips. The code snippet below shows how I attempted this. Initially, I included the necessary stylesheets at the top of the page: <link rel=&q ...

Adjust the Angular menu-bar directly from the content-script of a Chrome Extension

The project I've been working on involves creating an extension specifically for Google Chrome to enhance my school's online learning platform. This website, which is not managed by the school itself, utilizes Angular for its front-end design. W ...

Ease up smooth before the following glide

I've been searching for a solution without any luck. Essentially, I have text in my slick slider that I want to animate when transitioning to the next slide (after clicking the arrows or dots). mySlick.slick({ infinite: true, slidesT ...

Arrange two Angular 2 divs in a single row, with one positioned below the

Good Evening, I have a project in angular and I need help with styling. Specifically, I have 3 divs - card, table, and map. I want the card and table to be in the same row, and the map to be below them with double the size. The top left should have item in ...

Retrieve outcome from successful AJAX post and update HTML using globalEval

I have a function in JQuery that asynchronously posts data function post_data_async_globalEval(post_url, post_data, globaleval) { $.ajax({ type: 'POST', url: post_url, data: post_data, dataType: 'html', async: true, ...

Why does the React input value keep its value when the modal is re-rendered, despite the state being updated correctly?

Take a look at this sandbox link for the code snippet: Sandbox Showcased below is a table structure: https://i.sstatic.net/3F3Mc.png By clicking on the 'edit' button, a modal window opens up as shown below allowing you to edit input fields (onC ...

Display each value in a foreach loop just a single time

Utilizing a foreach loop to store error messages from invalid user inputs in an array, converting it into a string, and displaying them on another page. However, although the string is successfully displayed, it appears multiple times. This snippet showca ...

Exploring the depths of nested object arrays and navigating through historical indexes

I am working with nested object arrays within an array and looking to determine the path of a specific key. For instance: const dataList = [ [ [{id: 100,name: 'Test1'}, {id: 120,'Test12'}], [{id: 101,name: 'Test1&apo ...

Achieving success was like uncovering a hidden treasure chest after a successful

Is there a way to address this JSON data issue? success{"data": [{"id":"1","name":"something1"},{"id":"2","name":"something2"},{"id":"3","name":"something3"}] } The success variable contains the JSON data. This is how the server script returns the data: ...

Is there a way to access and read all JSON files within a directory in a Meteor application?

Is there a way to read all JSON files within a folder in my Meteor application? This is the structure I currently have: /server -- /methods -- -- file1.json -- -- file2.json I've attempted to read all JSON files using this code snippet: var ...

CSS: The border line with a see-through section

I'm in the process of creating a div that has a unique design like this. https://i.sstatic.net/cHJNY.png .triangle-area { width: 100%; height: 150px; } .triangle1 { width: 100%; height: 50px; border-width: 50px 50px 0 50px; border-col ...

Does anyone know the ins and outs of how the website www.nikebetterworld.com was created?

Hello, I am new to web development and I am interested in creating a page similar to the style of nikebetterworld. Can you point me in the right direction on where to start studying to achieve this design? My impression is that it involves multiple scrol ...

Are there any JavaScript charting tools that can draw in a clockwise direction with multiple data points?

Can anyone recommend a JavaScript live chart/graph library that can create clockwise graphs with multiple points, similar to the example below? https://i.sstatic.net/dQfK4.png ...

I'm curious about the origin of this.on event handler. Is it part of a particular library or framework?

While casually perusing through the application.js file in the express source code, I stumbled upon this interesting piece of code. I'm curious about the origin of this '.on' event. Is it part of vanilla JavaScript or is it a feature provid ...

Next.js encounters an error when importing web3

Currently, I am utilizing next js and material ui to create a demo dapp for educational purposes. With metamask installed, I have successfully implemented a "connect to wallet" button. My issue arises when attempting to import the Web3 constructor. This i ...

Is there a way to send my form data to a different URL once it has been submitted?

In the form I am designing, customer-related information needs to be filled out. There are three tasks that I am trying to accomplish with this form: 1. Validate the information such as correct email address and mobile number. 2. Post the form data in ...

Within Vuex, the object store.state.activities contains a specific key labeled "list" which initially holds an array of three items. However, when attempting to access store.state.activities.list directly, an empty array

My project is utilizing Vue. The store.state.activities object contains 2 keys, including an array named list with 3 elements. However, despite attempting to access it using store.state.activities.list, I am getting an empty array. I have experimented w ...

Trapping an anchor tag event in asp.net

I am currently working on a menu bar using HTML code (I am unable to use asp link buttons). <ul> <li><a href="#"><span>Reconciliation</span></a> <ul> ...

Enhancing User Experience: Real-Time Preview in Text Area using Jquery

I have a code snippet here that allows me to insert images into a textarea and display a live preview when I click on a div. However, there is a small issue with the image not appearing instantly in the textarea; instead, I have to type a character for it ...