how to handle SVG pixel snapping - svg

I am trying to render two svg lines using path element.
The first line has 1px width and it is sharp
The second line has 2px width and it is blurred
The stroke-width is the same for both.
How to fix this
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<path style="stroke-width:1;stroke:red;opacity:1;" d="M 300.5 250 L 300.5 300 "></path>
<path style=" stroke-width:1;stroke:red;opacity:1;" d="M 350 250 L 350 300 "></path>
</svg>

Mainly it's the 0.5 offset that makes the line sharp. By default, integer coordinates map to the intersections of the pixel squares. So a width-1 horizontal/vertical line is centered on the boundary between pixel rows and extends half way into the pixels on either side.
So to fix the second line either add 0.5 to the co-ordinates or use the style shape-rendering: crispEdges. Note that crispEdges prevents antialiasing so horizonal/vertical lines are crisp but angled lines look blocky.
Also stroke-width=1 is not valid CSS syntax. The first example stroke-width: 1 has it right.

Just Try to move the SVG element.
svg {
padding: .5px;
}

Related

SVG circle where the stroke width is bigger than the diameter

I have two identical paths, but stroked differently: https://jsfiddle.net/vzbdcupf/
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" width="300" height="300">
<style>
.s0 { fill: none; stroke: red ; stroke-width: 80 }
.s1 { fill: none; stroke: black; stroke-width: 4 }
</style>
<path class="s0" d="m100 100c20 7 33-1 36-25 2-13-71 13-36 25z"/>
<path class="s1" d="m100 100c20 7 33-1 36-25 2-13-71 13-36 25z"/>
</svg>
When I stroke the path with red, the stroke-width is huge, and there should not be any "hole" inside it. Why is there a hole?
I think it is related to the rendering algorithm (stroking is converted into filling paths, and the inner path gets "reflected"). But how do you explain it in terms of the SVG specification, to be able to say the rendering is correct?
Not an explanation why this happens but a possible workaround:
Some observations on the occurrence of this rendering:
appears on paths containing curve commands (c, s, q etc.)
won't appear on primitives like <circle>, <polygon> or paths using only line commands like l, h, v
Applying a dashed stroke seems to fix this rendering issue:
pathLength="100"
stroke-dasharray="100 0"
stroke-linecap="round"
Example:
function applyDash() {
const svg = document.querySelector('svg');
const paths = svg.querySelectorAll('path');
paths.forEach(function(path) {
path.setAttribute('pathLength', 100);
path.setAttribute('stroke-dasharray', '100 0');
path.setAttribute('stroke-linecap', 'round');
})
}
<p><button type="button" onclick="applyDash()"> Apply dash fix</button></p>
<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="300px" viewBox="0 0 300 300">
<path fill="none" stroke="#FF1746" stroke-width="80" d="M58,100c10,3.5,18.3,3.3,24.4-0.9S92.5,87,94,75 c1-6.5-16.8-3.3-30.4,3.1S40.5,94,58,100z" />
<path fill="none" stroke="#FF1746" stroke-width="80" d="M211.6,78.1l-14.5,11.1L206,100l13.6,2.4l10.7-3.3
l7.5-9.1l4.1-15l-9.7-3.3L211.6,78.1L211.6,78.1z" />
<circle fill="none" stroke="#FF1746" stroke-width="80" cx="230" cy="232.5" r="9.8" />
<path fill="none" stroke="#FF1746" stroke-width="80" d="M77.1,232.5c0,5.4-4.4,9.8-9.8,9.8
s-9.8-4.4-9.8-9.8s4.4-9.8,9.8-9.8S77.1,227.1,77.1,232.5z" />
</svg>
Your guess as to what is happening is basically correct. When 2D graphics engines "stroke" a path, what they actually do is effectively convert the stroke into a filled path of its own. When the path that is being stroked has tight corners, larger stroke widths can cause the inside borders of the stroke path to overlap each other. The result is the same as if any path intersects with itself - it can cause holes. With actual fills, you can control how those holes are rendered with the fill-rule property. Unfortunately, there is no such property for strokes.
The SVG 2 specification briefly mentions this phenomenon here, but ultimately leaves it up to implementers how they want to deal this situation.

How avoid SVG from scaling as its container shrinks?

I want an SVG to not scale as its container shrinks. On scroll, my container shrinks its height (its width stays the same). That container contains an SVG that I want to not become scaled, but instead have its lower part become invisible/cut off.
I can do it if I (by CSS) use my SVG as a background, but I'd prefer to have the SVG inline in the HTML.
I have tried with various values for the SVG attribute preserveAspectRatio. I thought that the value xMidYMin slice would slice off the bottom part of my SVG (like I want), but it squashes its height instead.
My container is 245x80 px and on scroll is shrinked to 245x40 px.
My svg element has attribute viewBox set to 0 0 245 80 , and has no width or height explicitly defined.
You can use preserveAspectRatio="xMidYMin slice" for the svg element.
Please observe that the svg element has a viewBox and also a width and a height. While the aspect ratio of the viewBox is 1:1 the aspect ratio from width and height is 2:1
xMidYMin - Force uniform scaling.
Align the midpoint X value of the element's viewBox with the midpoint X value of the viewport.
Align the of the element's viewBox with the smallest Y value of the viewport.
slice - Scale the graphic such that:
the aspect ratio is preserved and
the entire viewport is covered by the viewBox
Please read more about preserveAspectRatio
In the next demo use the slider to change the height of the svg element
itr.addEventListener("input",()=>{svg.setAttribute("height",itr.value)})
svg{border:solid}
<p><input id="itr" type="range" min="10" max="200" value="100"/></p>
<svg preserveAspectRatio="xMidYMin slice" viewBox="0 0 100 100" width="200" height="100" id="svg">
<defs>
<path style="fill:gold; stroke:black; stroke-width: 8px;
stroke-linecap: round; stroke-linejoin: round;" id="smiley" d="M50,10 A40,40,1,1,1,50,90 A40,40,1,1,1,50,10 M30,40 Q36,35,42,40 M58,40 Q64,35,70,40 M30,60 Q50,75,70,60 Q50,75,30,60" />
</defs>
<use href="#smiley" />
</svg>

SVG viewBox, rectangle, and polyline

The SVG attribute viewBox appears to be inconsistent. It seems it doesn't scale all SVG graphics primitives the same way. Here's a sample SVG file that has a rectangle, a circle, a polyline, and a polygon. The rectangle has been properly scaled and almost filled the viewPort (which has a width of 500 and a height of 500).
Please see the SVG code and image it produced below. As you will notice the polyline, polygon, and circle did not scale to fill the view port. They do (consistently) occupy the top-left quarter of the view port though (moved but retaining the original size). Can anyone please throw some light on what's going on with this? I will greatly appreciate your feedback.
<?xml version='1.0' encoding='utf-8'?>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg'
xmlns:xlink='http://www.w3.org/1999/xlink'
height='499' width='501' viewBox='100 100 200 200'>
<g stroke='BLACK' stroke-width='5' fill='none'>
<rect x='105' y='105' width='193' height='193'/>
<polygon points="150,100 200,200 100,200" style="stroke:purple" />
<polyline points='115,180 155,127 180,180' stroke='red'/>
<circle cx='150' cy='150' r='50' stroke='green'/>
</g>
</svg>
Short answer:
The SVG attribute viewBox on the sample code does scale all SVG graphics the same way; so the smaller object representations are actually smaller objects.
Explanation:
It's useful to look at he viewBox documentation to better understand the calculations. Let's try to go through you sample code step-by-step:
the SVG viewport dimensions are set to 501 by 499 (width by height)
the viewBox attributes are set as
100 for min-x and min-y, which will act like shifting the position of the viewport before its container top and left positions (which in the image it seems like irrelevant, since you also shifted all the coordinates by 100; see my note below)
200 for width and height, which will represent 100% of the viewport size (in this case ~500px); in other words, the 200 value in any children will be mapped (scaled) into ~500px
the rect has 193 as width and height, which is nearly 200, this makes it occupy almost all of the ~500px by 500px viewport area
the other items are scaled properly, but they seem smaller because, in fact, they are smaller
e.g. the circle has r='50' which would fit an imaginary outer square of 100 by 100; 100 is 50% of 200, so it is scaled to ~250px by ~250px (250 = 50% of 500); that is why the circle seems to use 1/4 of the area
the same idea is applied to the other graphic elements.
NOTE:
I found it was easier to understand the final results if there was no shift on the viewport and on the positioning coordinates. So, removing 100 from viewBox > min-x and min-y (step 2.1 above) and from all the positioning attributes would make this code easier to understand:
<?xml version='1.0' encoding='utf-8'?>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg'
xmlns:xlink='http://www.w3.org/1999/xlink'
height='499' width='501' viewBox='0 0 200 200'>
<g stroke='BLACK' stroke-width='5' fill='none'>
<rect x='5' y='5' width='193' height='193'/>
<polygon points="50,0 100,100 0,100" style="stroke:purple" />
<polyline points='15,80 55,27 80,80' stroke='red'/>
<circle cx='50' cy='50' r='50' stroke='green'/>
</g>
</svg>

