jQuery & Snap.svg -> 圆圈未正确跟随鼠标

jQuery & Snap.svg -> circle not following mouse correctly

我正在玩 Snap.svg 和 jQuery 一点点,我正在创建这个 bitmoji 并试图让他的眼睛跟随鼠标光标。

一切都很好,除了眼睛。移动光标时它们在变换和旋转,但不是 100% 正确,我不明白为什么。

这是我在 JSFiddle 中的代码:http://jsfiddle.net/bmp5j4x9/1/

调整结果框的大小,使其变大并移动鼠标,我想您会明白我的意思的。或者看看 http://dante-c.be.

这是 jQuery 部分:

        var s = Snap(420, 420).attr({ viewBox: "0 0 120 120" });
        $(s.node).appendTo(".button");

        var image = s.paper.image('https://render.bitstrips.com/v2/cpanel/10220069-circle-357822728_5-s4-v1.png?palette=1', 0, 0, 1, 1);
        image = image.pattern().attr({
            patternContentUnits: "objectBoundingBox",
            patternUnits: "",
            width: "100%", height: "100%", viewBox: "" 
        });
        var bitmojiCircle = s.circle(60, 60, 39).attr({ fill: image });

        var circleX = 50, circleY = 63, circleRadius = 4.5;
        var bigEyeCircle = s.circle(circleX, circleY, circleRadius);
        var L1 = s.path("M "+circleX+" "+circleY +"L 0 0").attr({stroke: "blue"});
        bigEyeCircle.attr({
            fill: "#bada55",
            stroke: "#000",
            strokeWidth: 1
        });
        var smallEyeCircle = s.circle(0,0,3.5).attr({ fill: "red" });

        var opacityCircle = s.circle(60, 60, 39).attr({ fill: "rgba(255,255,255,0.7)" });
        var menuButton = s.path("M58.486 56.324H57.19c-.48 0-.866.387-.866.865v1.29c0 .48.387.86.865.86h1.29c.48 0 .86-.39.86-.87v-1.29c0-.48-.39-.87-.87-.87zm-4.324 0h-1.297c-.478 0-.865.387-.865.865v1.29c0 .48.387.86.865.86h1.297c.478 0 .865-.39.865-.87v-1.29c0-.48-.387-.87-.865-.87zM58.486 52H57.19c-.48 0-.866.387-.866.865v1.297c0 .478.387.865.865.865h1.29c.48 0 .86-.387.86-.865v-1.297c0-.478-.39-.865-.87-.865zm-4.324 0h-1.297c-.478 0-.865.387-.865.865v1.297c0 .478.387.865.865.865h1.297c.478 0 .865-.387.865-.865v-1.297c0-.478-.387-.865-.865-.865zm12.973 4.324h-1.297c-.478 0-.865.387-.865.865v1.29c0 .48.387.86.865.86h1.297c.478 0 .865-.39.865-.87v-1.29c0-.48-.387-.87-.865-.87zm-4.324 0h-1.29c-.48 0-.86.387-.86.865v1.29c0 .48.39.86.87.86h1.3c.48 0 .87-.39.87-.87v-1.29c0-.48-.38-.87-.86-.87zM67.14 52h-1.3c-.48 0-.866.387-.866.865v1.297c0 .478.387.865.865.865h1.29c.48 0 .86-.387.86-.865v-1.297c0-.478-.39-.865-.87-.865zm-4.324 0H61.52c-.48 0-.865.387-.865.865v1.297c0 .478.386.865.865.865h1.297c.48 0 .866-.387.866-.865v-1.297c0-.478-.386-.865-.864-.865zM58.49 64.973h-1.3c-.48 0-.866.387-.866.865v1.297c0 .478.387.865.865.865h1.29c.48 0 .86-.387.86-.865v-1.297c0-.478-.39-.865-.87-.865zm-4.325 0h-1.297c-.478 0-.865.387-.865.865v1.297c0 .478.387.865.865.865h1.297c.478 0 .865-.387.865-.865v-1.297c0-.478-.388-.865-.866-.865zm4.324-4.324h-1.3c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.29c.48 0 .86-.39.86-.87V61.5c0-.48-.39-.864-.87-.864zm-4.33 0h-1.3c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.29c.472 0 .86-.39.86-.87V61.5c0-.48-.39-.864-.867-.864zm12.97 4.32h-1.29c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.29c.48 0 .86-.39.86-.87v-1.29c0-.48-.387-.87-.865-.87zm-4.33 0h-1.29c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.3c.48 0 .862-.39.862-.87v-1.29c0-.48-.39-.87-.867-.87zm4.32-4.33h-1.3c-.48 0-.87.38-.87.86v1.3c0 .48.384.86.862.86h1.3c.476 0 .863-.39.863-.87V61.5c0-.48-.388-.864-.866-.864zm-4.33 0H61.5c-.48 0-.864.38-.864.86v1.3c0 .48.387.86.866.86H62.8c.48 0 .87-.39.87-.87V61.5c0-.48-.383-.864-.86-.864z").attr({
            class: "menu-button",
            fill: "#9B9B9B",
            fillRule: "nonzero"
        });

        var c1 = s.circle(60, 60, 53).attr({ stroke: "#9B9B9B", transform: "rotate(90 60 60)" });
        var c2 = s.circle(60, 7, 2).attr({ fill: "#9B9B9B" });
        var c3 = s.circle(60, 113, 2).attr({ fill: "#9B9B9B" });
        var c4 = s.circle(113, 60, 2).attr({ fill: "#9B9B9B" });
        var c5 = s.circle(7, 60, 2).attr({ fill: "#9B9B9B" });

        var outerCircles = s.group(c1, c2, c3, c4, c5).attr({ class: "outer-circle" });
        var fullSVG = s.group(bitmojiCircle, bigEyeCircle, L1, smallEyeCircle, opacityCircle, menuButton, outerCircles).attr({ fill: "none", fillRule: "evenodd" });

        function OnMouseMove(evt) {
            L1.attr({ d: "M "+circleX+" "+circleY +"L "+evt.clientX+" "+evt.clientY });
            var totalLength = L1.getTotalLength();

            if (totalLength < circleRadius) {
                smallEyeCircle.attr({ cx: evt.clientX , cy: evt.clientY });
            } else {
                var PAL = L1.getPointAtLength(circleRadius);
                smallEyeCircle.attr({ cx: PAL.x , cy: PAL.y });
            }
        }
        document.onmousemove = OnMouseMove;

