Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent half-pixel SVG shift on high pixel ratio devices (Retina)?

Tags:

html

css

svg

retina

I have a HTML webpage with SVG image. I get a problem (excess white line, shown on the picture below) on the webpage when I visit it using iOS Safari or Android Browser. The screenshot resolution is 2x, the saw edge is a SVG image.

Expectation vs. result

I've found out that it happens when the page Y-position of the SVG image is not an integer amount of CSS pixels (px), i.e. with ½px. The browser rounds the SVG image position to integer px when it renders the webpage while doesn't round the other elements positions. That's why the ½px line appears.

Explanation

You can reproduce the problem using the snippet below (or this CodePen). You should run the snippet on a device with a high pixel density. You can also reproduce it in desktop Safari if you go to the responsive design mode and pick iPhone or iPad.

.common-bg {
  background: #222;
  fill: #222;
}
.block {
  max-width: 300px;
  margin: 20px auto;
}
.block_content {
  height: 50.5px;
}
.block_edge {
  display: block;
}
<div class="block">
  <div class="block_content common-bg"></div>
  <svg
    class="block_edge"
    width="100%"
    height="10"
    xmlns="http://www.w3.org/2000/svg"
    version="1.1"
    xmlns:xlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <pattern id="sawPattern" x="50%" width="20" height="10" patternUnits="userSpaceOnUse">
        <path d="M 0 0 L 10 10 L 20 0 Z" class="common-bg"/>
      </pattern>
    </defs>
    <rect x="0" y="0" width="100%" height="10" fill="url(#sawPattern)"/>
  </svg>
</div>

How to prevent ½px SVG shift on iOS Safari and Android Browser? Is it a bug and I should report it to WebKit developers? Maybe there is a way to make browsers round to px the other elements on the page?

I can solve this problem without preventing ½px shift:

  • Remove non-integer height of .block_content
  • Make such layout in which half-pixel shift doesn't lead to while line

But I wonder is there a way to prevent ½px shift because the solutions above are not always possible.

like image 202
Finesse Avatar asked Jan 29 '26 05:01

Finesse


1 Answers

iOS: You just need to add any CSS transform to the SVG element to fix it in Safari. For example .block_edge {-webkit-transform: scale(1); transform: scale(1)}.

Android: First you need to add a tiny CSS scale transform to the SVG element. When you do it, the <svg> and the <rect> elements will be rendered where they must be but the <rect> background will be repeated at the top and at the bottom:

enter image description here

To fix it you need to extend the pattern to the top and the bottom to prevent background repeating. Then you need to add a filled <rect> just above the top of the SVG to remove the last blank line at the top. There still will left a hardly visible dark grey line at the top in Android browser.

.common-bg {
  background: #222;
  fill: #222;
}
.block {
  max-width: 300px;
  margin: 20px auto;
}
.block_content {
  height: 50.5px;
}
.block_edge {
  display: block;
  
  /* Fix. No more than 5 zeros. */
  -webkit-transform: scale(1.000001);
  transform: scale(1.000001);
}
<div class="block">
  <div class="block_content common-bg"></div>
  <svg
    class="block_edge"
    width="100%"
    height="10"
    xmlns="http://www.w3.org/2000/svg"
    version="1.1"
    xmlns:xlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <pattern id="sawPattern" x="50%" y="-1" width="20" height="12" patternUnits="userSpaceOnUse">
        <path d="M 0 0 L 0 1 L 10 11 L 20 1 L 20 0 Z" class="common-bg"/>
      </pattern>
    </defs>
    <rect x="0" y="-1" width="100%" height="1" common-bg="common-bg"/>
    <rect x="0" y="0" width="100%" height="10" fill="url(#sawPattern)"/>
  </svg>
</div>

The snippet on CodePen

I tested it on mobile and desktop Safari 10, Android 4.4 and Chrome 58 on Android.

Conclusion: the fixes are too complicated and not reliable so I advice to make such layout in which half-pixel shift doesn't lead to a blank line.

.common-bg {
  background: #222;
  fill: #222;
}
.block {
  max-width: 300px;
  margin: 20px auto;
}
.block_content {
  height: 50.5px;
}
.block_edge {
  display: block;
  
  /* Overflow for unexpected translateY */
  margin-top: -1px;
}
<div class="block">
  <div class="block_content common-bg"></div>
  <svg
    class="block_edge"
    width="100%"
    height="12"
    xmlns="http://www.w3.org/2000/svg"
    version="1.1"
    xmlns:xlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <!-- The teeth pattern is extended to the top -->
      <pattern id="sawPattern" x="50%" width="20" height="12" patternUnits="userSpaceOnUse">
        <path d="M 0 0 L 0 1 L 10 11 L 20 1 L 20 0 Z" class="common-bg"/>
      </pattern>
    </defs>
    <rect x="0" y="0" width="100%" height="11" fill="url(#sawPattern)"/>
  </svg>
</div>

The snippet on CodePen

like image 77
Finesse Avatar answered Jan 30 '26 21:01

Finesse



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!