1

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>
Mike L.
  • 313
  • 2
  • 12

2 Answers2

2

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>
Indrajeet Patil
  • 652
  • 4
  • 11
1

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>
Gavin
  • 2,214
  • 2
  • 18
  • 26