css 使眼睛跟随此SVG中的光标

sf6xfgos  于 2022-12-20  发布在  其他
关注(0)|答案(2)|浏览(165)

我试图使SVG的眼睛瞳孔跟随光标使用本教程:
https://dev.to/anomaly3108/make-svg-follow-cursor-using-css-and-js-2okp
我们有4个分区:
1.眼球_左侧
1.眼球_右侧
1.瞳孔_左侧
1.瞳孔_右侧
看起来js起作用了,但是Angular 不是很准确。学生们走得太高了,他们没有停留在正确的位置。

let eyeball_left = document.querySelector("#eyeball_left"),
  pupil_left = document.querySelector("#pupil_left"),
  eyeArea_left = eyeball_left.getBoundingClientRect(),
  pupil_leftArea = pupil_left.getBoundingClientRect(),
  R_left = eyeArea_left.width / 2,
  r_left = pupil_leftArea.width / 2,
  centerX_left = eyeArea_left.left + R_left,
  centerY_left = eyeArea_left.top + R_left;
console.log(centerX_left)
console.log(centerY_left)
let eyeball_right = document.querySelector("#eyeball_right"),
  pupil_right = document.querySelector("#pupil_right"),
  eyeArea_right = eyeball_right.getBoundingClientRect(),
  pupil_rightArea = pupil_right.getBoundingClientRect(),
  R_right = eyeArea_right.width / 2,
  r_right = pupil_rightArea.width / 2,
  centerX_right = eyeArea_right.left + R_right,
  centerY_right = eyeArea_right.top + R_right;
console.log(centerX_right)
console.log(centerY_right)
document.addEventListener("mousemove", (e) => {
  let x_left = e.clientX - centerX_left,
    y_left = e.clientY - centerY_left,
    theta_left = Math.atan2(y_left, x_left),
    angle_left = (theta_left * 180) / Math.PI + 360;
  let x_right = e.clientX - centerX_right,
    y_right = e.clientY - centerY_right,
    theta_right = Math.atan2(y_right, x_right),
    angle_right = (theta_right * 180) / Math.PI + 360;
  pupil_left.style.transform = `translateX(${
      R_left - r_left + "px"
    }) rotate(${angle_left + "deg"})`;
  pupil_left.style.transformOrigin = `${r_left + "px"} center`;
  pupil_right.style.transform = `translateX(${
      R_right - r_right + "px"
    }) rotate(${angle_right + "deg"})`;
  pupil_right.style.transformOrigin = `${r_right + "px"} center`;
});
#monster {
  height: 100px;
  width: 400px;
}
<div id="monster">
  <svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="168.88 0 290.9 400.77">
                <g>
                  <title>Layer 1</title>
                  <path
                    id="svg_1"
                    fill="#6c63ff"
                    d="m296.30999,388.65991l0,-67.88825l-22,0l0,67.88825a13.98286,13.98286 0 0 0 -7,12.11175l36,0a13.98286,13.98286 0 0 0 -7,-12.11175z"
                  />
                  <path
                    id="svg_2"
                    fill="#6c63ff"
                    d="m355.30999,388.65991l0,-67.88825l-22,0l0,67.88825a13.98286,13.98286 0 0 0 -7,12.11175l36,0a13.98286,13.98286 0 0 0 -7,-12.11175z"
                  />
                  <circle
                    id="svg_3"
                    fill="#6c63ff"
                    r="145.45113"
                    cy="238.54887"
                    cx="314.33362"
                  />
                  <ellipse
                    id="svg_4"
                    fill="#fff"
                    ry="19.21053"
                    rx="57.63158"
                    cy="311.43609"
                    cx="314.33362"
                  />
                  <circle
                    id="svg_5"
                    fill="#fff"
                    r="24.69925"
                    cy="205.61654"
                    cx="262.19076"
                  />
                  <circle
                    id="svg_6"
                    fill="#fff"
                    r="24.69925"
                    cy="205.61654"
                    cx="366.47648"
                  />

                  {/* eyebol */}
                  <circle
                    id="eyeball_left"
                    fill="#3f3d56"
                    r="19.21053"
                    cy="205.31579"
                    cx="262.67948"
                  />
                  <circle
                    id="eyeball_right"
                    fill="#3f3d56"
                    r="19.21053"
                    cy="205.31579"
                    cx="366.73212"
                  />
                  {/* eyebol */}

                  <ellipse
                    id="svg_9"
                    fill="#3f3d56"
                    ry="74.09774"
                    rx="96.05263"
                    cy="87.09774"
                    cx="314.33362"
                  />
                  <ellipse
                    id="svg_10"
                    fill="#3f3d56"
                    ry="18"
                    rx="38"
                    cy="18"
                    cx="314.33362"
                  />
                  <path
                    id="svg_11"
                    fill="#3f3d56"
                    d="m315.39428,259.75517c6.323,-6.40629 16.04713,-6.53419 24.2561,-4.42458c9.786,2.51489 18.116,8.57423 27.17791,12.79851a49.55555,49.55555 0 0 0 14.58024,4.54776a38.27945,38.27945 0 0 0 36.63871,-17.0858a38.7584,38.7584 0 0 0 4.54212,-30.91717a1.50128,1.50128 0 0 0 -2.89283,0.79752a35.70693,35.70693 0 0 1 -3.34417,27.11259a35.29669,35.29669 0 0 1 -35.30417,17.03843a49.62651,49.62651 0 0 1 -14.22886,-4.81212c-8.76148,-4.28973 -16.98465,-10.00419 -26.54935,-12.41745c-9.21411,-2.32481 -19.9481,-1.90083 -26.997,5.241c-1.35753,1.37543 0.76245,3.4981 2.12132,2.12132l-0.00002,-0.00001z"
                  />
                  <path
                    id="svg_12"
                    fill="#3f3d56"
                    d="m315.39428,257.63384c-6.22928,-6.31139 -15.3898,-7.36984 -23.77027,-5.92682c-9.6154,1.65567 -17.88675,6.88869 -26.379,11.36988c-8.6772,4.57879 -17.92825,8.08187 -27.8912,6.48578a35.20905,35.20905 0 0 1 -23.1751,-14.039a35.77653,35.77653 0 0 1 -5.208,-30.05228a1.50128,1.50128 0 0 0 -2.89283,-0.79752a38.80889,38.80889 0 0 0 2.82291,27.89016a37.47231,37.47231 0 0 0 20.97865,18.1838c9.41409,3.348 19.35061,2.63 28.52089,-1.11613c9.42621,-3.85066 17.77515,-10.13661 27.45644,-13.36827c8.93708,-2.98324 20.2603,-3.75844 27.41619,3.49176c1.3583,1.37619 3.47944,-0.7453 2.12132,-2.12132l0,-0.00004z"
                  />
                  <circle
                    id="svg_13"
                    fill="#3f3d56"
                    r="11"
                    cy="258.5"
                    cx="314.36371"
                  />
                  {/* PUPIL */}
                  <circle
                    id="pupil_left"
                    fill="#fff"
                    r="4"
                    cy="198.77165"
                    cx="254.31"
                  />
                  <circle
                    id="pupil_right"
                    fill="#fff"
                    r="4"
                    cy="198.77165"
                    cx="376.31"
                  />
                  {/* PUPIL */}
                </g>
              </svg>
o4tp2gmn

o4tp2gmn1#

这里的基本思想是,我使用一个线元素来决定眼睛的旋转/方向。一条线可以在两端和中间有一个标记。在这个例子中,眼球是一个标记,然后我根据鼠标的位置更新线的末端。
首先是一个带有轮廓的简单示例,然后是完整示例:

let l1 = document.querySelector("#l1");
let l2 = document.querySelector("#l2");
let svg1 = document.querySelector("#svg1");

const toSVGPoint = (svg, x, y) => {
  let p = new DOMPoint(x, y);
  return p.matrixTransform(svg.getScreenCTM().inverse());
};

document.addEventListener('mousemove', e => {
  let p = toSVGPoint(svg1, e.clientX, e.clientY);
  l1.setAttribute('x2', p.x);
  l1.setAttribute('y2', p.y);
  l2.setAttribute('x2', p.x);
  l2.setAttribute('y2', p.y);
});
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50">
  <circle cx="75" cy="25" r="20" fill="none" stroke="blue" />
  <circle cx="125" cy="25" r="20" fill="none" stroke="blue" />
  <line marker-start="url(#pupil)" id="l1" x1="75" y1="25" stroke="red" />
  <line marker-start="url(#pupil)" id="l2" x1="125" y1="25" stroke="red" />
  <defs>
    <marker id="pupil" viewBox="0 0 10 10" refX="16" refY="5" markerWidth="10"
      markerHeight="10" orient="auto-start-reverse">
      <rect width="10" height="10" fill="none" stroke="red"/>
      <circle fill="none" stroke="green" r="4" cy="5" cx="5" />
    </marker>
  </defs>
</svg>

一个一个二个一个一个一个三个一个一个一个一个一个四个一个

f0brbegy

f0brbegy2#

