Home > database >  Make eyes follow cursor in this SVG
Make eyes follow cursor in this SVG

Time:12-20

I am trying to make the eye pupil of the svg to follow cursor using this tutorial:

https://dev.to/anomaly3108/make-svg-follow-cursor-using-css-and-js-2okp

We have 4 divs:

  1. eyeball_left
  2. eyeball_right
  3. pupil_left
  4. pupil_right

looks like the JS is working, but the angle is not really accurate. the pupils are going too high and they do not stay in the correct position.

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>

CodePudding user response:

The basic idea here, is that I use a line element to decide the rotation/direction of the eye. A line can have a marker in both ends and in the middle. In this example the eye ball is a marker and then I update the end of the line based on the position of the mouse.

First a simple example with outlines and then the full example:

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>

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);
});
#monster {
  height: 100px;
  width: 400px;
}
<div id="monster">
  <svg id="svg1" xmlns="http://www.w3.org/2000/svg" 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"
                  />
                </g>
                {/* PUPIL */}
                <line marker-start="url(#pupil)" id="l1" x1="262.67948" y1="205.31579" stroke="none" />
                <line marker-start="url(#pupil)" id="l2" x1="366.73212" y1="205.31579" stroke="none" />
                {/* PUPIL */}
                <defs>
                  <marker id="pupil" viewBox="0 0 10 10" refX="16" refY="5" markerWidth="10"
                    markerHeight="10" orient="auto-start-reverse">
                    <circle fill="#fff" r="4" cy="5" cx="5" />
                  </marker>
                </defs>
              </svg>
            </div>

CodePudding user response:

Alternative: update <circle> cx and cy attributes

This approach requires to calculate

  1. the angle between cursor coordinates and the eyeball's center
  2. the new point position on the circle (based on angle, radius

Demo example

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 >
    <circle  cx="30" cy="50" r="25" fill="none" stroke="#000" />
    <circle  cx="30" cy="50" r="5" fill="#000" />
  </g>

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

The above script can be applied by wrapping all eyeballs and pupils in a group with a class "eye" like so :

  <g >
    <circle  cx="120" cy="50" r="25" />
    <circle  cx="120" cy="50" r="5" />
  </g>

Like in @chrwahl's example we need to convert HTML DOM coordinates to SVG user units.

let pCursor = new DOMPoint(e.clientX, e.clientY);
pCursor = pCursor.matrixTransform(svg.getScreenCTM().inverse());

Calculate angles

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;

Fine tune pupil positioning within eyeball area

Calculating the distance between cursor and eyeball center, allows us to further adjust the pupil movement: If the cursor is within the eyeball, the pupil will be centered around the current mouse coordinates.

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;  

Point on circle

let pOnCircle = {
  x: pCenter.x   Math.cos((angle * Math.PI) / 180) * radiusOuter,
  y: pCenter.y   Math.sin((angle * Math.PI) / 180) * radiusOuter
}

Sinc we want the circle to be placed within the eyball's boundaries we need to use a decreased radius for this calculation (according to the pupil's radius).

  • Related