编辑

尝试 throttle/debounce 它,正如 Nikos 所说,通过用以下代码替换 OnMouseMove 函数:

var pageX = 0,
pageY = 0;

var moveIt = function() {
    L1.attr({ d: "M "+circleX+" "+circleY +"L "+pageX+" "+pageY });
    var totalLength = L1.getTotalLength();

    if (totalLength < circleRadius) {
        smallEyeCircle.attr({ cx: pageX, cy: pageY });
    } else {
        var PAL = L1.getPointAtLength(circleRadius);
        smallEyeCircle.attr({ cx: PAL.x , cy: PAL.y });
    }
    setTimeout(moveIt, 1000/25);
};

$(document).on('mousemove', function(e) {
    pageX = e.pageX;
    pageY = e.pageY;
}).one('mousemove', moveIt);

这似乎不起作用。

更新

我找到了更好的解决方案,但它仍然不是 100% 的功能,眼球移动的区域太大,但我不知道如何让它变小。 这是更新后的 fiddle:http://jsfiddle.net/bmp5j4x9/3/

正如我所评论的那样,您正在检测鼠标相对于文档的位置,并且您正在使用这些坐标在大小为 120/120 的 SVG canvas 中绘制。这是行不通的。 接下来是一个例子(Javascript),其中线条正确地跟随鼠标

let m = {}
test.addEventListener("mousemove",(e)=>{
// draw the line on mousemove
  m=oMousePosSVG(e);
  _line.setAttributeNS(null,"x2",m.x);
  _line.setAttributeNS(null,"y2",m.y);
})