备选方案:更新<circle>cxcy属性

这种方法需要计算
1.光标坐标和眼球中心之间的Angular
1.圆上的新点位置(基于Angular 、半径

演示示例

const svg = document.getElementById('svg')
document.addEventListener("mousemove", (e) => {
  movePupils(e);
});

function movePupils(e) {
  let eyes = svg.querySelectorAll('.eye');
  eyes.forEach(eye=>{
    let eyeball = eye.querySelector('.eyeball');
    let pupil = eye.querySelector('.pupil');
    
    // get center cx/cy and radius
    let pCenter = {x: +eyeball.getAttribute('cx'), y:+eyeball.getAttribute('cy') };
    let rEyeball = +eyeball.getAttribute('r');
    let rPupil = +pupil.getAttribute('r');
    
    // translate cursor HTML DOM coordinates to SVG DOM units
    let pCursor = new DOMPoint(e.clientX, e.clientY);
    pCursor = pCursor.matrixTransform(svg.getScreenCTM().inverse());

    // get angle between cursor and eyeball center;
    let angle = (Math.atan2(pCursor.y - pCenter.y, pCursor.x - pCenter.x) * 180) / Math.PI;
        
    //get distance between cursor and eyeball center
    let a = pCursor.x - pCenter.x;
    let b = pCursor.y - pCenter.y;
    let distance =  Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    
    // adjust pupil movement inside eyeball boundaries
    let offset = distance<rEyeball ? 1/rEyeball*distance : 1;
    let radiusOuter = (rEyeball-rPupil)*offset;
    
    let pMoved = {
      x: pCenter.x + Math.cos((angle * Math.PI) / 180) * radiusOuter,
      y: pCenter.y + Math.sin((angle * Math.PI) / 180) * radiusOuter
    }
    // update attributes
    pupil.setAttribute('cx', pMoved.x)
    pupil.setAttribute('cy', pMoved.y)
        
  })

}
body{
  margin:5em;
}

svg{
  width:20em;
  overflow:visible;
  border:1px solid #ccc;
}
<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 100">

  <g class="eye">
    <circle class="eyeball" cx="30" cy="50" r="25" fill="none" stroke="#000" />
    <circle class="pupil" cx="30" cy="50" r="5" fill="#000" />
  </g>

  <g class="eye">
    <circle class="eyeball" cx="75" cy="25" r="20" fill="none" stroke="#000" />
    <circle class="pupil" cx="75" cy="25" r="5" fill="#000" />
  </g>
  
  <g class="eye">
    <circle class="eyeball" cx="120" cy="50" r="25" fill="none" stroke="#000" />
    <circle class="pupil" cx="120" cy="50" r="5" fill="#000" />
  </g>
  
  <g class="eye">
    <circle class="eyeball" cx="75" cy="75" r="20" fill="none" stroke="#000" />
    <circle class="pupil" cx="75" cy="75" r="5" fill="#000" />
  </g>
  
</svg>

上面的脚本可以通过使用类"eye"将所有眼球和瞳孔 Package 在一个组中来应用,如下所示:

<g class="eye">
    <circle class="eyeball" cx="120" cy="50" r="25" />
    <circle class="pupil" cx="120" cy="50" r="5" />
  </g>

就像@chrwahl的示例一样,我们需要将HTML DOM坐标转换为SVG用户单位。

let pCursor = new DOMPoint(e.clientX, e.clientY);
pCursor = pCursor.matrixTransform(svg.getScreenCTM().inverse());
计算Angular
let pCenter = {x: +eyeball.getAttribute('cx'), y:+eyeball.getAttribute('cy') };
let angle = (Math.atan2(pCursor.y - pCenter.y, pCursor.x - pCenter.x) * 180) / Math.PI;
微调眼球区域内的瞳孔定位

计算光标和眼球中心之间的距离,允许我们进一步调整瞳孔运动:如果光标在眼球内,瞳孔将以当前鼠标坐标为中心。

let a = pCursor.x - pCenter.x;
let b = pCursor.y - pCenter.y;
let distance =  Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
let offset = distance<rEyeball ? 1/rEyeball*distance : 1;
let radiusOuter = (rEyeball-rPupil)*offset;
圆上的点
let pOnCircle = {
  x: pCenter.x + Math.cos((angle * Math.PI) / 180) * radiusOuter,
  y: pCenter.y + Math.sin((angle * Math.PI) / 180) * radiusOuter
}

由于我们希望圆形被放置在眼球的边界内,因此我们需要使用一个减小的半径来进行计算(根据瞳孔的半径)。

相关问题