Preserve aspect ratio for SVG Text - javascript

This is an edited copy of https://stackoverflow.com/questions/29105120/preserve-aspect-ratio-for-svg-text-and-react-to-javascript-touch-events which I will remove, because it asked 2 related but technically different questions.
as I already explained in my last question, I'm trying to make a navigation-div with 4 buttons, one to go left, one to go right, another one to go down and yet another one to go up. Plus there needs to be an OK-button in the middle.
That worked really well with the explanation given here: Using CSS and HTML5 to create navigation buttons using trapezoids
I created the SVG like:
<div class="function height3x svg-container" style="height: 112px; width: 200px;">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="mySVG" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none" style="background-color: whitesmoke">
<g class="function" id="keyboard_btn_24">
<polygon id="ok" points="25,25 75,25 75,75 25,75"></polygon>
<text id="ok_text" x="39" y="55">OK</text>
</g>
<g class="function" id="keyboard_btn_25">
<polygon id="up" stroke="black" stroke-width="0.1" points="0,0 100,0 65,35 35,35"></polygon>
<text x="42" y="20"></text>
</g>
<g class="function" id="keyboard_btn_26">
<polygon id="right" stroke="black" stroke-width="0.1" points="100,0 100,100 65,65 65,35"></polygon>
<text x="81" y="53"></text>
</g>
<g class="function" id="keyboard_btn_27">
<polygon id="down" stroke="black" stroke-width="0.1" points="0,100 35,65 65,65 100,100"></polygon>
<text x="42" y="91"></text>
</g>
<g class="function" id="keyboard_btn_28">
<polygon id="left" stroke="black" stroke-width="0.1" points="0,0 35,35 35,65 0,100"></polygon>
<text x="5" y="53"></text>
</g>
</svg>
</div>
But I have two problems that I seem not to be able to figure out.
First of all: While I want the SVG to be responsive, I don't want to scale the text without keeping the aspect-ratio given by the font.
I already tried (unsuccessfully) preserveAspectRatio, which does not seem to do much to the text.
Questions: How can you make the tag keep it's aspect ratio, while changing the aspect ratio of the svg?
You can view and edit the minimal example: jsFiddle
Paulie_D - Commented on my old question:
As for the Aspect Ratio, you should remove the width & height 100%
values. They aren't really needed. The SVG will scale to the required
size based on the div size. - jsfiddle.net/2qqrL7ng/1 –
This is not an option, because the SVG Element needs to respond to size changes that can not keep the aspect ratio. Just the text needs to keep the ratio, everything else should be as responsive as possible.
EDIT
Switching the svg argument of perserveAspectRatio from "none" to "xMidYMid" keeps the aspect ratio of the SVG, but the desired effect is, that the SVG itself does not keep it's aspect ratio, but the -tags do. Which means the following would NOT be a solution:
<div class="function height3x svg-container" style="height: 112px; width: 200px;">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="mySVG" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="background-color: whitesmoke">
<g class="function" id="keyboard_btn_24">
<polygon id="ok" points="25,25 75,25 75,75 25,75"></polygon>
<text id="ok_text" x="39" y="55">OK</text>
</g>
<g class="function" id="keyboard_btn_25" preserveAspectRatio="xMidYMid">
<polygon id="up" stroke="black" stroke-width="0.1" points="0,0 100,0 65,35 35,35"></polygon>
<text x="42" y="20"></text>
</g>
<g class="function" id="keyboard_btn_26">
<polygon id="right" stroke="black" stroke-width="0.1" points="100,0 100,100 65,65 65,35"></polygon>
<text x="81" y="53"></text>
</g>
<g class="function" id="keyboard_btn_27">
<polygon id="down" stroke="black" stroke-width="0.1" points="0,100 35,65 65,65 100,100"></polygon>
<text x="42" y="91"></text>
</g>
<g class="function" id="keyboard_btn_28">
<polygon id="left" stroke="black" stroke-width="0.1" points="0,0 35,35 35,65 0,100"></polygon>
<text x="5" y="53"></text>
</g>
</svg>
</div>
/EDIT
Thanks in advance.

