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.