简体   繁体   中英

Make an indicator move around a circle

I was trying to come up with some JS (pure JS) code making the indicator move around the circle while the pin is being moved (as if it was a speedometer), but I failed. I tried to use offsets, count the offsetTop etc., but it doesn't work. Can anyone help me out here, please?

If there is some other way to do this without using some extra libraries (for example, just using the css options), let me know - I'll be really grateful as it's really important for me to understand the concept here!

 'use strict'; let firstIndicator = document.querySelector('.indicator'); let pinLevel = document.querySelector('.effect-level__pin'); let effectLevelLine = document.querySelector('.effect-level__line'); let effectLevelDepth = document.querySelector('.effect-level__depth'); let changeOverlay = function (percentage) { pinLevel.style.left = percentage + '%'; effectLevelDepth.style.width = percentage + '%'; }; pinLevel.addEventListener('mousedown', function (evt) { evt.preventDefault(); let startX = evt.clientX; let startLevelDepthWidth = effectLevelDepth.offsetWidth; let clickedPercentageLevel = startLevelDepthWidth / effectLevelLine.offsetWidth * 100; changeOverlay(clickedPercentageLevel); let onMouseMove = function (moveEvt) { moveEvt.preventDefault(); let shift = moveEvt.clientX - startX; let levelWidth = startLevelDepthWidth + shift; let movedPercentageLevel = levelWidth / effectLevelLine.offsetWidth * 100; movedPercentageLevel = Math.max(0, movedPercentageLevel); movedPercentageLevel = Math.min(100, movedPercentageLevel); changeOverlay(movedPercentageLevel); firstIndicator.style.top = (firstIndicator.offsetHeight * 3) - (movedPercentageLevel / 100) + 'px'; firstIndicator.style.transform = 'rotate('+ (52 + movedPercentageLevel * 2.4) + 'deg' + ')'; }; let onMouseUp = function (upEvt) { upEvt.preventDefault(); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); 
 .circle { margin: 0 auto; margin-top: 50px; width: 150px; height: 150px; border: 1px solid black; border-radius: 50% } .indicator { position: relative; } .indicator svg { position: absolute; top: -100px; transform: rotate(82deg); left: 108px; } .effect-level { position: absolute; bottom: -30px; left: 50%; width: 495px; height: 33px; font-size: 12px; line-height: 42px; text-align: center; color: black; white-space: nowrap; background-color: #ffffff; border: none; -webkit-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%); } .effect-level__value { display: none; } .effect-level__line { position: absolute; top: 50%; right: 20px; left: 20px; height: 5px; font-size: 0; background-color: rgba(0, 0, 0, 0.2); -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); } .effect-level__pin { position: absolute; top: 50%; left: 0%; z-index: 1; width: 18px; height: 18px; margin: -9px 0 0; background-color: #fff; border-radius: 50%; border: 1px solid #323232; -webkit-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%); cursor: move; } .effect-level__depth { position: absolute; width: 0%; height: 100%; background-color: #323232; } 
 <div class="circle"></div> <div class="indicator"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="337.0744522647706 304.4241607573019 28.925547735229372 104" width="24.93" height="100"><defs><path d="" id="c6VRx4235S"></path><path d="M348.07 305.42L338.07 405.42" id="f84HmfmJk"></path><path d="M340.01 305.42L338.07 403.88" id="azVXtGrDR"></path></defs><g><g><g><use xlink:href="#c6VRx4235S" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="1"></use></g></g><g><g><use xlink:href="#f84HmfmJk" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="1"></use></g></g><g><g><use xlink:href="#azVXtGrDR" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="1"></use></g></g></g> </svg> </div> <fieldset class="effect-level"> <input class="effect-level__value" type="number" name="effect-level" value="0"> <div class="effect-level__line"> <div class="effect-level__pin" tabindex="0">Кнопка изменения эффекта </div> <div class="effect-level__depth">Глубина эффекта</div> </div> </fieldset> 

You can define the size of your .indicator such that, when rotated using transform: rotate(angle) , it rotates about the center of your defined circle.

It's always better to define your moving elements & their references as position: absolute and later have container divs to place it where ever you want.

.circle {
  position: absolute;
  top: 100px;
  left: 100px;
  width: 150px;
  height: 150px;
  border: 1px solid black;
  border-radius: 50%;
}

.indicator {
    position: absolute;
    top: 6px;
    left: 155px;
    height: 340px;
    width: 44px;
    transform: rotate(0deg);
}

.indicator svg {
  transform: rotate(177deg);
}

This makes our lives very easy, we just have to rotate .indicator based on your movedPercentageLevel.

Check out the following code snippet.

 'use strict'; let firstIndicator = document.querySelector('.indicator'); let pinLevel = document.querySelector('.effect-level__pin'); let effectLevelLine = document.querySelector('.effect-level__line'); let effectLevelDepth = document.querySelector('.effect-level__depth'); let changeOverlay = function(percentage) { pinLevel.style.left = percentage + '%'; effectLevelDepth.style.width = percentage + '%'; }; pinLevel.addEventListener('mousedown', function(evt) { evt.preventDefault(); let startX = evt.clientX; let startLevelDepthWidth = effectLevelDepth.offsetWidth; let clickedPercentageLevel = startLevelDepthWidth / effectLevelLine.offsetWidth * 100; changeOverlay(clickedPercentageLevel); let onMouseMove = function(moveEvt) { moveEvt.preventDefault(); let shift = moveEvt.clientX - startX; let levelWidth = startLevelDepthWidth + shift; let movedPercentageLevel = levelWidth / effectLevelLine.offsetWidth * 100; movedPercentageLevel = Math.max(0, movedPercentageLevel); movedPercentageLevel = Math.min(100, movedPercentageLevel); changeOverlay(movedPercentageLevel); firstIndicator.style.transform = 'rotate(' + movedPercentageLevel * 3.6 + 'deg' + ')'; }; let onMouseUp = function(upEvt) { upEvt.preventDefault(); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); 
 .circle { position: absolute; top: 100px; left: 100px; width: 150px; height: 150px; border: 1px solid black; border-radius: 50%; } .indicator { position: absolute; top: 6px; left: 155px; height: 340px; width: 44px; transform: rotate(0deg); } .indicator svg { transform: rotate(177deg); } .effect-level { position: absolute; bottom: -30px; left: 50%; width: 495px; height: 33px; font-size: 12px; line-height: 42px; text-align: center; color: black; white-space: nowrap; background-color: #ffffff; border: none; -webkit-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%); } .effect-level__value { display: none; } .effect-level__line { position: absolute; top: 50%; right: 20px; left: 20px; height: 5px; font-size: 0; background-color: rgba(0, 0, 0, 0.2); -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); } .effect-level__pin { position: absolute; top: 50%; left: 0%; z-index: 1; width: 18px; height: 18px; margin: -9px 0 0; background-color: #fff; border-radius: 50%; border: 1px solid #323232; -webkit-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%); cursor: move; } .effect-level__depth { position: absolute; width: 0%; height: 100%; background-color: #323232; } 
 <div class="circle"></div> <div class="indicator"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="337.0744522647706 304.4241607573019 28.925547735229372 104" width="24.93" height="100"><defs><path d="" id="c6VRx4235S"></path><path d="M348.07 305.42L338.07 405.42" id="f84HmfmJk"></path><path d="M340.01 305.42L338.07 403.88" id="azVXtGrDR"></path></defs><g><g><g><use xlink:href="#c6VRx4235S" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="1"></use></g></g><g><g><use xlink:href="#f84HmfmJk" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="1"></use></g></g><g><g><use xlink:href="#azVXtGrDR" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="1"></use></g></g></g> </svg> </div> <fieldset class="effect-level"> <input class="effect-level__value" type="number" name="effect-level" value="0"> <div class="effect-level__line"> <div class="effect-level__pin" tabindex="0">Кнопка изменения эффекта </div> <div class="effect-level__depth">Глубина эффекта</div> </div> </fieldset> 

Just for fun, here's an even more javascript-y flavour that doesn't need any elements rotating. Just a circle and a line, redrawn to new coordinates after rotation about a point.

Here it just updates on mouse movement but desired angle just needs to be fed into the function as an offset.

 'use strict'; // get the canvas element const canvas = document.getElementById('canvas'); canvas.width = 170; canvas.height = 170; canvas.style.background = 'red'; canvas.style.border = '1px solid black'; // somewhere to store the position and state of the needle const needle = { start_angle: 0, current_angle: 0, point_x: canvas.width, point_y: canvas.height/2 // the 3oclock position is just straight to the right! from the centre of our circle, } // handy function to rotate a point about a point. first google result const rotatePoint = (x, y, centerx, centery, degrees)=>{ // https://stackoverflow.com/a/45649110/2244284 let newx = (x - centerx) * Math.cos(degrees * Math.PI / 180) - (y - centery) * Math.sin(degrees * Math.PI / 180) + centerx; let newy = (x - centerx) * Math.sin(degrees * Math.PI / 180) + (y - centery) * Math.cos(degrees * Math.PI / 180) + centery; return [newx, newy]; } // handy function to convert degrees to radians const d2r = (degree)=>{ return degree * (Math.PI / 180); } // handy function to draw a circle const drawCircle = ()=>{ var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.arc(canvas.width/2, canvas.height/2, canvas.width/2, 0, d2r(360)); // just use the whole canvas ctx.stroke(); } // draw our needle const drawNeedle = (offset)=>{ let xy = rotatePoint(needle.point_x, needle.point_y, canvas.width/2, canvas.height/2, offset); // point_x/y is the far end of the needle, the other side is just the centre of the circle/canvas // draw a line from centre to the new point of the needle var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(canvas.width/2, canvas.height/2); ctx.lineTo(xy[0], xy[1]); ctx.stroke(); } // --- init stuff drawCircle(); // draw first circle drawNeedle(needle.start_angle); // draw first needle // add an event for every mouse move detected over the canvas canvas.addEventListener('mousemove', (e)=>{ canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); // clear clear the canvas drawCircle(); // redraw the circle let offset = needle.start_angle + needle.current_angle++ % 360; // increase angle by 1 for every mouseover event, reset to 0 if a full circle is reached drawNeedle(offset) // draw needle }) 
 <html> <body> <canvas id="canvas"></canvas> </body> </html> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM