SVG background-image blurriness in Chrome with repeating background
Asked Answered
D

3

7

An SVG background is appearing Blurry in Chrome and other Blink/webkit based browsers:

Screenshot of a portion of the SVG in Chrome:

Chrome

Screenshot of a portion of the SVG in Firefox:

Firefox

This only happens when displaying the SVG with "background-repeat: true" and "background-size: contain" CSS values. We'd like to repeat the SVG background height as many times as is necessary, since the page height is dynamic and stretching the vector image would look poor.

The SVG is displayed inline in the CSS in the sample page below, but the issue happens in the same way when I link to the external SVG image.

Here's a test page which shows this behavior: https://august-13-2020-test-canvas.bubbleapps.io/version-test/pagemaker-test-original-2 (log in with "username" and "password", we're required to put this authentication here).

A test page with a properly displayed (crisp/smooth edged) SVG is here: https://august-13-2020-test-canvas.bubbleapps.io/version-test/pagemaker-test-online-1

A version with crisp/smooth edges is displayed in exactly the same way - the only difference is the content of the SVG.

Here's a link to the background SVG for the blurry page: https://dd7tel2830j4w.cloudfront.net/d44/f1588704991659x396570736162959360/background.svg

All the SVGs I tested from this page had crisp edges: https://www.svgbackgrounds.com/#flat-mountains (the bottom-right one is the one used in the above link)

