SVG viewBox scales some elements differently than others - svg

The client asked to scale the drawing down so I added width/height and viewBox to the SVG element. The problem is that some elements are scaled differently than others. The structure of the svg is like this:
<svg width="100%" height="100%" viewBox="0 50 700 200">
<defs>...</defs>
<g id="group-ab">
Here are the boxes (drawn with polyline) that are scaled correctly, both in their own group (group-a, group-b)
</g>
<g id="group-a-id">
<text>A</text>
<circle id="group-a-id-bg-circle"></circle>
</g>
<g id="group-B-id">
<text>B</text>
<circle id="group-b-id-bg-circle"></circle>
</g>
<path id="group-a-pattern" d="..."></path>
<path id="group-b-pattern" d="..."></path>
</svg>
The path is the pattern for the pieces. It seems that everything that isn't under the group "group-ab" isn't scaled correctly, see the image below. Normally (when not using viewBox) the pattern fills up the boxes and the A/B are centered with the boxes (same translates are done to them).
Why is this happening? One would think that it doesn't matter if elements are within groups or not if svg is scaled.

Related

Clipping an image with a preserved aspect ratio using a polygon that doesn't need to have its aspect ratio preserved

I'd like to create a responsive SVG that consists of an image clipped by a polygon.
The image needs to preserve its aspect ratio and get cropped, while the polygon needs to adapt in width to its container but preserve the same height.
I don't want the whole thing to scale homothetically.
visual explanation of the issue
If I set preserveAspectRatio="none" to the svg, the polygon stretches like planned, but the image is then distorted. I've tried setting preserveAspectRatio="xMidYmid slice" to the image, but since the svg is set not to have its aspect ratio preserved it doesn't work as planned.
Here's my current code:
<svg viewBox="0 0 1440 810" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<clipPath id="clip">
<polygon points="0 750,1440 810,1440 60,0 0" />
</clipPath>
</defs>
<g aria-hidden="true" clip-path="url(#clip)">
<image
opacity="0.75"
preserveAspectRatio="xMinYMid slice"
xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6e/Monasterio_Khor_Virap%2C_Armenia%2C_2016-10-01%2C_DD_25.jpg/2880px-Monasterio_Khor_Virap%2C_Armenia%2C_2016-10-01%2C_DD_25.jpg"
/>
</g>
</svg>
Thanks in advance for your help.

Zero stroke-width in SVG

