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:
- eyeball_left
- eyeball_right
- pupil_left
- 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
- the angle between cursor coordinates and the eyeball's center
- 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).