function oMousePosSVG(e) {
// a function to detect the mouse position inside an SVG
      var p = test.createSVGPoint();
      p.x = e.clientX;
      p.y = e.clientY;
      var ctm = test.getScreenCTM().inverse();
      var p =  p.matrixTransform(ctm);
      return p;
}
<svg id="test" viewBox="0 0 120 120" width="100vw" height="100vh">
  <circle cx="60" cy="60" r="20" fill="#d9d9d9" />
  <line id="_line" x1="55" y1="60" stroke="blue" />
</svg>

然而,另一种解决方案是让事情保持原样,但根据文档大小重新计算鼠标位置:

let w = window.innerWidth;
let h = window.innerHeight;
let m = {}


document.addEventListener("mousemove",(e)=>{
  //get the mouse position
  m=oMousePos(e);
  //calculate the x2 and y2 for the line in function of the size of the window
  let x2 = map(m.x, 0, w, 0, 120)
  let y2 = map(m.y, 0, h, 0, 120)
  // set the attributes x2 and y2 for the line
  _line.setAttributeNS(null,"x2",x2);
  _line.setAttributeNS(null,"y2",y2);
})

function init(){
// a function to get the size of the window on resize
  w = window.innerWidth;
  h = window.innerHeight;
}

// you call the init on resize
setTimeout(function() {
  init();
  addEventListener('resize', init, false);
}, 15);

// a function to get the mouse position
function oMousePos(evt) {
   return { 
       x: evt.clientX,
       y: evt.clientY
      }
}


function map(n, a, b, _a, _b) {
  let d = b - a;
  let _d = _b - _a;
  let u = _d / d;
  return _a + n * u;
}
svg {
  border: 1px solid;
  position: absolute;
  margin: auto;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
}
<svg id="test" viewBox="0 0 120 120" width="240" >
  <circle cx="60" cy="60" r="20" fill="#d9d9d9" />
  <line id="_line" x1="55" y1="60" stroke="blue" />
</svg>

希望对您有所帮助。

更新

OP 评论说实际上他们希望红色小圆圈跟随鼠标。在这种情况下你需要计算眼睛中心和鼠标之间的角度,并使用这个角度绘制红色圆圈:

let m = {}
let c = {x:55,y:60}// the center of the eye
let r = whitecircle.getAttribute("r") - redcircle.getAttribute("r") - .5;
// where .5 is 1/2 stroke-width
test.addEventListener("mousemove",(e)=>{
// draw the line on mousemove
  m=oMousePosSVG(e);
  //_line.setAttributeNS(null,"x2",m.x);
  //_line.setAttributeNS(null,"y2",m.y);
  var angle = getAngle(m,c)
  //this are the coordinates for the center of the red circle 
  var x2 = c.x + r * Math.cos(angle);
  var y2 = c.y + r * Math.sin(angle);

  redcircle.setAttributeNS(null,"cx",x2);
  redcircle.setAttributeNS(null,"cy",y2);
})

function oMousePosSVG(e) {
// a function to detect the mouse position inside an SVG
      var p = test.createSVGPoint();
      p.x = e.clientX;
      p.y = e.clientY;
      var ctm = test.getScreenCTM().inverse();
      var p =  p.matrixTransform(ctm);
      return p;
}

function getAngle(p1,p2){
  // a function to calculate the angle between two points p1 and p2
  var deltaX = p1.x - p2.x;
  var deltaY = p1.y - p2.y;
  return Math.atan2(deltaY, deltaX);
}
<svg id="test" viewBox="0 0 120 120" width="100vw" height="100vh">
  <circle cx="60" cy="60" r="20" fill="#d9d9d9" />
  <circle id="whitecircle" cx="55" cy="60" r="5" fill="#fff" stroke="black" />
  <circle cx="55" cy="60" r="3" fill="#f00" id="redcircle"  />
  <!--<line id="_line" x1="55" y1="60" stroke="blue" />-->
</svg>