Having used Postscript for years, I am now learning SVG. There is a feature of PS that I have not been able to replicate so far: zero-width lines. In PS, a line with zero width is always visible: PostScript converts zero line width to the smallest printable width. On the screen, when zooming they never get any thinkness, yet are visible no matter the scale. I have used them when I wanted to render very thin lines, without worring about the final resolution I was going to use, and they turned out really useful.
However, in the official SVG docs (https://www.w3.org/TR/svg-strokes/) it says that:
A zero value causes no stroke to be painted. A negative value is invalid.
Is there a way in SVG to build zero-width lines in the sense of PostScript?
As Robert said, the nearest thing to what you want in SVG is vector-effect="non-scaling-stroke". This fixes the stroke width at 1 no matter how the SVG is scaled.
This works on Chrome and Firefox (and probably Opera - haven't checked), but AFAIK not IE/Edge.
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80"
fill="none" stroke="black" stroke-width="1"
vector-effect="non-scaling-stroke"/>
</svg>
Note that antialiasing will come into play depending on the position of the lines. The position will be affected by the scale.
If your lines are rectilinear (horizontal or vertical), you might also want to use shape-rendering="crispEdges". This will turn off antialiasing for the shape on which it is used, resulting in sharp one-pixel lines.
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80"
fill="none" stroke="black" stroke-width="1"
vector-effect="non-scaling-stroke" shape-rendering="crispEdges"/>
</svg>

Avoiding the aliasing / thin "bleed between touching elements

I have an svg which has multiple stroked paths, and a rectangle shape behind them that has the shape of the paths cut out of them. Effectively, the stroked paths should be "plugging the holes" in the rectangle shape.
The reason for this is that I would like to animate the paths so that they are erased, revealing what is underneath through the holes in the rectangle shape.
That's all well and good, and the animation works fine. The problem is that there is a hairline-thin space between the holes and the outside of the path strokes, so you can see what is underneath even while the paths are still there. You can see a screen capture of that here:
How do I avoid this happening? The space is not in the svg, because making it bigger on the page still has the space hairline thin:
I figure it has something to do with the aliasing, but have no idea how to combat it. I can't just apply a thicker stroke to the paths, because then the stroke starts to bleed into the other shapes, as seen here:
What else is there to do?
You could use a mask instead of a clip-path, since masks allow using the stroke to define the masked area.
<mask id="strokemask" maskContentUnits="objectBoundingBox"
x="0" y="0" width="100%" height="100%">
<circle cx="0.5" cy="0.5" r="0.1" stroke="white" fill="white"
stroke-width="0.02"/>
<circle cx="0.5" cy="0.5" r="0.15" stroke="white" fill="none"
stroke-width="0.03"/>
<circle cx="0.5" cy="0.5" r="0.22" stroke="white" fill="none"
stroke-width="0.05"/>
<circle cx="0.5" cy="0.5" r="0.3" stroke="white" fill="none"
stroke-width="0.06"/>
</mask>
Here's a live example of an animated mask that uses some stroked circles.

Scaling when using a defined symbol

I want to create a SVG graphic that loads an external SVG file, stores it as a symbol into its defs section and uses this symbol in the main section:
<svg width="1000" height="1000">
<defs>
<symbol id="sym1" width="50" height="50" viewBox="0 0 45 45">
<svg width="45" height="45">
<rect x="0" y="0" width="45" height="45" style="stroke:red;stroke-width:1;fill:none"/>
</svg>
</symbol>
</defs>
<rect x="100" y="100" width="50" height="50" style="stroke:black;stroke-width:1;fill:none"/>
<use x="100" y="100" xlink:href="#sym1"></use>
</svg>
http://jsfiddle.net/tx0rpxdf/4/
One frame condition is that I cannot change the inner SVG element, because it will be loaded from an external file.
The symbol should have a size of 50x50, so I define its width and height accordingly. I know that the inner SVG element has a size of 45x45, so I define its viewBox as "0 0 45 45".
To test the usage of the symbol, I create a black rectangle of size 50x50 and place a symbol instance at the same position. The lines should match.
But they don't match using Chrome (Chromium) on Debian Jessie. Even worse, it produces different results in Firefox and Opera.
What's the problem here and how can I produce the correct result in all browsers that support SVG?
There are a couple of issues:
a <rect> with width/height 45 and a stroke-width of 1 is not 45 user units wide, it's 46 user units wide as the stroke pokes out inside by 0.5 of a unit and outside by 0.5 of a unit on each edge. If you can't change the inner <svg> element then you won't be able to fix this issue though.
drawing two shapes one on top of another will not make one hide another as there will be antialiasing in effect. shape-rendering="optimizeSpeed" can prevent this.

Programmatically centering svg path

I'm working on a PHP script that generates a jpg wallpaper from an SVG-file according to the screen resolution of the visitor. The wallpaper consists of a circular gradient (rectangle) background and a path on top of it. How would you go about centering the path horizontally and vertically to the rectangle? Remember that the rectangle's size and proportions are not a constant. Should I separate the background and path to different svg files or is there an easy way to center paths? Maybe a framework?
This is easilly achieved by using nested <svg> elements and the preserveAspectRatio attribute. Put your background in the outer svg and your path in the inner one.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%">
<rect id="background" width="100%" height="100%" fill="grey"/>
<svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 30 40" width="100%" height="100%">
<g>
<circle cx="15" cy="20" r="10" fill="yellow"/>
<circle cx="12" cy="17" r="1.5" fill="black"/>
<circle cx="18" cy="17" r="1.5" fill="black"/>
<path d="M 10 23 A 8 13 0 0 0 20 23" stroke="black" stroke-width="2" fill="none"/>
</g>
</svg>
</svg>
Run this snippet and try resizing the window.
To get this to work, all you need to ensure is that the viewBox attribute on the inner <svg> element is correctly set.
If you know the coordinates of the paths, you could take the total of the x/y coordinates and divide by the number of coordinates, this will give you the average position for the coordinate set. Then, offset each coordinate by the coordinates for half the width/height of the square, plus any offset, minus the difference between the center of the coordinate set and half the width/height of the square.
This should result in your coordinates being centered within the square, I think (it is rather early here, and I've just started my first coffee, so I could be wrong). This is of course assuming you know all the variables in play (the width/height of the square, any offset applied and the coordinates of the path).

Resources