Scaling after rotation changes the position of rect element - javascript

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>

Related

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'

How to rotate SVG inside mask?

I have an SVG mask that I would like to rotate the elements inside. However, when I attempted it, it looks like the entire mask is being rotated.
I tried at this SO post, but the fiddle wasn't working (the image wasn't showing anymore).
This is what it originally looks like
What happened
What I wanted to happen
I wanted only the "liquid" element to be rotated, but somehow the liquid element is being rotated the wrong way.
This is the SVG (with mask element):
<svg width="200px" height="200px" viewBox="0 0 200 200">
<defs>
<rect id="path-1" x="75" y="65" width="50" height="100" rx="5"></rect>
</defs>
<g id="bottle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bottle-lid" stroke="#916548" stroke-width="5" x="82.5" y="52.5" width="35" height="15" rx="5"></rect>
<rect id="bottle-holder" stroke="#916548" stroke-width="5" x="117.5" y="55.5" width="30" height="7" rx="3.5"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Mask"></g>
<rect id="bottle-liquid" fill="#3498DB" mask="url(#mask-2)" x="40" y="80" width="120" height="120"></rect>
<rect id="bottle-outline" stroke="#916548" stroke-width="5" mask="url(#mask-2)" x="77.5" y="67.5" width="45" height="95" rx="5"></rect>
</g>
</svg>
And this is the JS (I am using a GSAP library)
const liquid = document.querySelector("#bottle-liquid")
const tl = new TimelineMax()
tl.to(liquid, 1, {
rotation: 20
})
How can I rotate just the a certain element inside a masked SVG?
I've made a few changes to your SVG, the most important: I've putted the bottle-liquid inside a g element and I'm applying the mask to the g element and the transform to the bottle-liquid. I hope it helps.
<svg width="200px" height="200px" viewBox="0 0 200 200">
<defs>
<rect id="path-1" x="75" y="65" width="50" height="100" rx="5"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#bottle-outline"></use>
</mask>
</defs>
<g id="bottle" stroke-width="5" stroke="#916548" fill="none" fill-rule="evenodd">
<rect id="bottle-lid" x="82.5" y="52.5" width="35" height="15" rx="5"></rect>
<rect id="bottle-holder" x="117.5" y="55.5" width="30" height="7" rx="3.5"></rect>
<g mask="url(#mask-2)">
<rect id="bottle-liquid" stroke="none" fill="#3498DB" x="40" y="80" width="120" height="120" transform="rotate(20 100 140)"></rect>
</g>
<rect id="bottle-outline" x="77.5" y="67.5" width="45" height="95" rx="5"></rect>
</g>
</svg>

Scale overriding SVG width and height attributes

I have an SVG element with a defined width and height, like <svg width="100px" height="100px"></svg>, filled with various elements.
I want to have a kind of "zoom" feature, where a particular region of the SVG is zoomed in on to fill the whole SVG element.
I planned to do this with the scale and translate attributes, i.e. by applying scale(x) to the SVG element and then calculating what I need to translate by in order to have the desired region remain visible.
I expected this would keep the SVG at 100x100px and simply hide any element outside this region. However, this doesn't happen; the whole SVG element just gets bigger instead, even though the dimensions are explicitly defined as attributes.
Clearly I'm misunderstanding the way that scaling and SVG dimensions work, does anyone know how I can achieve what I'm trying to do here?
You can warp the svg with a div element and use overflow: hidden.
<div style="width: 300px; height: 300px; overflow: hidden">
<svg width="100" height="100" style="transform: scale(4);">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
</div>
Do you mean something like this?
function setViewBox(vbx){
svg.setAttribute("viewBox",vbx)
}
<svg viewBox="0 0 100 100" width="200px" height="200px" id="svg">
<rect x="0" y="0" width="100" height="100" stroke="black" fill="white" onclick="setViewBox('0 0 100 100')"/>
<circle cx="25" cy="25" r="25" fill="red" onclick="setViewBox('0 0 50 50')"/>
<rect x="60" y="10" width="30" height="30" fill="green" onclick="setViewBox('50 0 50 50')"/>
<rect x="10" y="60" width="30" height="30" fill="blue" transform="rotate(45,25,75)" onclick="setViewBox('0 50 50 50')"/>
<path d="M50 100L75 50L100 100z" fill="yellow" onclick="setViewBox('50 50 50 50')"/>
</svg>
or more like that?
var last=null
function setTransform(evt,trs){
reset()
svg.appendChild(evt.target)
evt.target.setAttribute("transform","scale(2 2) translate("+trs+")")
last=evt.target
}
function reset(){
if(last) last.removeAttribute("transform")
}
<svg viewBox="0 0 100 100" width="200px" height="200px" id="svg">
<rect x="0" y="0" width="100" height="100" stroke="black" fill="white" onclick="reset()"/>
<circle cx="25" cy="25" r="25" fill="red" onclick="setTransform(event,'0 0')"/>
<rect x="60" y="10" width="30" height="30" fill="green" onclick="setTransform(event,'-50 0')"/>
<rect x="10" y="60" width="30" height="30" fill="blue" transform="rotate(45,25,75)" onclick="setTransform(event,'0 -50')"/>
<path d="M50 100L75 50L100 100z" fill="yellow" onclick="setTransform(event,'-50 -50')"/>
</svg>

