Search code examples
htmlcsscss-shapeslinear-gradientscss-gradients

CSS Gradient Text with Opacity and Gradient Text Stroke / Outline


Although I've found similar questions, none explicitly match my goal.

I'm trying to display text with a gradient colour, with low opacity (so you can see the background through it), but also with a (matching) solid gradient border (i.e. text-stroke). I've discovered that answers to similar questions develop a workaround by creating a ::before underlay state that replicates a (gradient) border effect, but changing the opacity on the text's natural state just displays the colour of the ::before underlay text instead of the background colour (the desired result).

What I'm wondering is, is there any workaround to effectively create a solid gradient text-stroke with (internal) gradient text that has an opacity lower than 1?

Using the aforementioned other similar questions, I've developed the following code over a period of hours but can't quite figure out how it can be done. You'll notice in my code that I've used different rgba values - this was simply done to view the result more easily and see if I acheived my intended goal of making the (internal) text have opacity (to enable the background to be seen), as opposed to using identical colours. The more opacity I apply, the more you can see the gradient colours of the workaround/makeshift text-stroke.

body {
  background: #000000;
}

h1 {
  font-size: 60px;
  font-weight: 800;
  font-family: arial;
  color: rgb(255, 255, 255);
  background-image: linear-gradient(to left,
      rgba(255, 0, 0, 0.7),
      rgba(0, 0, 255, 0.7),
      rgba(255, 0, 0, 0.7));
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  margin: 10px;
}

h1::before {
  content: attr(data-text);
  background: -webkit-linear-gradient(-180deg, #3399ff, #82ed89, #3399ff);
  -webkit-background-clip: text;
  -webkit-text-stroke: 5px transparent;
  position: absolute;
  z-index: -1;
}
<h1 data-text="Text">Text</h1>


Solution

  • Actually the only way i can think about using CSS and without SVG is to rely on element() combined with mask but still the support is too low:

    Here is an example that will only work in Firefox. The trick is to create a text with only stroke and transparent color as a reference that will be used inside a mask of the same text where we have the same stroke to keep only the stroke visible and get the needed effect. Yes, it's a bit crazy as idea but it works.

    body {
      background: #000000;
    }
    h1 {
      font-size: 80px;
      font-weight: 800;
      font-family: arial;
      color:#fff;
    }
    #ref {
      -webkit-text-stroke: 5px red;
      color:transparent;
      display:inline-block;
    }
    h1.grad {
      background-image: linear-gradient(to left,
          rgba(255, 0, 0, 0.3),
          rgba(0, 0, 255, 0.4),
          rgba(255, 0, 0, 0.5));
      -webkit-text-fill-color: transparent;
      -webkit-background-clip: text;
      margin: 10px;
    }
    h1.grad::after {
      content: attr(data-text);
    }
    
    h1.grad::before {
      content: attr(data-text);
      background: linear-gradient(-180deg, #3399ff, #82ed89, #3399ff);
      -webkit-background-clip: text;
      -webkit-text-stroke: 5px transparent;
      position: absolute;
      z-index: -1;
      -webkit-mask:element(#ref);
              mask:-moz-element(#ref);
              mask:element(#ref);
    }
    
    body {
      background:url(https://picsum.photos/id/107/1000/1000) center/cover;
    }
    <h1 data-text="Some Text" class="grad"></h1>
    
    
    <div style="height:0;overflow:hidden;">
    <h1 id="ref">Some Text</h1>
    </div>

    Below how it should look:

    enter image description here

    Another idea is to consider background-attachment:fixed and use the same background twice. This should work everywhere but you background will be fixed:

    body {
      background: #000000;
    }
    h1 {
      font-size: 80px;
      font-weight: 800;
      font-family: arial;
      color:#fff;
    }
    
    h1.grad {
      background: linear-gradient(to left,
          rgba(255, 0, 0, 0.3),
          rgba(0, 0, 255, 0.4),
          rgba(255, 0, 0, 0.5)),
          url(https://picsum.photos/id/110/1000/1200) center/cover fixed;
      -webkit-text-fill-color: transparent;
      -webkit-background-clip: text;
              background-clip: text;
      margin: 10px;
    }
    h1.grad::after {
      content: attr(data-text);
    }
    
    h1.grad::before {
      content: attr(data-text);
      background: linear-gradient(-180deg, #3399ff, #82ed89, #3399ff);
      -webkit-background-clip: text;
      -webkit-text-stroke: 5px transparent;
      position: absolute;
      z-index: -1;
    }
    
    body {
      background:url(https://picsum.photos/id/110/1000/1200) center/cover fixed;
    }
    <h1 data-text="Some Text" class="grad"></h1>

    enter image description here