2

I have implemented an analog clock with a minute hand and an hour hand. The purpose of this exercise is to rotate the clock hands to the corresponding angle of the hour label when dragging; i.e., when the mouse is being move around.

Some examples I've found use JQuery and other libraries. I am trying to do this with vanilla js only. I've looked at some examples located here, in stackoverflow, as well as other places and studying the logic and trying to understand the documentation of atan2, this is as far as I've been able to get:

//rotate handler
const rotateHand = (event)=>{
    const elem = event.target;
    elem.style.cursor = "grabbing";
    let rotating = true;
    const clock = document.querySelector(".gamut__timePicker__clock");
    const radius = 252 / 2;
    const rotateHandler = (e)=>{
        const radians = Math.atan2(e.pageX - radius, e.pageY - radius);
        let rotateDegrees = (radians * (180 / Math.PI) * -1) -180;
        if (rotating) {
            elem.style.transform = `rotate(${rotateDegrees}deg)`;
        }
    };
    document.addEventListener("mousemove", rotateHandler);
    const cancelRotate = (event)=>{
        elem.style.cursor = "grab";
        rotating = !rotating;
        document.removeEventListener("mousemove", rotateHandler);
        document.removeEventListener("mouseup", cancelRotate);

    };
    document.addEventListener("mouseup", cancelRotate);
};

document.querySelector(".gamut__timePicker__minHand").addEventListener("mousedown", rotateHand);
document.querySelector(".gamut__timePicker__hrHand").addEventListener("mousedown", rotateHand);
@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");
.gamut__timePicker {
  border: 1px solid gray;
  width: 300px;
  height: 335px;
  display: -webkit-box;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
          flex-direction: column;
  font-family: 'Roboto', sans-serif;
}

.gamut__timePicker__meridiamSelector {
  height: 20px;
  display: -webkit-box;
  display: flex;
  -webkit-box-pack: justify;
          justify-content: space-between;
}

