Make eyes follow cursor in this SVG - javascript

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>

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>

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 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>
The above script can be applied by wrapping all eyeballs and pupils in a group with a class "eye" like so :
<g class="eye">
<circle class="eyeball" cx="120" cy="50" r="25" />
<circle class="pupil" 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

How to position an SVG circle along another circle's path

I'm building a Gauge chart in a presentational component in React.
I just need to pass it a percentage and let the component do the rest. I can't use any animations because I'm taking a screenshot of the component to place the image in a Powerpoint presentation.
Here's a screenshot of what I'm trying to do:
As you can see in my code snippet, the circle <marker> is being positioned at the end of the grey <path> instead of at the end of the green <path>. How could I position the circle so it sits at the stroke-linecap of the green <path> as in the image above?
Here's the HTML code I have so far:
<div style="width:400px">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker
id="dot"
viewBox="0 0 10 10"
refX="4"
refY="4"
markerWidth="4"
markerHeight="4"
>
<circle
cx="4"
cy="4"
r="2"
fill="#FFFFFF"
stroke="#008000"
stroke-width="2"
/>
</marker>
</defs>
<path d="M20,60a35,35 0 1,1 60,0" stroke="#D3D7DB" stroke-width="4" fill="none" stroke-linecap="round"></path>
<path d="M20,60a35,35 0 1,1 60,0" stroke="#008000" stroke-width="6" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="75 35" marker-end="url(#dot)"></path>
</svg>
</div>
You can do it all in SVG by setting pathLength and using animationMotion to position the circle.
Some JavaScript and a W3C standard Web Component (supported in all modern Browsers) help in putting multiple gauges on screen and making them dynamic.
customElements.define("svg-gauge", class extends HTMLElement {
connectedCallback() {
let speed = 0.5; // set to 0.0001 for instant display!
let arc = "M20,60a35,35 0 1,1 60,0";
let col = this.getAttribute("color") || "green";
this.innerHTML =
`<input type="range" min="0" max="100" step="5" value="0"`+ // delete 2 lines
` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
`<svg viewbox="0 0 100 100">
<path d="${arc}" stroke="grey" stroke-width="6" fill="none" stroke-linecap="round"></path>
<path id="P" d="${arc}" stroke="${col}" stroke-width="8" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="75 35"/>
<circle stroke="${col}" cx="0" cy="0" fill="#fff" r="4" stroke-width="3">
<animateMotion dur="${speed}s" path="${arc}" keyPoints="0;0.75" fill="freeze" keyTimes="0;1" calcMode="linear"/>
</circle>
<text x="50%" y="40%" text-anchor="middle">XXX</text>
</svg>`;
this.style.display='inline-block';
this.percent = this.getAttribute("value") || "50";
}
set percent(val = 0) {
this.setAttribute("value", val);
let dash = val + " " + (105 - val);
this.querySelector("#P").setAttribute('stroke-dasharray', dash);
this.querySelector("animateMotion").setAttribute('keyPoints', '0;'+val/100);
this.querySelector("text").innerHTML =`${val} %`;
this.querySelector("animateMotion").beginElement();
this.querySelector("input").value = val;
}
})
<svg-gauge value="35" color="red" ></svg-gauge>
<svg-gauge value="50" color="orange"></svg-gauge>
<svg-gauge value="75" color="green" ></svg-gauge>
I went ahead and accepted Danny '365CSI' Engelman's answer above, but just in case anyone wants to do this without the animations here is how I ended up implementing it:
<div style="width:400px">
<svg viewBox="0 -10 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="M20,60a35,35 0 1,1 60,0" stroke="#D3D7DB" stroke-width="4" fill="none" stroke-linecap="round"></path>
<path d="M20,60a35,35 0 1,1 60,0" stroke="#008000" stroke-width="6" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="50 85"></path>
<circle
cx="0"
cy="0"
r="6"
stroke-width="6"
fill="#FFFFFF"
stroke="#008000"
>
<animateMotion
begin="0s"
dur="infinite"
repeatCount="infinite"
keyPoints="0.5;0.5"
fill="freeze"
keyTimes="0;1"
calcMode="linear"
path="M20,60a35,35 0 1,1 60,0"
></animateMotion>
</circle>
</svg>
</div>

How do I rotate and skew an SVG rect "in-place"?

I am playing with SVG and I am stumped by something.
I am trying to make the pink square into a diamond by using skew and rotate.
However I am getting strange behaviour that I cannot figure out how to overcome.
Adding the skew, gets me the diamond effect I want, but then I need to rotate and reposition it so it lines up with the circles.
<rect x="126" y="0" width="40" height="40"fill="pink" transform="skewY(10)" />
However, when I apply rotation transform="rotate(45)" to the rect, it doesn't rotate "in-place", but rotates [I think] relative from the corner of the page.
<rect x="126" y="0" width="40" height="40"fill="pink" transform="skewY(10)" />
Does anyone know how I can freely rotate and skew this rectangle (in SVG and not CSS or anything) without it moving around so wildly and awkwardly ?
<h1>Shapes</h1>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="blue" stroke="red" ></circle>
<circle cx="62" cy="20" r="20" fill="yellow" stroke="red" ></circle>
<circle cx="104" cy="20" r="20" fill="blue" stroke="red" ></circle>
<rect x="126" y="0" width="40" height="40"fill="pink"/>
<circle cx="188" cy="20" r="20" fill="green" stroke="red" ></circle>
</svg>
Simplest is to use transform-origin and transform-box
rect {
transform-origin: center;
transform-box: fill-box;
}
<h1>Shapes</h1>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="blue" stroke="red" ></circle>
<circle cx="62" cy="20" r="20" fill="yellow" stroke="red" ></circle>
<circle cx="104" cy="20" r="20" fill="blue" stroke="red" ></circle>
<rect transform="rotate(45)" x="126" y="0" width="40" height="40"fill="pink"/>
<circle cx="188" cy="20" r="20" fill="green" stroke="red" ></circle>
</svg>
<h1>Shapes</h1>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="21" cy="21" r="20" fill="blue" stroke="red"></circle>
<circle cx="63" cy="21" r="20" fill="yellow" stroke="red"></circle>
<circle cx="105" cy="21" r="20" fill="blue" stroke="red"></circle>
<rect x="154" y="0" width="40" height="40" fill="pink" transform="rotate(45, 154, 0)"/>
<circle cx="203" cy="21" r="20" fill="green" stroke="red"></circle>
</svg>
This can be done using rotate. For rotate
First argument is the angle of rotation i.e. 45 degrees.
Second argument is the x offset about which the rect is to be rotated and is calculated as follows:
x = (border_width_of_circle_1 * 2 + radius_of_circle_1 * 2) + (border_width_of_circle_2 * 2 + radius_of_circle_2 * 2) + (border_width_of_circle_3 * 2 + radius_of_circle_3 * 2) + 1/2 * diagonal_of_square
= (2 + 40) + (2 + 40) + (2 + 40) + (1/2 * sqrt(40^2 + 40^2))
= 42 + 42 + 42 + (1/2 * sqrt(3200))
= (42 * 3) + (1/2 * 56)
= 126 + 28 = 154
Third argument is the y offset about which the rect is to be rotated, which in our case will be 0.

Scripted SVG animation no-longer working in some browsers

I am trying to work out why the code below no-longer works in Firefox or Chrome.
The files were last modified over ten years ago.
The static svg dispays, but the script does not run. To my considerable surprise, it does work as it should in Edge, as does a more complicated diagram with interactive elements.
There is probably some obscure setting I need to doctor in Firefox, but I don't know where to look. I don't know when I last tried one of these files, but I would be fairly sure they still worked a couple of years ago.
The code is probably full of daftnesses, as I have done very little javascript, and I probably should now be using requestanimationframe, but that is not the point - it has worked, and still does in Edge.
(Question edited to remove link to irrelevant SMIL version of the animation.)
This is the html file:
<html>
<head>
<title>
SVG slider-crank animated by script
</title>
</head>
<body onload="main()">
<script type="text/javascript">
<!--
var svgdoc = null;
var crank = null;
var crosshead = null;
var conrod = null;
var pi = Math.PI;
function main()
{
var timer = null;
var angle = 0;
var diagram = document.getElementById('svg');
if (diagram && diagram.contentDocument)
{
svgdoc = diagram.contentDocument;
}
else
{
try
{
svgdoc = diagram.getSVGDocument();
}
catch(exception)
{
alert("Unable to get SVG document");
}
}
crank = svgdoc.getElementById('ShowCrank');
crosshead = svgdoc.getElementById('ShowCrosshead');
conrod = svgdoc.getElementById('ShowConRod');
timer = setInterval(function(){(angle = rotation(angle))}, 25);
}
function rotation(angle)
{
var step = 3;
var theta = angle * pi / 180;
var alpha = Math.asin(Math.sin(theta) / 5);
var offset = 100 * (Math.cos(theta) -1) - 500 * (Math.cos(alpha) - 1);
crank.setAttributeNS(null, 'transform', ("rotate(" + angle + ", 800, 300)"));
crosshead.setAttributeNS(null, 'transform', ("translate(" + offset + ", 0)"));
conrod.setAttributeNS(null, 'transform', ("translate(" + offset + ", 0) rotate(" + (alpha * 180 / pi) + ", 400, 300)"));
angle = angle < 360 - step ? angle + step : 0;
return angle;
}
-->
</script>
<object id="svg" type="image/svg+xml" data="Slider_Crank.svg" width="1200" height="800">
<param name="src" value="Slider_Crank.svg">
</object>
</body>
</html>
This is the svg file:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
width="1200"
height="800">
<title> Slider-Crank </title>
<defs>
<rect
id="Slidebar"
stroke-width="1"
stroke="black"
fill="silver"
fill-opacity="1"
x="0"
y="-12"
width="300"
height="24"
/>
<g id="Crosshead" stroke-width="1" stroke="black" fill-opacity="1">
<rect
fill="gold"
x="-50"
y="-25"
width="100"
height="50"
/>
<circle cx="0" cy="0" r="15" fill="white"/>
</g>
<g id="Crank" stroke-width="1" stroke="black" fill-opacity="1">
<path fill="silver"
d="M 99.959 40.000
A 40 40 0 0 0 99.959, -40.000
A 450 450 0 0 1 9.950, -49.000
A 50 50 0 1 0 9.950, 49.000
A 450 450 0 0 1 99.959, 40.000
z"/>
<circle cx="100" cy="0" r="25" fill="white"/>
<circle cx="0" cy="0" r="30" fill="lightgrey"/>
</g>
<g id="ConRod" stroke-width="1" stroke="black" fill-opacity="0.7">
<path fill="silver"
d="M 12.387 21.715
A 30 30 0 0 1 27.551 17.776
L 453.475 22.035
A 30 30 0 0 1 473.243 29.733
A 40 40 0 0 1 473.243 -29.733
A 30 30 0 0 1 453.475 -22.035
L 27.551 -17.776
A 30 30 0 0 1 12.387 -21.715
A 25 25 0 0 1 12.387 21.715
z"/>
<circle cx="0" cy="0" r="25" fill="silver"/>
<circle cx="0" cy="0" r="15" fill="white"/>
<circle cx="500" cy="0" r="40" fill="silver"/>
<circle cx="500" cy="0" r="25" fill="white"/>
</g>
</defs>
<use id="ShowTopSlidebar" xlink:href="#Slidebar" x="150" y="263"/>
<use id="ShowBottomSlidebar" xlink:href="#Slidebar" x="150" y="337"/>
<use id="ShowCrosshead" xlink:href="#Crosshead" x="400" y="300"/>
<use id="ShowCrank" xlink:href="#Crank" x="800" y="300"/>
<use id="ShowConRod" xlink:href="#ConRod" x="400" y="300"/>
</svg>
Thanks to Robert Longson: Firefox about:config setting 'security.fileuri.strict_origin_policy'

Scaling after rotation changes the position of rect element

Original rect, with a rotation:
<rect id="location_1" x="40" y="40" height="100" width="100" fill="red" stroke="2" stroke-width="2" transform="rotate(45, 90, 90)"></rect>
After scaling, the rotation center is changed based on the new bbox:
<rect id="location_1" x="40" y="40" height="200" width="200" fill="red" stroke="2" stroke-width="2" transform="rotate(45, 140, 140)"></rect>
After scaling of the rect, if we update the rotation center then the postion of the rect is changed. But if we do not update the center then it works as expected, till the other transformations are applied.
Scale as in updating width and height, not the scale transform
My question is how to scale keeping the position of the element same.?
JSFiddle with the situation
If clicked on Scale - No Update the position is not changed but when clicked on Scale - Update the position changes.
function scaleAndUpdate(id, update) {
var
elem = document.getElementById(id),
bbox, cx, cy;
elem.setAttribute('width', 200);
elem.setAttribute('height', 200);
if (update) {
bbox = elem.getBBox();
cx = bbox.x + (bbox.width / 2);
cy = bbox.y + (bbox.height / 2);
elem.setAttribute('transform', 'rotate(45, ' + cx + ', ' + cy + ')');
}
}
function reset(id) {
var
elem = document.getElementById(id);
elem.setAttribute('x', 40);
elem.setAttribute('y', 40);
elem.setAttribute('width', 100);
elem.setAttribute('height', 100);
elem.setAttribute('transform', 'rotate(45, 90, 90)');
}
<div>
<svg x="0" y="0" width="300" height="300" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="smallGrid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="100" height="100" patternUnits="userSpaceOnUse">
<rect width="100" height="100" fill="url(#smallGrid)" />
<path d="M 100 0 L 0 0 0 100" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" style="pointer-events: none;"></rect>
<g id="locationGroup">
<rect id="location_1" x="40" y="40" height="100" width="100" fill="red" stroke="2" stroke-width="2" transform="rotate(45, 90, 90)"></rect>
<circle cx="90" cy="20" r="5" fill="black"></circle>
</g>
</svg>
<button onclick="scaleAndUpdate('location_1')">Scale - No Update</button>
<button onclick="scaleAndUpdate('location_1', true)">Scale - Update</button>
<button onclick="reset('location_1')">Reset</button>
</div>

Captain America with svg

How I can do this star right with svg? I must use svg and I try with points but not work. I mustn't use element path. Thanks.
<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<polygon points="50,25 30,80 75,40 25,40 70,70" style="fill:white;"/>
</svg>
</body>
</html>
If you want to get the most accurate points for the pentastar, you can get them easily from the underlying pentagon.
A simple js function to obtain these points is something like this (REPL, which by the way you can use for polygons with any n edges):
var n = 5;
var points = [];
for (var i=0; i < n; i++) {
var x = 50;
var y = -50;
var r = 25;
points.push([x + r * Math.sin(2 * Math.PI * i / n),
y + r * Math.cos(2 * Math.PI * i / n)]);
}
Result is the pentagon points clock-wise, starting at the top (use all values as positive ones):
[ [ 50, -25 ],
[ 73.77641290737884, -42.27457514062631 ],
[ 64.69463130731182, -70.22542485937369 ],
[ 35.30536869268818, -70.22542485937369 ],
[ 26.22358709262116, -42.27457514062632 ] ]
Your order would be points[x], where x = 0, 3, 1, 4, 2.
And using them for your example rounded to the nearest pixel:
<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<polygon points="50,25 35,70 73,42 26,42 65,70" style="fill:white;"/>
</svg>
</body>
</html>
This seems a bit closer.
<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<polygon points="50,25 35,70 73,42 26,42 65,70" style="fill:white;"/>
</svg>
</body>
</html>
updated to use #frhd's number, make community wiki,
please see his answer for calculations
Here with path
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<!-- <polygon points="50,25 30,80 75,40 25,40 70,70" style="fill:white;"/> -->
<path fill="#fff" d="m50,25 5,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z" />
</svg>

Categories

Resources