I know this is an old question but I did find a way to do what is asked in pure SVG, no JS, and I've used it in prod without issues!
The trick is to define a parent <svg> element without a viewBox so that it takes the container's dimensions, and then define children <svg> elements for the different kind of preserveAspectRatio values that you want!
In the following snippet, I just took the SVG used in the question and put all the <polygon> apart in a <svg viewBox="0 0 100 100" preserveAspectRatio="none">.
div {
height: 112px;
width: 200px;
}
div > svg {
height: 100%;
width: 100%;
background-color: whitesmoke;
}
polygon {
fill: none;
stroke: black;
stroke-width: 0.1;
}
<div>
<!-- SVG wrapper without viewBox to take parent's dimensions -->
<svg>
<!-- sub SVG that will be resized -->
<svg viewBox="0 0 100 100" preserveAspectRatio="none">
<g>
<polygon points="0,0 100,0 65,35 35,35"></polygon>
<text x="42" y="20"></text>
</g>
<g>
<polygon points="100,0 100,100 65,65 65,35"></polygon>
<text x="81" y="53"></text>
</g>
<g>
<polygon points="0,100 35,65 65,65 100,100"></polygon>
<text x="42" y="91"></text>
</g>
<g>
<polygon points="0,0 35,35 35,65 0,100"></polygon>
<text x="5" y="53"></text>
</g>
</svg>
<!-- regular SVG elements that won't be resized -->
<text id="ok_text"
x="50%" y="50%"
text-anchor="middle"
alignment-baseline="middle">
OK
</text>
</svg>
</div>
PS: the end result can even be used as the src of a regular image <img src="./my-weird-responsive-svg.svg" /> which makes this trick really robust!

Questions: How can you make the tag keep it's aspect ratio, while changing the aspect ratio of the svg?
You cannot. If the text is part of the SVG it gets scaled with the SVG. There is no way to make a part of the SVG exempt from the scaling.
Possible solutions:
(1) Remove the text from the SVG and position it on top. For example use a positioned <div> in your HTML or something.
(2) Use JS to calculate the aspect ratio of the SVG and apply an inverse scaling transform to the <text> element.

If you have javascript control on the current dimensions (like going fullscreen, for instance, or simply by checking what viewport size the user is using), you can add a simple class in a parent div outside the SVG.
Then, style the SVG text's font-size for both cases: when the parent div has such class (it tells you that your svg has scaled), and when the parent div hasn't such a class (i.e., the SVG is non-scaled). If this is your case, this simple solution works.
.svg_text {font-size: 12px;} /* normal non-scaled SVG */
.case_for_big_screen_thus_SVG_scaled .svg_text {font-size: 6px;} /* this parent class only is added when viewport dimensions make your SVG to be scaled */

Related

I want to make it appear as if text is always the same distance away from circle

My problem is that when i am rotating this svg i counter rotate the text to keep it horizontal. however this makes it look like it is further from the circle when the degree is for example 0 or 180 when its high above or below. but when its 90 its right next to it. i believe this is actually correct since the center of the text is the same distance away. is there a way to fix this?
<svg
height="200"
viewBox="0 0 100 100"
fill="none"
transform="rotate(45, -16, -16)"
>
<circle
cx="30"
cy="30"
r="5"
fill="#FFFFFF"
stroke="#3D3D3D"
stroke-width="1.5"
/>
<g transform="rotate(90, 20, 20)">
<path
d="M15.6528 7.06945L15.4939 7.36025L15.602 7.67352L17.8504 14.1941L2.3372 6.25279L18.9596 1.01652L15.6528 7.06945Z"
fill="white"
stroke="#3D3D3D"
stroke-width="1.5"
/>
</g>
<text
x="30"
y="70"
text-anchor="middle"
font-size="14px"
fill="#3D3D3D"
pointer-events="none"
font-weight={500}
transform="rotate(-45, 30, 70)"
>
some text here
</text>
</svg>
Remember to set your transform-origin, because they help solv this: if you want the text at a fixed distance, put it in a <g> with the circle's center as transform-origin, and a rotation transform, and then give the text element a rotation by the same angle in the opposite direction so that it stays upright.

SVG in HTML: How to use a multicolored SVG as a mask

