Multiple preserveAspectRatio in svg? - svg

I'm looking to have an svg fill a particular space in my layout, so preserveAspectRatio="none" seems like a good first approach.
However, within the svg, there is a mask that I do not want to stretch / warp. Rather, it should occupy 100% the width, with the height scaling according to its ratio. The two images illustrate the mask's behaviour when the parent svg is in either landscape or portrait. (Note: the grey in the image is the rest of the <svg>, which should stretch to fit)
Can the mask have its own aspectRatio settings? Is there a better way to achieve this? Or, is it even possible?
```
<!-- this should scale according to its bounding parent -->
<svg class="fix" viewbox="0 0 2880 1620" preserveAspectRatio="none..?">
<!-- this should scale according to some intrinsic ratio -->
<mask id="mask"
maskUnits="userSpaceOnUse"
maskContentUnits="userSpaceOnUse">
<rect fill="white" x="0" y="0" width="2880" height="1620" />
<path fill="black" d="M57.59,60h409c344.17,.... ...."/>
</mask>
<rect mask="url(#mask)" x="0" y="0" width="100%" height="100%" />
</svg>
```
edit: using mask-image instead of just mask seems possible (as it has additional positioning options), but this does not seem to work with svg elements.

You don't need to use preserveAspectRatio="none" to have the rectangle and mask fill the page. Just extend the <rect> and <mask> past the boundaries of the SVG in all directions. Root <svg> elements have overflow: visible by default, so the extended rect will fill SVGs parent container - as long as you extend far enough of course.
<rect mask="url(#mask)" x="-1000%" y="-1000%" width="3000%" height="3000%" />
I've used 1000% here, but you can adjust that if you need more or less (than 10x).
And note that we are just using the standard default SVG preserveAspectRatio. So we still get the automatic centerinng and scaling of the SVG.
body {
margin: 0;
padding: 0;
}
svg {
width: 100vw;
height: 100vh;
}
<svg viewbox="0 0 2880 1620">
<!-- this should scale according to some intrinsic ratio -->
<mask id="mask"
maskUnits="userSpaceOnUse"
maskContentUnits="userSpaceOnUse"
x="-1000%" y="-1000%" width="3000%" height="3000%">
<rect fill="white" x="-1000%" y="-1000%" width="3000%" height="3000%" />
<circle cx="1440" cy="810" r="400" fill="black"/>
</mask>
<rect mask="url(#mask)" x="-1000%" y="-1000%" width="3000%" height="3000%" />
</svg>
Demo in fiddle format

Related

Positioning rotated letters inside viewBox in svg

I have code which generates svg (by means of producing the XML DOM). It takes input text and randomly scatters it's letters on page as shown below.
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- ... styles omitted -->
  <text x="20" y="150" rotate="45">M</text>
