0

I have made the below spin dial, using popmotion.

const {
  listen,
  styler,
  pointer,
  value,
  transform,
  spring,
  inertia,
  calc
} = window.popmotion;
const { pipe } = transform;

const dial = document.querySelector(".dial");
const dialStyler = styler(dial);
const dialRotate = value(0, dialStyler.set('rotate'));

const dialRect = dial.getBoundingClientRect();
const dialY = dialRect.top + window.scrollY + (dialRect.height / 2);
const dialX = dialRect.left + window.scrollX + (dialRect.width / 2);
// console.log(dialX, dialY);

const pointA = {x: dialX, y: dialY};
// let pointB = {x: 0, y: 0};
// let angle = 0;
// let prevAngle = 90;
// Angle between origo and pointer
const pointerAngle = o => pointer( o ).pipe(v => {
  const pointB = {x: v.x, y: v.y};
  const angle = calc.angle(pointA, pointB) + 90;
  // console.log('pointA: ', pointA);
  // console.log('pointB: ', pointB);
  // console.log('angle: ', angle);
  // console.log('prevAngle: ', prevAngle);
  // console.log('angle - prevAngle: ', angle - prevAngle);
  return angle;
});

listen(dial, "mousedown touchstart").start(e => {
  e.preventDefault();
  // prevAngle = angle;
  pointerAngle().start(dialRotate);
});

listen(document, "mouseup touchend").start(() => {
  dialRotate.stop();
});
img {
  width: 200px;
}
<script src="https://unpkg.com/popmotion/dist/popmotion.global.min.js"></script>

<img class="dial" src="https://greensock.com/wp-content/uploads/custom/draggable/img/knob.png">

How can i get the dial to start at the same location each time i let go and re-click?

The way it works now, it moves to the cursor/finger position when clicking/touching. I would like it to instead start from the exact place it is currently at, and calculate the angle from there.

This is probably trigonometry related, but I have not been able to figure it out.

Magnus
  • 6,791
  • 8
  • 53
  • 84
  • long time no see :p – Temani Afif Feb 25 '19 at 21:49
  • if I understand well, you want each time to consider the current position as the new reference to calculate the new angle and move there? – Temani Afif Feb 25 '19 at 21:57
  • Hey @TemaniAfif , long time no see. I have a few exciting updates for you, next time we speak. Yes, your understanding is correct. – Magnus Feb 25 '19 at 21:57
  • @TemaniAfif Solved it, see below :) There is probably an easier way to do it, but it was the only approach I could wrap my head around. – Magnus Feb 26 '19 at 12:18

1 Answers1

0

I came up with the following solution:

Normally, atan2 (angle from popmotion in my case) calculates the angle between two points by assuming that a horizontal x-axis runs through the first point. It then calculates the angle between the x-axis and the vector running through the two points. After all, it does not make sense to talk about an angle between two points, an angle only exists between two vectors / lines.

In our case, we wanted the hypothetical x-axis to move to wherever we click/touch, so we start at 0 angle each time, before spinning the dial. The obvious solution was then to calculate two atan2, one from origin of the dial to the point when we first click, and one from origin of the dial to whatever point our pointer/finger moves to. Then we just subtract the first standstill atan2 from the one that moves.

The above will ensure we always start at a 0 degree angle, when placing our pointer / finger down.

Finally, we simply add the old angle to the new, to start from wherever we previously left off.

Below is the final code.

PS: As a bonus effect, I added Inertia to the dial so it continues rotating at decreased speed until it stops, when we let it go:

const {
  listen,
  styler,
  pointer,
  value,
  transform,
  spring,
  inertia,
  calc
} = window.popmotion;
const {
  pipe
} = transform;

const dial = document.querySelector(".dial");
const dialStyler = styler(dial);
const dialRotate = value(0, dialStyler.set('rotate'));

// Get origin of dial graphic
const dialRect = dial.getBoundingClientRect();
const dialY = dialRect.top + window.scrollY + (dialRect.height / 2);
const dialX = dialRect.left + window.scrollX + (dialRect.width / 2);

// Angle between origo and pointer
const pointA = {
  x: dialX,
  y: dialY
};
let startSet = false;
let startPoint = {
  x: 0,
  y: 0
};
let combinedAngle = 0;
let prevAngle = 0;

const pointerAngle = o => pointer(o).pipe(v => {
  // Capture exact coordinate click/touch event happens
  // Used to calculate angle from that point and to where pointer is dragged
  // Also, capture last rotate position, to add to new angle
  // Ensures angle starts from where it previously stopped (not from 0 degrees)
  if (!startSet) {
    startPoint = {
      x: v.x,
      y: v.y
    };
    prevAngle = dialRotate.get();
    startSet = true;
  }

  const startAngle = calc.angle(pointA, startPoint) + 90;
  const pointB = {
    x: v.x,
    y: v.y
  };
  const mainAngle = calc.angle(pointA, pointB) + 90;
  const newAngle = mainAngle - startAngle;

  combinedAngle = newAngle + prevAngle;
  return combinedAngle;
});

listen(dial, "mousedown touchstart").start(e => {
  e.preventDefault();
  pointerAngle().start(dialRotate);
});

listen(document, "mouseup touchend").start(() => {
  startSet = false;
  const angle = dialRotate.get();
  inertia({
    velocity: dialRotate.getVelocity(),
    power: 0.8,
    from: angle,
  }).start(dialRotate);
});
img {
  width: 200px;
}
<script src="https://unpkg.com/popmotion/dist/popmotion.global.min.js"></script>
<img class="dial" src="https://greensock.com/wp-content/uploads/custom/draggable/img/knob.png">
Magnus
  • 6,791
  • 8
  • 53
  • 84
  • `Firefox 65.0.1 (64-bit) / Windows 10 Enterprise` Things are a bit odd when holding the cursor down as I go past the directly-up angle - the dial spins about 90 degrees opposite to the direction in which I'm dragging the dial – Scoots Feb 26 '19 at 13:29
  • @scoots hmmm, that's weird. I only tested on `Chrome`. If you figure it out, let me know. I might have a chance to look at it later today. – Magnus Feb 26 '19 at 13:31
  • @Scoots Interesting, my CodePen works in Firefox: https://codepen.io/magnusriga/pen/WmNOwO?editors=0010 – Magnus Feb 26 '19 at 13:39
  • Very strange, it now suddenly works in Firefox again, without changing anything. Perhaps it was related to the network connection somehow... – Magnus Feb 26 '19 at 13:43
  • Working fine here now :) – Scoots Feb 26 '19 at 15:17