.gamut__timePicker__meridiamSelector--meridiam {
  text-align: center;
  -webkit-box-flex: 1;
          flex-grow: 1;
  border: 1px solid gray;
  font-size: 0.875rem;
  line-height: 20px;
  cursor: pointer;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

.gamut__timePicker__meridiamSelector--meridiam:hover {
  background: #efefef;
}

.gamut__timePicker__meridiamSelector--selected {
  background: #607d8b !important;
  color: white;
}

.gamut__timePicker__clockWrapper {
  -webkit-box-flex: 1;
          flex-grow: 1;
  align-self: stretch;
  display: -webkit-box;
  display: flex;
  -webkit-box-pack: center;
          justify-content: center;
  -webkit-box-align: center;
          align-items: center;
  position: relative;
}

.gamut__timePicker__clock {
  border-radius: 50%;
  width: 250px;
  height: 250px;
  border: 1px solid gray;
  margin: 0 auto;
  position: relative;
}

.gamut__timePicker__clock--minPointer {
  width: 1px;
  height: 10px;
  background-color: #424242;
  position: absolute;
  -webkit-transform-origin: 0% 0%;
          transform-origin: 0% 0%;
}

.gamut__timePicker__clock--hrPointer {
  position: absolute;
  -webkit-transform-origin: 0% 0%;
          transform-origin: 0% 0%;
}

.gamut__timePicker__clock--hrPointer::before {
  top: -4px;
  left: -7px;
  content: "\25BC";
  position: absolute;
}

.gamut__timePicker__labels {
  border-radius: 50%;
  width: 210px;
  height: 210px;
  margin: 0 auto;
  position: absolute;
  pointer-events: none;
}

.gamut__timePicker__labels--hourLabel {
  position: absolute;
  -webkit-transform: translate(-50%, -45%);
          transform: translate(-50%, -45%);
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

.gamut__timePicker__minHand {
  width: 20px;
  height: 100px;
  background-image: url(https://res.cloudinary.com/dvzwvxhev/image/upload/v1578025454/clock_min.svg);
  background-size: 100% 100%;
  position: absolute;
  -webkit-transform-origin: 49% 92%;
          transform-origin: 49% 92%;
  top: 15%;
  -webkit-transform: rotate(0deg);
          transform: rotate(0deg);
  cursor: -webkit-grab;
  cursor: grab;
}

.gamut__timePicker__hrHand {
  width: 40px;
  height: 70px;
  background-image: url(https://res.cloudinary.com/dvzwvxhev/image/upload/v1578274567/clock_hr.svg);
  background-size: 100% 100%;
  position: absolute;
  -webkit-transform-origin: 50% 93%;
          transform-origin: 50% 93%;
  top: 25%;
  -webkit-transform: rotate(0deg);
          transform: rotate(0deg);
  cursor: -webkit-grab;
  cursor: grab;
}

.gamut__timePicker__time {
  height: 34px;
  display: -webkit-box;
  display: flex;
  -webkit-box-pack: center;
          justify-content: center;
}

.gamut__timePicker__time > input {
  width: 35px;
  border: none;
  border-bottom: 1px solid #ababab;
  text-align: center;
  font-size: 1.125rem;
  outline: none;
  height: 25px;
  padding: 0;
}

.gamut__timePicker__time > input:focus {
  border-bottom: 2px solid #607d8b;
}

.gamut__timePicker__time--separator {
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
  font-size: 1.125rem;
  height: 25px;
}

.gamut__timePicker__done {
  background: #efefef;
  text-align: center;
  border-top: 1px solid gray;
  cursor: pointer;
}
<div class="gamut__timePicker" style="top: 31px; left: 8px; z-index: 9;"><div class="gamut__timePicker__meridiamSelector"><div data-meridiam-value="am" class="gamut__timePicker__meridiamSelector--meridiam gamut__timePicker__meridiamSelector--selected">AM</div><div data-meridiam-value="pm" class="gamut__timePicker__meridiamSelector--meridiam">PM</div></div><div class="gamut__timePicker__clockWrapper"><div class="gamut__timePicker__clock"><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(90deg); left: 100%; top: 50%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(96deg); left: 99.7261%; top: 55.2264%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(102deg); left: 98.9074%; top: 60.3956%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(108deg); left: 97.5528%; top: 65.4508%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(114deg); left: 95.6773%; top: 70.3368%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(120deg); left: 93.3013%; top: 75%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(126deg); left: 90.4508%; top: 79.3893%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(132deg); left: 87.1572%; top: 83.4565%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(138deg); left: 83.4565%; top: 87.1572%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(144deg); left: 79.3893%; top: 90.4508%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(150deg); left: 75%; top: 93.3013%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(156deg); left: 70.3368%; top: 95.6773%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(162deg); left: 65.4508%; top: 97.5528%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(168deg); left: 60.3956%; top: 98.9074%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(174deg); left: 55.2264%; top: 99.7261%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(180deg); left: 50%; top: 100%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(186deg); left: 44.7736%; top: 99.7261%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(192deg); left: 39.6044%; top: 98.9074%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(198deg); left: 34.5492%; top: 97.5528%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(204deg); left: 29.6632%; top: 95.6773%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(210deg); left: 25%; top: 93.3013%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(216deg); left: 20.6107%; top: 90.4508%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(222deg); left: 16.5435%; top: 87.1572%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(228deg); left: 12.8428%; top: 83.4565%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(234deg); left: 9.54915%; top: 79.3893%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(240deg); left: 6.69873%; top: 75%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(246deg); left: 4.32273%; top: 70.3368%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(252deg); left: 2.44717%; top: 65.4508%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(258deg); left: 1.09262%; top: 60.3956%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(264deg); left: 0.273905%; top: 55.2264%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(270deg); left: 0%; top: 50%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(276deg); left: 0.273905%; top: 44.7736%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(282deg); left: 1.09262%; top: 39.6044%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(288deg); left: 2.44717%; top: 34.5492%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(294deg); left: 4.32273%; top: 29.6632%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(300deg); left: 6.69873%; top: 25%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(306deg); left: 9.54915%; top: 20.6107%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(312deg); left: 12.8428%; top: 16.5435%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(318deg); left: 16.5435%; top: 12.8428%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(324deg); left: 20.6107%; top: 9.54915%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(330deg); left: 25%; top: 6.69873%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(336deg); left: 29.6632%; top: 4.32273%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(342deg); left: 34.5492%; top: 2.44717%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(348deg); left: 39.6044%; top: 1.09262%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(354deg); left: 44.7736%; top: 0.273905%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(360deg); left: 50%; top: 0%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(366deg); left: 55.2264%; top: 0.273905%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(372deg); left: 60.3956%; top: 1.09262%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(378deg); left: 65.4508%; top: 2.44717%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(384deg); left: 70.3368%; top: 4.32273%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(390deg); left: 75%; top: 6.69873%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(396deg); left: 79.3893%; top: 9.54915%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(402deg); left: 83.4565%; top: 12.8428%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(408deg); left: 87.1572%; top: 16.5435%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(414deg); left: 90.4508%; top: 20.6107%;"></div><div class="gamut__timePicker__clock--hrPointer" style="transform: rotate(420deg); left: 93.3013%; top: 25%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(426deg); left: 95.6773%; top: 29.6632%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(432deg); left: 97.5528%; top: 34.5492%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(438deg); left: 98.9074%; top: 39.6044%;"></div><div class="gamut__timePicker__clock--minPointer" style="transform: rotate(444deg); left: 99.7261%; top: 44.7736%;"></div></div><div class="gamut__timePicker__labels"><span class="gamut__timePicker__labels--hourLabel" style="left: 100%; top: 50%;">3</span><span class="gamut__timePicker__labels--hourLabel" style="left: 93.3013%; top: 75%;">4</span><span class="gamut__timePicker__labels--hourLabel" style="left: 75%; top: 93.3013%;">5</span><span class="gamut__timePicker__labels--hourLabel" style="left: 50%; top: 100%;">6</span><span class="gamut__timePicker__labels--hourLabel" style="left: 25%; top: 93.3013%;">7</span><span class="gamut__timePicker__labels--hourLabel" style="left: 6.69873%; top: 75%;">8</span><span class="gamut__timePicker__labels--hourLabel" style="left: 0%; top: 50%;">9</span><span class="gamut__timePicker__labels--hourLabel" style="left: 6.69873%; top: 25%;">10</span><span class="gamut__timePicker__labels--hourLabel" style="left: 25%; top: 6.69873%;">11</span><span class="gamut__timePicker__labels--hourLabel" style="left: 50%; top: 0%;">12</span><span class="gamut__timePicker__labels--hourLabel" style="left: 75%; top: 6.69873%;">1</span><span class="gamut__timePicker__labels--hourLabel" style="left: 93.3013%; top: 25%;">2</span></div><div class="gamut__timePicker__minHand" style="transform: rotate(0deg);"></div><div class="gamut__timePicker__hrHand" style="transform: rotate(240deg);"></div></div><div class="gamut__timePicker__time"><input type="text" data-time-param="hour"><span class="gamut__timePicker__time--separator">:</span><input type="text" data-time-param="minute"></div><div class="gamut__timePicker__done">DONE</div></div>

As you can see on the above snippet, the clock hand is rotating but the angle is not exact, there is a bit of discrepancy. The goal is that the hand points to the correct hour/minute when the mouse pointer is at an angle that corresponds to the respective hour.

Morfinismo
  • 4,985
  • 4
  • 19
  • 36
  • 1
    The geometry is mostly fine but you're not properly calculating the distance of the mouse from the center of the clock. https://jsfiddle.net/khrismuc/b3cxh5ky/ –  Jan 14 '20 at 14:42
  • @ChrisG Could you post your comment as answer so I can mark it as the accepted solution please. Also, if its not too much to ask, could you ellaborate more on your answer? Although, I'm greatly thankful for the solution, I'd like to understand how this works. Thanks! – Morfinismo Jan 14 '20 at 14:46
  • I'm calling [getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) on the clock element which conveniently returns size and position. All that was missing from your code is taking into account that the top left corner of the clock isn't at the top left corner of the page. To get the clock center, simply add it's position to the radius for both x and y. –  Jan 14 '20 at 14:53
  • Does this answer your question? [Get mouse click position](https://stackoverflow.com/questions/34345139/get-mouse-click-position) –  Jan 14 '20 at 14:59
  • @ChrisG Thank you very much for ellaborating. I think I have a better understanding now. You'be been awesome, thanks for your help! – Morfinismo Jan 14 '20 at 15:05

0 Answers0