<text x="100" y="80" rotate="45">W</text>
<text x="90" y="50" rotate="270">X</text>
<!-- etc ... -->
</svg>
I have problem how to fill the page efficiently without having letters extend outside of the view box. Either I limit the random values for the x and y, but then there is lot of space left empty around borders. Or I use wider ranges for x,y but then the letters bleed outside. As show the red letters in the jsfiddle example (https://jsfiddle.net/5zqrugx1/1/).
I tried to adjust the x, y ranges based on rotation, but still it does not help much because of different letter shapes.
I am looking for way to style/position these letters in svg in a way which would force them to be completely inside the view port while being able to fill the space border-to-border (this second condition added later to clarify). Something like giving 0-100% where 0% would mean "touching left border" and 100% would be "touching right border". Is there any way to do it?
Below is example which I hand-edited to achieve more-less desired result.
What this probably amounts to is controling the center of rotation in such a way that it is in the center of the glyph. This way, all you need is a 0.5em padding at each edge.
You can start out with positioning the text control point at the middle both horizontally and vertically:
text {
text-anchor:middle;
dominant-baseline:middle;
}
Unfortunately, using the rotate attribute of the text element does not work as expected (at least in Firefox). But you can get around that by adding a post-rotation via a transform attribute. The best way to formulate it would be to also position the glyph with a translation:
<text transform="translate(40 100) rotate(60)">A</text>
Order is important - translate must come before rotate.
The following example rotates all glyphs around the center of the circles they are sitting in. It turns out the font-defined middle is a bit off, so you have to tweak with a dy attribute. If it shows still a bit wrong on your screen, this is because the font used by your system might define or compute a different middle line. For a system-independent experience, you would need to use a web font for you to have complete control.
circle {
fill: none;
stroke: blue;
}
text {
text-anchor: middle;
dominant-baseline: middle;
font-family: sans-serif;
font-size: 50px;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 100">
<circle r="25" cx="50" cy="50" />
<circle r="25" cx="100" cy="50" />
<circle r="25" cx="150" cy="50" />
<circle r="25" cx="200" cy="50" />
<circle r="25" cx="250" cy="50" />
<circle r="25" cx="300" cy="50" />
<text dy="4" transform="translate(50 50) rotate(60)">A</text>
<text dy="4" transform="translate(100 50) rotate(120)">B</text>
<text dy="4" transform="translate(150 50) rotate(180)">C</text>
<text dy="4" transform="translate(200 50) rotate(240)">D</text>
<text dy="4" transform="translate(250 50) rotate(300)">E</text>
<text dy="4" transform="translate(300 50) rotate(360)">F</text>
</svg>

How to automatically create the minimal size of the viewbox which fits for the complete content?

I have a simple or complex SVG graphic. For example a rotated rectangle.
Without calculating you cannot know the minimal size of the viewbox, where the graphic fits into completely.
<svg viewBox="0 0 30 30">
<rect x="20" y="0" width="100" height="20" transform="rotate(45)" fill="black" />
</svg>
The result is, that the graphic does not fit into the viewbox.
Is there any method, how to get an the minimal size of the viewbox, where the graphic is shown completely?
Ideally I do not want to declare a size/ratio of a viewbox. I just want that the minimal size is a result of the content of the SVG graphics.
Is there any disadvantage, when I do not declare the viewBox attribute at all?
Thanks for your help.
One way to do it is wrapping the transformed rectangle in a <g> element and then get the value of the bounding box for theG. Next you use the values of the bounding box (BB) to reset the viewBox of theSVG. I hope it helps.
// the bounding box for the wrapping g
let BB = theG.getBBox();
theSVG.setAttributeNS(null, "viewBox", `${BB.x} ${BB.y} ${BB.width} ${BB.height}`)
svg{border:1px solid}
<svg id="theSVG" viewBox="0 0 30 30" width="300">
<g id="theG">
<rect x="20" y="0" width="100" height="20" transform="rotate(45)" fill="black" />
</g>
</svg>

SVG or HTML text that scales to fully fit a container so it stretches, bot vertically and horizontally, disregarding aspect ratio

I need to make text automatically stretch in both dimensions, to fill a container. It will distort.
This shows the the container space in red
This shows what a long name would normally resize to put in that space and maintaining aspect ratio
.
This shows what my client wants to happen
.
I would prefer to use SVG but I will work with what works.
I have searched for a solution to the best of my abilities but all seem to either refer to maintaining aspect ratio or stretching text when the page or viewbox changes dimensions.
That's quite a broad question, but yes you can do it with svg, I'll let you implement it though since you didn't provided anything to chew on.
The key point is to set your svg's preserveAspectRatio to "none":
svg{
height: 100vh;
width: 50vw;
}
body{
margin:0;
}
<div>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 40" preserveAspectRatio="none">
<text x="0" y="35" font-family="Verdana" font-size="35">
Hello, out there
</text>
</svg>
</div>
If your text is already part of an SVG (as it appears in your example), you will probably need to use a nested <svg> element.
<svg width="400" height="400">
<rect width="400" height="400" fill="rebeccapurple"/>
<!-- rect representing area that our text has to squeeze into -->
<rect x="20" y="50" width="200" height="50" fill="white"/>
<!-- x y width height match above rect -->
<!-- viewBox values need to match text bounds -->
<svg x="20" y="50" width="200" height="50"
viewBox="0 8 244 28" preserveAspectRatio="none">
<text x="0" y="35" font-family="Verdana" font-size="35">
HELLO THERE
</text>
</svg>
</svg>
The hardest part is workoing out the correct values for viewBox. It needs to match the bounds of the (normal unsqueezed) text.

SVG mask is clipping stroke on masked element

I have a completely vertical <path> with a thick stroke-width applied. I would like to add a mask (i.e. mask="url(#...)") to it, however when I do, (how do i put this?) the stroke is ignored when computing the visible area. Here's a code snippet:
function toggleMask() {
var path = $('path');
if (path.attr('mask')) {
path.removeAttr('mask');
} else {
path.attr('mask',"url(#test)");
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<div><button onclick="toggleMask()">toggle mask</button></div>
<svg width="400" height="400">
<defs>
<mask id="test">
<rect
width="100%"
height="100%"
x="0"
y="0"
fill="white">
</rect>
<circle r="20" cx="35" cy="80" fill="black"></circle>
</mask>
</defs>
<path
d="M30,30L30,300"
stroke-width="40"
stroke="black"></path>
<path
d="M50,30L100,300"
stroke-width="40"
stroke="black"></path>
</svg>
I expected the applied mask to look something like this:
Thanks in advance!
Add maskUnits="userSpaceOnUse"to your mask and it'll work as you want.
https://codepen.io/nerdmanship/pen/XazJVR
The reason is that when the maskUnits attribute is unspecified it defaults to the objectBoundingBox value, which means that the mask is applied to the area inside a target element's bounding box. The bounding box of a completely vertical or horizontal path has the width or height of 0. The result is that the mask is applied to a total area of 0 pixels of the target element.
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskUnits

How to make SVG scale only on X axis?

I have the following SVG graphic that is currently scaling when the window is resized, but the aspect ratio is maintained. How could I get this to only scale on the X axis, and keep the Y at 80px?
<svg width="100%" viewBox="0 0 300 80">
<rect x="0" y="0" fill="yellow" height="80" width="100"/>
<rect x="100" y="0" fill="blue" height="80" width="100"/>
<rect x="200" y="0" fill="red" height="80" width="100"/>
</svg>
Thank you,
You have a couple of options. First, you could simply specify the height of the graphic, e.g. using CSS.
svg {
height: 80px;
width: 100%;
}
If that's not the effect you want, you can get more sophisticated with the preserveAspectRatio attribute. It's hard to say what value would work for you since it's not completely clear what you want (assuming the CSS approach above doesn't do it), but maybe something like:
<svg viewBox="0 0 300 80" preserveAspectRatio="none">
Check out the reference link for more details.

Resources