I know how to use SVG masks to completely "cut out" the mask in another shape, if the mask is monochrome.
How can I use a multicolored SVG definition X as the mask so that the outer shape of X defines the "hole" to be cut out?
Here are three images that illustrate what I am trying to achieve:
svg #1 to be used as mask
svg #2 on which the outer shape of #1 should be used as a cut-out
result
Creating a white-filled version of the shape as #enxaneta proposed is not applicable to my problem, as I have many "complicated" external SVG definitions, and I don't want to change every single one of them.
Is there another, simpler way to achieve what I want?
You need to define your paths with no fill. Then you use your paths for the mask and fill them with white. To draw the image you fill those paths with the colors of your choice.
svg{border:1px solid; width:49vw}
svg:nth-child(2){background:red;}
mask use{fill:white;}
<svg viewBox="0 0 100 50">
<defs>
<polygon id="a" points="30,5 70,20 75,40 20,20" />
<circle id="b" cx="50" cy="25" r="15" />
<circle id="c" cx="60" cy="35" r="10" />
<mask id="m">
<use xlink:href="#a"/>
<use xlink:href="#b"/>
<use xlink:href="#c"/>
</mask>
</defs>
<g id="complexShape">
<use xlink:href="#a" fill="lightblue" />
<use xlink:href="#b" fill="gold"/>
<use xlink:href="#c" fill="red"/>
</g>
</svg>
<svg viewBox="0 0 100 50">
<rect width="100" height="50" style="mask: url(#m)" />
</svg>
The colour of a mask determines the final opacity of the masked object at that point. The R, G, B, and A components of the mask colour are combined in a formula to determine a luminance value that is used to set the final transparency that the mask will be a that point. So, for example, if the mask is red, the final masked result will be semi transparent.
There is no way to make a coloured object be a solid (not translucent) mask. Only full white will do that.
Update
Assuming you have an external SVG image that looks like the following:
<svg viewBox="0 0 100 50">
<polygon id="a" points="30,5 70,20 75,40 20,20" fill="lightblue"/>
<circle id="b" cx="50" cy="25" r="15" fill="gold"/>
<circle id="c" cx="60" cy="35" r="10" fill="red" stroke="blue" stroke-width="4"/>
</svg>
You can turn this into a "mask" version by adding three lines to the start of your SVG.
<svg viewBox="0 0 100 50">
<filter id="blacken"><feFlood flood-color="black"/><feComposite operator="in" in2="SourceGraphic"/></filter>
<style>svg :not(#maskbg) { filter: url(#blacken); }</style>
<rect id="maskbg" x="-100%" y="-100%" width="300%" height="300%" fill="white"/>
<polygon id="a" points="30,5 70,20 75,40 20,20" fill="lightblue"/>
<circle id="b" cx="50" cy="25" r="15" fill="gold"/>
<circle id="c" cx="60" cy="35" r="10" fill="red" stroke="blue" stroke-width="4"/>
</svg>
This is something that could easily be scripted. This method should work for almost all SVGs.
Once you have all the mask variants built, you can apply them using mask-image.
https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image

Making inline SVG responsive in browsers without using javascript?

I've been reviewing articles like this one which are really good, but possibly dated, and I'm wondering if any progress has been made on a simple way to make inline svg responsive while maintaining the aspect ratio?
In other words if we have svg with a 1:1 aspect ratio inside a div container, and the container shrinks from 400x400px to 200x200 px, then the view box width and height parameters double.
I am confused about what you want. I don't know what you mean, in your question, when you talk about "view box width and height parameters double". Perhaps you are confused about how viewBox works.
As long as the SVG has a viewBox it should be responsive.
In the following example, I've put the SVG in a div container and animated the container size to simulate the page size changing.
#container {
width: 200px;
background: linen;
animation: scale 1s alternate infinite;
}
#keyframes scale {
from { width: 200px; }
to { width: 100px; }
}
<div id="container">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="gold"/>
<rect x="10" y="50" width="20" height="50" fill="gold"/>
<rect x="70" y="50" width="20" height="50" fill="gold"/>
<circle cx="35" cy="45" r="5"/>
<circle cx="65" cy="45" r="5"/>
</svg>
</div>
But it could just as easily be a container that is the width of the page. Try running the following snippet. Then click "Fullpage" and resize the browser window.:
#container {
width: 100%;
background: linen;
}
<div id="container">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="gold"/>
<rect x="10" y="50" width="20" height="50" fill="gold"/>
<rect x="70" y="50" width="20" height="50" fill="gold"/>
<circle cx="35" cy="45" r="5"/>
<circle cx="65" cy="45" r="5"/>
</svg>
</div>
If you don't need to change svg with css or modify it, I suggest you to use it as an img element. You create an img and put the path to your svg in the src. Here's what I mean: https://codepen.io/tlemaitre/pen/PBKGOo
I've recently had a similar problem, I have a container that needs to be preserveAspectRatio="none" to keep the shape fluid on different window sizes but the image inside gets gets distorted. I've not seen any solution to this problem with or without JS, so I don't think it's possible.

