Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep SVG element on exact pixel

Tags:

html

css

svg

I have an SVG element which draws items to exact pixels. When the element is itself placed on an exact pixel in the DOM it renders with no aliasing in all major browsers. However when there is any sub-pixel offset in the element's ancestory then it becomes aliased.

I can prevent aliasing in most browsers by setting shape-rendering to crisp-edges or optimize-speed, however that can misalign the content of the SVG by 1 pixel if it rounds in the wrong direction, so a pixel row may be missed at the top or side of the image.

I can fix the problem using el.getBoundingClientRect() and adjusting the margins to eliminate the sub-pixel offset. This works once, but if the element is moved in the dom - via scrolling, dragging etc, etc, then the method has to be reapplied. Which means listening to the DOM, which does not seem efficient.

What I would like to find is a CSS rule, or an element that I can wrap the SVG in to force it to be positioned at an exact pixel. I was wondering if there is some native element - e.g. a checkbox that browsers might force to render on an exact pixel for their own rendering purposes. Then I could align the SVG relative to that...

http://codepen.io/anon/pen/HvCcK (The problem is best demonstrated in Firefox)

HTML:

Crisp on FF and Chrome/Safari:<br/>
<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block;"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>

<br/>


<div class='offpixel'>
Blurry on Firefox (not on exact pixel):<br/>

<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block;"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>

<br/>
Crisp on Firefox - but not properly drawn: (not on exact pixel, but uses shape-rendering):<br/>       
<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block; shape-rendering: crispedges"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>

</div>

CSS:

body{
    font-family:sans-serif;
    font-size:10px;
}
.offpixel {
    padding:3.5px;
}
like image 216
Piwakawaka Avatar asked Oct 24 '14 19:10

Piwakawaka


2 Answers

I searched a solution for this as well. It seems that no one has an answer for it. Neither on stackoverflow nor on the rest of the internet. Yet the solution turned out to be so simple...

As you already guessed Firefox doesn't align SVGs to the pixel raster. However it aligns elements transformed via the CSS transform rule. So just transform the svg by 0 pixels and it gets aligned to the pixel raster. I attached your code with an additional example that exposes this technique.

body{
    font-family:sans-serif;
    font-size:10px;
}
.offpixel {
    padding:3.5px;
}
.crisp {
    transform: translate(0, 0);
}
<div class="crisp">
<!-- ^^ Reset. Just in case this snippet adds some unwanted subpixel offset -->
Crisp on FF and Chrome/Safari:<br/>
<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block;"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>

<br/>


<div class='offpixel'>
Blurry on Firefox (not on exact pixel):<br/>

<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block;"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>

<br/>
Crisp on Firefox - but not properly drawn: (not on exact pixel, but uses shape-rendering):<br/>       
<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block; shape-rendering: crispedges"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>
<br/>
Crisp on Firefox and correctly drawn (uses transform snapping):<br/>

<div id="s2" style="width:21px; height:7px; background-color:#FFFFFF;">
    <svg class="crisp" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" overflow="hidden" viewBox="0 0 50 70" preserveAspectRatio="xMinYMin" style="display: block;"><defs></defs><g style="display: block;">
    <rect x="0" y="0" width="100%" height="100%" fill="red" stroke="none"></rect>
    <path d="M10 10 L 40 10  40 20  20 20  20 30  40 30  40 60  10 60  10 50  30 50  30 40  10 40 Z" fill="white" stroke="none"></path>
    </g></svg>
</div>

</div>
</div>
like image 183
Scindix Avatar answered Nov 14 '22 23:11

Scindix


You have a design problem and not a markup/styling problem.

The responsibility of rendering your SVG code pixel perfect should be a shared responsibility of the SVG code, the page code, and the browser behavior.

So I'd say, keep the SVG code as is, since it is pixel perfect.

The page and markup design should be pixel perfect, so no 3.5px values on the higher level design.

Then you still have the scrolling/zoom issue, and I think the getBoundingClientRect approach is the correct one. But maybe attach it to a timer event if you're afraid of performance.

like image 36
Pieter21 Avatar answered Nov 15 '22 00:11

Pieter21