SVG - Partially stroking a triangle

I have a simple triangle SVG and I'm stuck trying to figure out how I can partially apply borders like I would in CSS? How would I go about just applying a stroke to just the left and the right side of the triangle but not the top?
https://jsfiddle.net/rf8a9xzy/1/
<span class="svg-triangle">
<svg width="100%" viewBox="0 0 20 10">
<polygon points="0,10 20,10 10,0" />
</svg>
</span>
You can use stroke-dasharray to set the parts of the stroke that you want to draw.
Dasharrays are made up of one or more pairs of numbers describing the length to draw, followed by the length to skip. It always starts with the drawn length. So a dasharray of "10 5" means draw a stroke section of length 10, followed by a gap of length 5. It then repeats. Draw another 10 and a gap of 5 etc.
Your triangle starts with a horizontal line of length 20, followed by two 45deg lines (of 10,10). The length of those other two sides are 14.142 - derived using Pythagoras' Theorem: sqrt(10^2 + 10^2).
So the dash array to draw the two sides would need to be:
0 20 28.284
Thats:
draw a stroke of 0,
a gap of 20 (the horizontal part of the triangle)
draw the two other sides (14.142 * 2)
.svg-triangle {
display: block;
width: 100px;
transform: rotate(180deg);
}
.svg-triangle svg {
fill: #FFF;
stroke: #000;
stroke-width: 1px;
stroke-opacity: 0.2;
}
.svg-triangle svg polygon{
stroke-dasharray: 0 20 28.284;
}
<span class="svg-triangle">
<svg width="100%" viewBox="0 0 20 10">
<polygon points="0,10 20,10 10,0" />
</svg>
</span>

How does setting viewBox attribute, affects the user coordinates

I have the following code: http://jsfiddle.net/fCWJ5/1/, and following doubts regarding the viewbox.
body{margin:0;}
#test{width:200px;height:200px;border:solid red 1px;}
<body>
<div id="test">
<!-- preserveAspectRatio="xMinYMin meet" -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 150"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full">
<g>
<rect class="drag resize" x="150" y="50" width="50" height="50" fill="#c66" />
</g>
</svg>
</div>
</body>
From the fiddle one user coordinate = .2, this I got by dividing
200/1000 (test div width / viewBox width attribute). According to
this, the rectangle should be at (30px, 10px), with a width and
height of 10px, 10px respectively. But the rectangle is at
(30px,97px), with a width and height of 10px,10px (some how height
and width is correct as per calculation.). Please point out why the
y coordinate is wrong.
Then I gave preserveAspectRatio="xMinYMin meet" as said in a svg
tutorial pdf. It was working fine for this value. But for other
value the display goes for toss. Please explain what is this. I
already asked a question regarding this
could not able to put viewbox,viewport,userspace together and get the picture.
I'm unable to understand the answer and the concept.
what will be the value of the ratio, if I didn't specified any width
and height for the svg dom element container.
I'm seeing that the ratio 1.3, (height of the test div/height
attribute of the viewBox), is not used. Should that be used for
calculating things like height,y coordinates.
The problem is the aspect ratio of your DIV does not match the aspect ratio of your viewBox. So HTML puts your SVG in the center of the DIV with the empty space above and below. Add the following to your SVG code to illustrate:
<rect x="0" y="0" width="100%" height="100%" fill="none" stroke="black"/>
This will show you the boundaries of your SVG element, while the red border you put on your DIV will show you its boundary. They don't match.
If you don't put a width or height on the SVG element then it will fill its container. In your example you set the DIV to 200px X 200px, the viewBox will then be applied effectively dividing the 200px by 1000 user units for X and 30px by 150 for the Y (because of the aspect ratio of the SVG only 15% of the DIV height is used by the SVG, 15% of 200px is 30px). Remove the width and height from the DIV and it will use the full width of the screen.
If you add my rect element you will see that your box is 1/3 (50/150 = 1/3) from the top extending 1/3 down, while also being 3/20 (150/1000 = 3/20) in from the left and extending 1/20 (50/1000 = 1/20) across.

Resources