How to zoom all things except some paths / text on an svg canvas without changing its exact position

I want to zoom all things except some paths / text on an svg canvas without changing the path / text exact position using svgpanzoom js.
The actual working of my needs is something like a map with some markers on it, I also want to dynamically place new pin or markers also ...
<?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" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="640px" height="480px" viewBox="0 0 640 480">
<g id="viewport">
<rect x="27" y="33" fill="#14A0E8" width="134" height="134"/>
<rect x="27" y="167" fill="#8ABB50" width="134" height="134"/>
<rect x="161" y="231" fill="#F97659" width="134" height="134"/>
<rect x="295" y="167" fill="#14A0E8" width="134" height="134"/>
<rect x="57" y="365" fill="#8ABB50" width="372" height="57"/>
<rect x="161" y="33" fill="#CCCC99" width="134" height="134"/>
<rect x="429" y="33" fill="#F97659" width="134" height="134"/>
<rect x="429" y="301" fill="#F97659" width="134" height="64"/>
<rect x="429" y="202" fill="#CCCC99" width="134" height="64"/>
<g class="noZoom">
<path d="M103.912,213.845c0,5.248-6.218,9.378-9.502,14.649c-4.293-5.507-9.501-9.401-9.501-14.649s4.254-9.501,9.501-9.501
S103.912,208.598,103.912,213.845z"/>
<circle fill="#FFFFFF" cx="94.548" cy="212.497" r="3.349"/>
</g>
<g class="noZoom">
<path d="M480.912,334.846c0,5.247-6.219,9.378-9.502,14.648c-4.293-5.508-9.502-9.401-9.502-14.648
c0-5.248,4.254-9.502,9.502-9.502C476.657,325.344,480.912,329.598,480.912,334.846z"/>
<circle fill="#FFFFFF" cx="471.548" cy="333.496" r="3.349"/>
</g>
<text class="noZoom" transform="matrix(1 0 0 1 80.1992 240)" font-size="8">Marker 1</text>
<text class="noZoom" transform="matrix(1 0 0 1 459.1992 359.5)" font-size="8">Marker 2</text>
<text class="noZoom" transform="matrix(1 0 0 1 201.1992 204.3438)" font-size="8">Some Information</text>
<text class="noZoom" transform="matrix(1 0 0 1 325.6992 274.3438)" font-size="8">Some Another Information</text>
</g>
</svg>
Above shown is an example of the svg that looks like and I am trying to zoom all things inside SVG but the markers and text should be in same place itself without increaing its zooming level
I am using the svgpanzoom
I've done similar thing by adding listener to onZoom event. In this listener I've increased font size of text elements and stroke width of paths depending on scale factor. Another way is to have separate group for noZoom elements and listen to onPan event where you can get new x and y translation and call getZoom to obtain new CTM and as result change coordinates of each noZoom element.
You can zoom one group element. You can place in it any elements you want. Documentation. Demo.

Interacting with a .svg image

