Search code examples
csssvg

Can we have an SVG background that tiles, with a gradient fill that doesn't?


Is is possible to have a tiling SVG background with a full-screen gradient?

Let's say this is a single tile of our SVG:

a single circle with a solid grey fill

Here is the SVG markup for the circle above (OK, not the cleanest way to code a circle, but content can be anything):

<?xml version="1.0" encoding="UTF-8"?>
<svg id="circleLayer" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 128 128" preserveAspectRatio="xMidYMid meet">
  <defs>
    <style>
      .st0 {
        fill: #6887b5;
      }

      .st1 {
        fill: #b2b2b2;
      }
    </style>
  </defs>
  <g id="circle">
    <path class="st1" d="M64,108c-24.262,0-44-19.738-44-44s19.738-44,44-44,44,19.738,44,44-19.738,44-44,44Z"/>
    <path class="st0" d="M64,24c22.056,0,40,17.944,40,40s-17.944,40-40,40-40-17.944-40-40,17.944-40,40-40M64,16c-26.51,0-48,21.49-48,48s21.49,48,48,48,48-21.49,48-48-21.49-48-48-48h0Z"/>
  </g>
</svg>

And here is an SVG of a radial gradient:

<?xml version="1.0" encoding="UTF-8"?>
<svg id="gradLayer" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">
  <defs>
    <style>
      .st0 {
        fill: url(#radial-gradient);
      }
    </style>
    <radialGradient id="radial-gradient" cx="262.914" cy="248.437" fx="262.914" fy="248.437" r="347.618" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff"/>
      <stop offset="1" stop-color="#000"/>
    </radialGradient>
  </defs>
  <g id="gradRect">
    <rect class="st0" x="0" y="0" width="512" height="512"/>
  </g>
</svg>

But we want to add a gradient fill that spans all of the tiles in the background:

an array of four-by-four circles sharing a single radial gradation, from white in the center, to black at the farthest circles

You might say "just make the inside of the circles transparent, and add the full-screen gradient as another background, layered behind the circle background". And that would be fine in some cases. But if you notice, the circle background already has transparency - around the circles.

I suspect the answer will be: it can be done with inline CSS, but not external CSS. But please ...fill me in.


Update

I tried applying chrwahl's answer, but it looks like this method won't be a viable solution, at least not in its present state. I will include the code I used.

I can't add this as a Snippet, because there is no field for SVG markup. But here is the HTML:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>SVG - Tile + Full Screen Gradient Test</title>
  <style type="text/css">
    body.svg-bg {
      background-image: url(tile-and-full-02.svg);
      background-position: center;
      background-repeat: repeat;
    }
  </style>
</head>
<body class="svg-bg">
</body>
</html>

And here is the SVG:

<svg id="tile-and-full" xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" preserveAspectRatio="xMidYMid meet">
  <defs>
    <circle id="c1" r="10"/>
    <pattern id="p1" viewBox="0 0 30 30" width="20%" height="20%">
      <use href="#c1" fill="white" transform="translate(15 15)"/>
    </pattern>
    <pattern id="p2" viewBox="0 0 30 30" width="20%" height="20%">
      <use href="#c1" fill="none" stroke="#6786b4" stroke-width="2" transform="translate(15 15)"/>
    </pattern>
    <mask id="m1">
      <rect width="100" height="100" fill="url(#p1)"/>
    </mask>
    <radialGradient id="rg1" cx="50" cy="50" r="70" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff"/>
      <stop offset="1" stop-color="#000"/>
    </radialGradient>
  </defs>
  <rect width="100" height="100" fill="url(#rg1)" mask="url(#m1)"/>
  <rect width="100" height="100" fill="url(#p2)"/>
</svg>

Here's a screenshot of the result, to save everyone time:

a screenshot showing how when the pattern is tiled, it ends up in clusters, or blocks, of tiled groups of tiles

They render as tile clusters, in other words tiles of grouped tiles.

Is tiling and clipping/masking even possible? It seems all clipping calculations are done before the tiling calculation. This makes me think multiple layered divs may be required. Maybe clipping with CSS is a route to explore? (Here too, not sure if tiling and clipping is possible.) I also experimented with feTile, but no luck so far.


Solution

  • I got it working.

    In this example the SVG is the background image for a div that spans the width and height of the page. The page's background image (thanks Lorem Picsum) can be seen through the transparent regions of the SVG.

    The tiling pattern of the SVG presently originates in the upper left corner, not the center. I might try to get that working another day. Someone has already posted a question on SO about it, and it has an accepted answer.

    The spherical gradient squashes and stretches with the div and page dimensions. I'm still tweaking the properties of the gradient for my own purposes, to try to improve the look. Also I will make a custom pattern (not simply circles). But as a demo of the concept, I think this is fine.

    HTML

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>SVG - Tile + Full Screen Gradient</title>
      <style type="text/css">
        body {
          margin: 0;
          background-color: #79827C;
          background-image: url("https://picsum.photos/id/114/640/360.webp");
          background-size: cover;
          background-repeat: no-repeat;
        }
        .div-dimensions {
          width:  calc(100vw - 2px);
          height: calc(100vh - 2px);
        }
        .svg-bg {
          background-image: url(tile-plus-gradient.svg);
          background-size: 100%;
        }
      </style>
    </head>
    <body>
      <div class="div-dimensions svg-bg"></div>
    </body>
    </html>
    

    SVG

    <?xml version="1.0" encoding="UTF-8"?>
    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="-50% -50% 100% 100%" preserveAspectRatio="xMidYMid meet">
        <defs>
            <!-- radial gradient -->
            <radialGradient id="bgGradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
                <stop offset="0%" stop-color="white"/>
                <stop offset="100%" stop-color="black"/>
            </radialGradient>
    
            <!-- circle definition -->
            <circle id="patternCircle" cx="64" cy="64" r="48"/>
    
            <!-- masking pattern -->
            <pattern id="maskPatternTile" x="-64" y="-64" width="128" height="128" patternUnits="userSpaceOnUse">
                <rect width="128" height="128" fill="black"/><!-- swap to invert mask -->
                <use href="#patternCircle" fill="white"/><!-- swap to invert mask -->
            </pattern>
    
            <mask id="maskLayer">
                <rect width="100%" height="100%" fill="url(#maskPatternTile)"/>
            </mask>
    
            <!-- stroke pattern -->
            <pattern id="strokePattern" x="-64" y="-64" width="128" height="128" patternUnits="userSpaceOnUse">
                <use href="#patternCircle" fill="none" stroke="#6887b5" stroke-width="4"/>
            </pattern>
        </defs>
    
        <!-- gradient layer -->
        <rect width="100%" height="100%" fill="url(#bgGradient)" mask="url(#maskLayer)"/>
        
        <!-- stroke layer -->
        <rect width="100%" height="100%" fill="url(#strokePattern)"/>
    </svg>
    

    Screenshot of the result in the browser:

    Screenshot of the result in the browser