Fixed stroke width in SVG - svg

I would like to be able to set the stroke-width on an SVG element to be "pixel-aware", that is always be 1px wide regardless of the current scaling transformations applied. I am aware that this may well be impossible, since the whole point of SVG is to be pixel independent.
Context follows:
I have an SVG element with its viewBox and preserveAspectRatio attributes set. It looks something like this
<svg version="1.1" baseProfile="full"
viewBox="-100 -100 200 200" preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg" >
</svg>
This means that when I scale that element, the actual shapes inside it scale accordingly (so far so good).
As you can see, I have set up the viewBox so that the origin is in the center. I would like to draw an x- and a y-axis within that element, which I do thus:
<line x1="-1000" x2="1000" y1="0" y2="0" />
Again, this works fine. Ideally, though, this axis would always be only 1px wide. I have no interest in the axes getting fatter when i scale the parent svg element.
So am I screwed?

You can use the vector-effect property set to non-scaling-stroke, see the docs. Another way is to use transform(ref).
That will work in browsers that support those parts from SVG Tiny 1.2, for example Opera 10. The fallback includes writing a small script to do the same, basically inverting the CTM and applying it on the elements that shouldn't scale.
If you want sharper lines you can also disable antialiasing (shape-rendering=optimizeSpeed or shape-rendering=crispEdges) and/or play with the positioning.

Here is a more concise answer based on Erik's answer to help you get started quickly.
<div style="background: blue; width: 100%; height: 130px;">
<svg xml:id="root" viewBox="0 0 100 100" width="100%" height="100%" preserveAspectRatio="none">
<rect xml:id="r" vector-effect="non-scaling-stroke" x="0" y="0" width="100" height="100" fill="none" stroke="#88CE02"
stroke-linecap="square" stroke-width="10" stroke-miterlimit="30"/>
</svg>
</div>
Adding the vector-effect="non-scaling-stroke" to the SVG rect makes the border size (or stroke size) fixed.

Related

Inline SVG pattern repeating horizontally and scaling vertically?

I'm looking for a way to use an inline SVG pattern that scales vertically and only repeats horizontally. Does anyone know if this is possible and if so how?
I know I can makes this just using a SVG as a background-image, but I want to be able to use this SVG in a javascript/component-based workflow so inline is the best fit for that.
Here is link with my work-in-progress: https://codepen.io/devotee/pen/GRJJpKL
And some code:
<div class="divider">
<svg width="100%" height="40px">
<defs>
<pattern id="pattern" x="0" y="0" width="60" height="6" patternUnits="userSpaceOnUse">
<path fill="none" stroke="#F5A861" d="M60 5C45 5 45 1 30 1S15 5 0 5"/>
</pattern>
</defs>
<rect x="10" y="6" width="100%" height="12" fill="url(#pattern)" />
</svg>
As you can see in the link, this repeats in both directions, so setting a bigger height value does not accomplish what I want. I would like this pattern to always fill the containers height (or simply be set to a value with CSS) but repeat horizontally.
Here are some images to illustrate what I mean:
Top is wanted behaviour, bottom is unwanted behaviour:
The background scales vertically and does not repeat. It takes as much space vertically as it can (fills parent height or whatever height value it has specified)
Top is wanted behaviour, bottom is unwanted behaviour:
It does not stretch the SVG horizontally but merely repeats it.
Any ideas or input on how to achieve this?

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>

SVG in Safari: Element positioned incorrectly when translated

I am trying to make an SVG shape a dropzone, and it works perfectly in Chrome/Firefox, but when testing on Safari I have come across a strange behaviour which I am looking for a way to get round.
The problem (shown in the code below and this fiddle https://jsfiddle.net/tp5e98rs/) is that when the viewBox width/height does not match the view port width/height (the first SVG), a translated element is not positioned correctly when viewed in the inspector. If you inspect the first rectangle, the blue highlight is not directly over it, but if you inspect the second rectangle the blue highlight is, as you can see in the attached image. The difference between the two SVGs is that in one the viewbox doesn't match the view port, and in the other they do match. If you do the same in Chrome, the highlight is correct in both cases.
This is a problem for making dropzones from translated rectangles, because the dropzone ends up being where the incorrect highlight is shown.
Any ideas of a workaround to fix this problem?
<svg width="100" height="100" viewBox="0 0 200 200">
<rect transform="translate(20 20)" width="100" height="100" fill="#aaeeee"></rect>
</svg>
<svg width="100" height="100" viewBox="0 0 100 100">
<rect transform="translate(10 10)" width="50" height="50" fill="#aaeeee"></rect>
</svg>

How to get correct SVG dimensioning / aspect ratio?

I'm making some pointers by hand coding svgs. I have defined a polygon using points in a 100x100 square and gotten the aspect ratio that I want by setting the width and height attributes of the outer SVG element. Here is a jsfiddle of the graphic: http://jsfiddle.net/62WpR/.
Unfortunately, the text is being compressed by the width and height attributes. Is there any way to use these without compressing text, or doing something silly like applying a transform to it?
If this is not possible, I guess I will have to define paths in a coordinate space with the final aspect ratio that I want. Disappointing.
Solved it kind of.
By setting the viewbox to a 1:2 ratio and using transforms on both of the polygons in the svg, I was able to get the dimensions I wanted. As long as the dimensions in the CSS are set to a 2:1 ratio, the graphic will display right.
I have also made judicious use of the preserveAspectRatio property to keep the image at the same aspect ratio. I'm only setting the width in the CSS: http://jsfiddle.net/62WpR/3/
<svg baseProfile="full" contentScriptType="text/ecmascript" contentStyleType="text/css" id="svg" preserveAspectRatio="xMinYMin meet" version="1.2" viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
<g transform="scale(1 .5)" stroke-linejoin="bevel" stroke="black">
<polygon vector-effect="non-scaling-stroke" fill="#666" id="tab" points="20.0,95.0 5.0,50.0 20.0,5.0 90.0,5.0 95.0,15.0 95.0,85.0 90.0,95.0"></polygon>
<polygon vector-effect="non-scaling-stroke" fill="#FFEA00" id="insert" points="25.0,95.0 10.0,50.0 25.0,5.0" visibility="visible"></polygon>
</g>
<text fill="white" font-size="30" text-anchor="middle" x="60" y="36">999</text>
</svg>​

Resources