SOURCE

console 命令行工具 X clear

                    
>
console
const SVG_NS = "http://www.w3.org/2000/svg";
const XLink_NS = "http://www.w3.org/1999/xlink";
const svg = document.querySelector("svg");
const point = document.querySelector("#point");
const text = document.querySelector("text");
const rad = Math.PI / 180;

let requestId = null;
let t = { x: 25, y: 25 }; // translation
let mouseAngle = 0; // initial position of the bubble
let deltaAngle = mouseAngle; // angle between point and mouse pos

let spring = 3 * rad - deltaAngle / 120;
const friction = 0.80;

class Point {
  constructor(angle, elmt) {
    this.a = 0;
    this.elmt = elmt;
    this.angle = angle;
    this.x = 10 * Math.cos(this.angle);
    this.y = 10 * Math.sin(this.angle);
    this.vel = 0;
  }
  draw() {
    // elmt == the bubble
    this.elmt.setAttribute( "cx", this.x);
    this.elmt.setAttribute( "cy", this.y);

    text.setAttribute("x", this.x);
    text.setAttribute("y", this.y);
    text.textContent = ~~getAngleInPercents(mouseAngle);
  }

  updateAngle(target) {
    this.dist = target - this.a;
    this.acc = this.dist * spring;
    this.vel += this.acc;
    this.vel *= friction;
    this.a += this.vel;
  }

  getAngle() {
    this.angle = Math.atan2(this.y, this.x);
  }

  rotate() {
    let cos = Math.cos(this.vel);
    let sin = Math.sin(this.vel);
    let p = { x: this.x, y: this.y };
    this.x = p.x * cos - p.y * sin;
    this.y = p.x * sin + p.y * cos;
  }
}

let p = new Point(0, A); // the bubble

function Draw() {
  requestId = window.requestAnimationFrame(Draw);

  p.updateAngle(deltaAngle);
  p.rotate();
  p.draw();
}
Draw();


svg.addEventListener(
  "click",
  function(e) {
    mouseAngle = getMouseAngle(e, t);
    number.value = getAngleInPercents(mouseAngle);

    onEvent();
  },
  false
);

number.addEventListener(
  "input",
  function(e) {
    mouseAngle = map(number.value, 0, 100, 0, 360) * rad;

    onEvent();
  },
  false
);

function onEvent() {
  if (requestId) {
    cancelAnimationFrame(requestId);
    requestId = null;
  }
  p.getAngle(); // changes the p.angle

  if (p.angle < mouseAngle - Math.PI) {
    p.angle = p.angle + 2 * Math.PI;
  }
  if (p.angle > mouseAngle + Math.PI) {
    p.angle = p.angle - 2 * Math.PI;
  }

  deltaAngle = mouseAngle - p.angle;

  spring = 3 * rad - Math.abs(deltaAngle / 120);
  p.a = 0;
  p.dist = 0;
  p.vel = 0;
  p.acc = 0;

  Draw();
}

function oMousePosSVG(e) {
  let p = svg.createSVGPoint();
  p.x = e.clientX;
  p.y = e.clientY;
  let ctm = svg.getScreenCTM().inverse();
  p = p.matrixTransform(ctm);
  return p;
}

function transformedMousePos(e, t) {
  let m = oMousePosSVG(e);
  return {
    x: m.x - t.x,
    y: m.y - t.y
  };
}

function getMouseAngle(e, t) {
  let m = transformedMousePos(e, t);
  //console.log("mouse: ",Math.atan2(m.y,m.x)/rad)
  return Math.atan2(m.y, m.x);
}

function getAngleInPercents(angle) {
  let A = angle < 0 ? (angle + 2 * Math.PI) / rad : angle / rad;
  return map(A, 0, 360, 0, 100);
}

function map(n, a, b, _a, _b) {
  var d = b - a;
  var _d = _b - _a;
  var u = _d / d;
  return _a + n * u;
}

svg.addEventListener("touchmove", function(e) {
    e = e.changedTouches[0];
	  mouseAngle = getMouseAngle(e, t);
		number.value = getAngleInPercents(mouseAngle);
		onEvent(); 
});
<div id="cont"><input id="number" type="range" value="0" min="0" max="100"></div>

<svg viewBox="0 0 50 50">
  <g transform="translate(25 25)">
    <circle r="10" />
    <circle class="point" id="A" r="1" />
    <text>0</text>
  </g>
</svg>

body{
  margin: 0;
}

svg {
  height: 100vmin;
  margin: 0 auto;
  display: block;
}
circle {
  stroke: #1da1f2;
  fill: none;
  stroke-width: 0.5;
}
.point {
  fill: #1da1f2;
}


text {
  dominant-baseline: central;
  text-anchor: middle;
  font-size: 2px;
  pointer-events: none;
  fill: #000;
}


div {
  padding: 0;
  width: 100vmin;
  height: 100vmin;
  pointer-events: none;
  display: grid;
  position: absolute;
  left: calc(50% - 50vmin);
  top: 0;
}

@media only screen and (max-width: 480px) {
  div {
    position: static;
    display: block;
    margin: 2em 0;
    width: 100%;
    height: auto;
  }
}