I have an image in the format .svg like the one below.
I want to make a webpage where the user can interact with a image like this, but with more nodes. The structure will be similar to a tree.
Is it possible to interact with this .svg image directly, using javascript/html/css?
If so, how?
Note: By interact I mean being able to click on the nodes -and the webpage recognizing it- and when one node is selected the color of the other nodes change.
Note2: I just have the .svg file, I don't know if I'm able to define this as a inline svg on html.
Note3: This image will have many nodes (80+), so I would rather not having to define a clickable area for each one of them and so on... But if this is the only solution, no problem.
Edit:
Here is the content of my .svg file:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: g Pages: 1 -->
<svg width="134pt" height="116pt"
viewBox="0.00 0.00 134.00 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>g</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 130,-112 130,4 -4,4"/>
<!-- a -->
<g id="node1" class="node"><title>a</title>
<ellipse fill="none" stroke="black" cx="27" cy="-90" rx="27" ry="18"/>
<text text-anchor="middle" x="27" y="-86.3" font-family="Times New Roman,serif" font-size="14.00">a</text>
</g>
<!-- b -->
<g id="node2" class="node"><title>b</title>
<ellipse fill="none" stroke="black" cx="27" cy="-18" rx="27" ry="18"/>
<text text-anchor="middle" x="27" y="-14.3" font-family="Times New Roman,serif" font-size="14.00">b</text>
</g>
<!-- a->b -->
<g id="edge1" class="edge"><title>a->b</title>
<path fill="none" stroke="black" d="M27,-71.6966C27,-63.9827 27,-54.7125 27,-46.1124"/>
<polygon fill="black" stroke="black" points="30.5001,-46.1043 27,-36.1043 23.5001,-46.1044 30.5001,-46.1043"/>
</g>
<!-- c -->
<g id="node3" class="node"><title>c</title>
<ellipse fill="none" stroke="black" cx="99" cy="-18" rx="27" ry="18"/>
<text text-anchor="middle" x="99" y="-14.3" font-family="Times New Roman,serif" font-size="14.00">c</text>
</g>
<!-- b->c -->
<g id="edge2" class="edge"><title>b->c</title>
<path fill="none" stroke="black" d="M54,-18C56.6147,-18 59.2295,-18 61.8442,-18"/>
<polygon fill="black" stroke="black" points="61.9297,-21.5001 71.9297,-18 61.9297,-14.5001 61.9297,-21.5001"/>
</g>
</g>
</svg>
The SVG would need to be inline to have interaction on a page. If you embed an image then the image (.svg) is treated as a single object. For the inline SVG each node should have a separate ID if you want to select them individually.
Here's one I created for another answer.
svg {
display: block;
width: 20%;
margin: 25px auto;
border: 1px solid grey;
stroke: #006600;
}
#buttons polygon:hover {
fill: orange;
}
#buttons rect:hover {
fill: blue
}
#center {
fill: #00cc00;
}
#top {
fill: #cc3333;
}
#right {
fill: #663399;
}
#left {
fill: #bada55;
}
<svg viewbox="0 0 100 100">
<g id="buttons">
<rect id="center" x="25" y="25" height="50" width="50" />
<polygon id="top" points="0,0 100,0 75,25 25,25" />
<polygon id="right" points="100,0 75,25 75,75 100,100" />
<polygon id="bottom" points="0,100 25,75 75,75 100,100" />
<polygon id="left" points="0,0 25,25 25,75 0,100" />
</g>
</svg>
You don't necessarily need to have the svg inline, you could have it in an object tag.
So the html would look like...
<div id="svgdiv">
<object id="svgobject" data="objectclicktest.svg"></object>
</div>
and correspending js
var mySvg = document.getElementById("svgobject").contentDocument.querySelectorAll('svg');
var myNodes = mySvg[0].querySelectorAll('.node');
for( var i = 0; i < myNodes.length; i++ ) {
myNodes[i].addEventListener('click', changeStyle );
}
function changeStyle() {
this.style.fill="blue";
}
Example Click on letters and they should go blue. Note, (I don't think this would work in a setup like a fiddle though)
inline svg elements can interact like other html elements, you can set css rules on them and apply js on them too, you dont need areas
svg is a markup language, meaning that you can use css selector libraries such as jquery to interact with the given svg. You can query the svg in order to get an element by its id, or get an array of elements selected by class. You can attach event handlers to them such as click, mouseover, mouseenter, etc. You can even style them with css.

Categories