SVG: Apply fill color to "masked" image on hover

I'm using clipPath to apply different "masking" effects to an image.
How can I fill the clipped image with a color on hover? I've tried using :hover in CSS, but that didn't seem to work, unless I was targeting the incorrect element.
I'm using jQuery in this project, so if a JS solution is needed, I can lean on jQuery.
Here's the HTML that I'm working with:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<clipPath id="ellipse">
<ellipse cx="50" cy="35.5" rx="49.5" ry="35" />
</clipPath>
<clipPath id="hexagon">
<polygon points="25, 0 75, 0 100, 43.30127018922193 75, 86.60254037844386 25, 86.60254037844386 0, 43.30127018922193"/>
</clipPath>
<clipPath id="rectangle">
<rect x="0" y="0" width="100" height="70"></rect>
</clipPath>
</defs>
<g>
<image preserveAspectRatio="none" x="0" y="0" width="100%" height="100%" xlink:href="http://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Bucephala-albeola-010.jpg/800px-Bucephala-albeola-010.jpg" id="clippy" clip-path="url(#hexagon)">
</g>
</svg>
You might want to use a filter effect for giving the image some color on hover (see Tinkerbin):
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<style type="text/css">
image:hover {
filter:url(#Matrix);
}
</style>
<defs>
<clipPath id="ellipse">
<ellipse cx="50" cy="35.5" rx="49.5" ry="35" />
</clipPath>
<clipPath id="hexagon">
<polygon points="25, 0 75, 0 100, 43.30127018922193 75, 86.60254037844386 25, 86.60254037844386 0, 43.30127018922193"/>
</clipPath>
<clipPath id="rectangle">
<rect x="0" y="0" width="100" height="70"></rect>
</clipPath>
<filter id="Matrix" filterUnits="objectBoundingBox"
x="0%" y="0%" width="100%" height="100%">
<feColorMatrix type="matrix" in="SourceGraphic"
values="1 0 0 0 .5
.1 .9 0 0 0
.1 0 .9 0 0
0 0 0 1 0"/>
</filter>
</defs>
<g>
<image preserveAspectRatio="none" x="0" y="0" width="100%" height="100%" xlink:href="http://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Bucephala-albeola-010.jpg/800px-Bucephala-albeola-010.jpg" id="clippy" clip-path="url(#hexagon)">
</g>
</svg>
Edit: Some explanation about the filter:
The applied filter changes the color of every rastered pixel. It takes its original color value, applies the matrix specified by <feColorMatrix> to the color vector, and the resulting color vector becomes the displayed color.
How does the matrix work?
The matrix consists of four rows. The first row calculates the new red component, the second the green one, third blue, fourth alpha.
What's the meaning of the five numbers in each row? The first number is multiplied by the original color's red component, the second by the green one, third blue, fourth alpha. All four products are summed up, and the fifth value in the row is added as well (as a constant that does not depend on any of the original color components).
Let's have a look at the above example: Let's assume we have a grey pixel with color values like
rgba(25%,25%,25%,1)
What would be the resulting red value? The first row that calculates the red value is
1 0 0 0 .5
We calculate the following:
1*r + 0*g + 0*b + 0*a + .5
= 1*.25 + 0*.25 + 0*.25 + 0*1 + .5 = .75
This means, the resulting red component for the pixel is 75%. The other components are calculated analogously.
Not sure if this is exactly what you want. Mouse events aren't sent to areas outside a cliparea. Quick & dirty, works in IE9, haven't tested in FF for example.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<script type="application/ecmascript">
function fillit(evt) {
document.getElementById('fillarea').setAttribute('display', 'visible');
}
function emptyit(evt) {
document.getElementById('fillarea').setAttribute('display', 'none');
}
</script>
<defs>
<clipPath id="ellipse">
<ellipse cx="50" cy="35.5" rx="49.5" ry="35" />
</clipPath>
<clipPath id="hexagon">
<polygon points="25, 0 75, 0 100, 43.30127018922193 75, 86.60254037844386 25, 86.60254037844386 0, 43.30127018922193" />
</clipPath>
<clipPath id="rectangle">
<rect x="0" y="0" width="100" height="70"></rect>
</clipPath>
</defs>
<g>
<image preserveAspectRatio="none" x="0" y="0" width="100%" height="100%"
xlink:href="http://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Bucephala-albeola-010.jpg/800px-Bucephala-albeola-010.jpg"
id="clippy" clip-path="url(#hexagon)" onmouseover="fillit(evt)" />
<rect x="0" y="0" width="100%" height="100%" fill="red" display="none"
id="fillarea" clip-path="url(#hexagon)" onmouseout="emptyit(evt)" />
</g>
</svg>

Categories

Resources