SVG in Safari: Element positioned incorrectly when translated - svg

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>

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?

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: why does y="0" start outside the viewport instead of in the top edge for text?

This is the only thing preventing me from understanding how the coordination system works...
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="400" x="0" y="0">
<text x="0" y="0">
<tspan>✉</tspan>
</text>
</svg>
If the y-axis points down why does y="0" start from the outside of the viewport? Shouldn't it start right at the top edge? It blows my logic away...
The x-axis points to the right and it starts right in the left edge of the viewport, now this is logic and normal behavior.
Why does the y-axis behave like this? Or why make one start from the outside and the other not? What is the logic behind this? Unless I'm misunderstanding how it works...
It's all in the SVG specification
the initial coordinate system has the origin at the top/left with the x-axis pointing to the right and the y-axis pointing down
The origin for text is basically the bottom left corner of the glyph for left-to-right text.
For most uses of Latin text (i.e., writing-mode:lr, text-anchor:start and alignment-baseline:baseline) the alignment-point in the glyph will be the intersection of left edge of the glyph cell (or some other glyph-specific x-axis coordinate indicating a left-side origin point) with the Latin baseline of the glyph.
default baseline is in text downside, can use attr dominant-baseline change baseline, here is mdn example
<svg viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg">
<path d="M20,20 L180,20 M20,50 L180,50 M20,80 L180,80" stroke="grey" />
<text dominant-baseline="auto" x="30" y="20">Auto</text>
<text dominant-baseline="middle" x="30" y="50">Middle</text>
<text dominant-baseline="hanging" x="30" y="80">Hanging</text>
</svg>

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.

Fixed stroke width in 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.

Resources