In the process of trying to fix this issue or identify the cause of it within the SVG code, I've edited the SVG quite a bit, attempting the following:

  • Converting all SVG point values (path's d attributes) to integers
  • Adding "PreserveAspectRatio" to the SVG's opening tag
  • Removing the height and width from the opening declaration of the SVG
  • Removing the "linear gradients" from the SVG and replacing them with static colors
  • Doubling the size of the SVG shapes to see if the blurriness decreases with a larger initial width and height
  • Adding the "crispEdges" property to the SVG paths
  • Optimizing the SVG (using SVGomg)

Additionally, I've attempted to change the CSS a bit, but couldn't get it working.

Some ideas I have for workarounds are the following:

  • Using multiple background images to repeat the display of the SVG (display it 5-10 times in the background, mimicking the background-repeat: repeat-y effect but by including multiple background images)
  • Using JS to draw the SVG inline on the page (where this bug doesn't happen), creating an HTML that is exactly the same height as our group's dynamic height (which can change at any time without triggering a JS event) and repeating the SVG somehow within that group.
  • Asking a designer for a different SVG background, and hoping that the blurriness issue will not repeat.

Any help here would be much appreciated, I'm pulling my hair out on this and wasn't able to fix the issue after consulting several developers on CodeMentor.io.

Dextral answered 24/8, 2020 at 15:23 Comment(0)
T
2

Blink seems to apply a faulty algorithm, first rendering the SVG at a wrong size and then scaling the resulting raster image. This is tricky to work around, as two scaling mechanisms are involved, SVG viewBox and CSS background-size.

I can only offer a partial solution, working in Chrome and Edge (both engines), but failing in Firefox.

It moves the responsibility for scaling and repetition from the CSS background properties to the SVG itself with a <pattern> element. The complete content of the SVG image is moved inside this element, including setting the viewBox attribute to the same values.

Then, the width and height values for pattern repetition are set to width="100vw" height="504vw". This fills the viewport width and keeps the aspect ratio for the height. (Firefox seems not to be able to interpret vw units in this context).

Finally, a <rect> with width = height = 100% is defined and filled with the pattern, and the root <svg> element, without a viewBox attribute, is similarly set to width = height = 100%.

<svg width="100%" height="100%" fill="none" xmlns="http://www.w3.org/2000/svg">
  <pattern id="pt" width="100vw" height="504vw" x="0" y="0" viewBox="0 0 1440 7258" patternUnits="userSpaceOnUse" preserveAspectRatio="xMinYMin meet">
    <rect width="1440" height="7258" fill="white"/>
    <path d="M-137 1749L1546 1994.95V2870L-137 2624.64V1749Z" fill="#F7F8FF"/>
    <path d="M-137 3776L1546 4021.95V4897L-137 4651.64V3776Z" fill="url(#paint0_linear)"/>
    <path d="M-137 3969L1546 4214.95V4900L-137 4654.64V3969Z" fill="url(#paint1_linear)"/>
    <path d="M1319 2764L1545 2796.96V2870L1319 2837.12V2764Z" fill="#264CE9"/>
    <path d="M-53 2645L96 2666.68V2744L-53 2722.37V2645Z" fill="#FBE89F"/>
    <path d="M-29 2571L307 2620.13V2699L-29 2649.99V2571Z" fill="#54A5F2"/>
    <path d="M1209 2820L1545 2869.13V2948L1209 2898.99V2820Z" fill="#6AD2F6"/>
    <path d="M-147 4649L307 4715.42V4891L-147 4824.74V4649Z" fill="#6AD2F6"/>
    <path d="M-137 -233L1546 12.9461V688L-137 442.638V-233Z" fill="url(#paint2_linear)"/>
    <g style="mix-blend-mode:multiply">
      <path d="M987 -144L2119 21.4151V388L987 222.978V-144Z" fill="#6AD2F6"/>
    </g>
    <path d="M-137 27L1546 272.946V948L-137 702.638V27Z" fill="url(#paint3_linear)"/>
    <path d="M-258 455L490 564.509V794L-258 684.751V455Z" fill="url(#paint4_linear)"/>
  </pattern>

  <linearGradient id="paint0_linear" x1="43.9999" y1="3807" x2="458.5" y2="4665" gradientUnits="userSpaceOnUse">
    <stop stop-color="#264CE9" stop-opacity="0.06"/>
    <stop offset="1" stop-color="#F7F8FF" stop-opacity="0.26"/>
  </linearGradient>
  <linearGradient id="paint1_linear" x1="43.9999" y1="4000" x2="1229" y2="5099" gradientUnits="userSpaceOnUse">
    <stop stop-color="#264CE9" stop-opacity="0.06"/>
    <stop offset="1" stop-color="#F7F8FF" stop-opacity="0"/>
  </linearGradient>
  <linearGradient id="paint2_linear" x1="194" y1="87.4999" x2="1671" y2="300" gradientUnits="userSpaceOnUse">
    <stop offset="0.270833" stop-color="#264CE9"/>
    <stop offset="1" stop-color="#6AD2F6"/>
  </linearGradient>
  <linearGradient id="paint3_linear" x1="194" y1="347.5" x2="1671" y2="560" gradientUnits="userSpaceOnUse">
    <stop stop-color="#264CE9"/>
    <stop offset="1" stop-color="#6AD2F6"/>
  </linearGradient>
  <linearGradient id="paint4_linear" x1="-108" y1="550.5" x2="490" y2="638" gradientUnits="userSpaceOnUse">
    <stop stop-color="#264CE9" stop-opacity="0.43"/>
    <stop offset="1" stop-color="#264CE9"/>
  </linearGradient>

  <rect width="100%" height="100%" fill="url(#pt)" />
</svg>

I have simplified the markup a bit, but these changes are all cosmetic, including the removal of the clipPath - it is applied for pattern elements, anyway.

Tiros answered 24/8, 2020 at 19:2 Comment(1)
Thanks so much for the help here, ccprog. That sounds promising and the rendering issue (rendering the SVG at one size and rescaling it like a PNG/JPEG) is what I had imagined as well. On this page, that SVG was still blurry on Chrome (latest stable version for Windows, 84.0.4147.135). I tried setting background-repeat to "no-repeat" and background-size to a number of different values - in this page it's set to 100% 100%. august-13-2020-test-canvas.bubbleapps.io/version-test/…Dextral
D
1

Never found a solution to this issue (or at least failed to implement the solution presented by ccprog).

We chose to edit the SVG files to be longer. Our SVG files are being used as the background for web pages that have dynamic lengths, and theoretically have no limit in their height.

By repeating an SVG that is ~8000px tall 5 times, it covers up until a web page would reach 40000px height, which should be fine for our use case. I used inkscape to go in and copy the content of each SVG and then replicate that content several times, making the final SVGs 5x taller than the original ones.

Dextral answered 14/10, 2020 at 15:25 Comment(0)
H
1

The problem still persists in Chrome-based browsers.

I've found some kind of workaround, works well for Chrome/Safari/Firefox + based. This is tricky, dirty a bit, but it is the only thing worked for me. Would appreciate other solutions.

Disclaimer: In theory this is worse than separate SVG file in matter of performance, as SVG becomes a part of the DOM.

Idea is: insert SVG markup as a symbol right into the document, then use it within absolute/fixed-positioned element.

  1. Create your nice repeatable background in SVG with the following structure:
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
    <symbol id="mysym" viewBox="0 0 500 10000" atternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMin slice">
        <pattern id="mypat" width="500px" height="1243px" viewBox="0 0 500 1243" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMin slice">
            <!--Markup-->
        </pattern>
        <rect x="0" y="0" width="100%" height="100%" fill="url(#mypat)" />
    </symbol>
</svg>

(I set symbol's viewBox height so large cause of aspect ratio config of mine, try your own setup, doesn't matter for the case)

  1. Insert the whole SVG into the document. I suggest some hand-written tooling to insert its content during CSR/SSR.

  2. Create absolute positioned component near to the page's root

<style>
.mainslot{
    position: relative;
}
.mainslot__bg{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
</style>
<!---...--->
<main class="mainslot"> <!--the page root-->
    <div class="mainslot__bg"> <!--background-->
        <svg width="100%" height="100%">
            <use href="#mysym" />
        </svg>
    </div>
    <!--Other content-->
</main>

And again, I suggest some tooling to do this during CSR/SSR.

Should work, play around aspect ratio and sizes. You might have to play with z-index as well if you rely on it.

Heigl answered 14/3 at 4:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.