91

I am looking to create an effect like this, but my website has a dynamic background-color. Note that this example uses a white overlay, which does not work with different backgrounds.

p {
    width: 300px;
    overflow: hidden;
    height: 50px;
    line-height: 50px;
    position: relative;
}
p:after {
    content: "";
    width: 100px;
    height: 50px;
    position: absolute;
    top: 0;
    right: 0;
    background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,1));
}

What I was hoping to do was to set up a CSS opacity gradient. This sort of works, but the code is too messy. Looking at this second example, I could implement it in jQuery, but is there any way to do this entirely in CSS?

TylerH
  • 20,799
  • 66
  • 75
  • 101
pgee70
  • 3,707
  • 4
  • 35
  • 41
  • My use case doesn't have a dynamic background so thank you thank you thank you for the "like this" link. That was exactly what I needed! (Except vertical :) ) – John Carrell Oct 27 '18 at 20:05

4 Answers4

135

You can do it in CSS, but there isn't much support in browsers other than modern versions of Chrome, Safari and Opera at the moment. Firefox currently only supports SVG masks. See the Caniuse results for more information.

EDIT: all browsers except IE now support all mask- properties mentioned here.

CSS:

p {
    color: red;
    -webkit-mask-image: -webkit-gradient(linear, left top, left bottom, 
    from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}

The trick is to specify a mask that is itself a gradient that ends as invisible (thru alpha value)

See a demo with a solid background, but you can change this to whatever you want.

DEMO

Notice also that all the usual image properties are available for mask-image

p  {
  color: red;
  font-size: 30px;
  -webkit-mask-image: linear-gradient(to left, rgba(0,0,0,1), rgba(0,0,0,0)), linear-gradient(to right, rgba(0,0,0,1), rgba(0,0,0,0));
  -webkit-mask-size: 100% 50%;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: left top, left bottom;
  }

div {
    background-color: lightblue;
}
<div><p>text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text </p></div>

Now, another approach is available, that is supported by Chrome, Firefox, Safari and Opera.

The idea is to use

mix-blend-mode: hard-light;

that gives transparency if the color is gray. Then, a grey overlay on the element creates the transparency

div {
  background-color: lightblue;
}

p {
  color: red;
  overflow: hidden;
  position: relative;
  width: 200px;
  mix-blend-mode: hard-light;
}

p::after {
  position: absolute;
  content: "";
  left: 0px;
  top: 0px;
  height: 100%;
  width: 100%;
  background: linear-gradient(transparent, gray);
  pointer-events: none;
}
<div><p>text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text </p></div>
gilad905
  • 2,842
  • 2
  • 16
  • 23
vals
  • 61,425
  • 11
  • 89
  • 138
  • 6
    Is there an equivalent for -webkit-mask-image for firefox? – suga_shane Jan 24 '14 at 23:30
  • @suga_shane Added equivalency for most modern browsers. – vals Mar 21 '15 at 07:55
  • I've had a problem with the second method in Firefox - it showed me some glitches. In the end I just used :after with linear gradient (from transparent to white) as a background. – Almir Sarajčić Sep 28 '15 at 13:48
  • The original answer is from 2013. Is there any update regarding browser support? – donquixote Dec 09 '16 at 13:57
  • @donquixote This is very sad ... We are exactly at the same point. You can check it clicking on the link to can i use in the answer. Also, once there, you can follow the links to the Microsoft page, and vote to ask for the implementation in Edge. (it is **under consideration**) – vals Dec 09 '16 at 17:01
  • @donquixote Well, not really. We have Opera working now ! (of course, since they have changed the render engine, but well.. ) – vals Dec 09 '16 at 17:04
  • could box-shadow replace opacity gradient? – donquixote Dec 09 '16 at 17:04
  • If you know what is the background color, yes – vals Dec 09 '16 at 17:05
  • Is there a way to limit mask size with the first method? E.g. I want to add opacity gradient only for 50% of height of the container. – quotesBro Oct 25 '17 at 08:46
  • 1
    @MikhailKhazov The easiest solution to what you wnat would be to set a initial step to the gradient. - that is keeping it opaque till a non 0 percentage. A more difficult approach, but with more different posibilities, is setting more than 1 mask, I have added a snippet showing this posibility – vals Oct 25 '17 at 17:37
  • As of Sept 2020, the -webkit-mask-image works for me in Chrome, Mac, and Safari. – colin moock Sep 05 '20 at 22:50
6

Except using css mask answered by @vals, you can also use transparency gradient background and set background-clip to text.

Create proper gradient:

background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);

Then clip the backgroud with text:

background-clip: text;
color: transparent;

Demo

https://jsfiddle.net/simonmysun/2h61Ljbn/4/

Tested under Chrome 75 under Windows 10.

Supported platforms:

simonmysun
  • 458
  • 4
  • 15
5

I think the "messy" second method, which is linked from another question here may be the only pure CSS solution.

If you're thinking about using JavaScript, then this was my solution to the problem:

demo: using a canvas element to fade text against an animated background

The idea is that your element with the text and the canvas element are one on top of the other. You keep the text in your element (in order to allow text selection, which isn't possible with canvas text), but make it completely transparent (with rgba(0,0,0,0), in order to have the text visible in IE8 and older - that's because you have no RGBa support and no canvas support in IE8 and older).

You then read the text inside your element and write it on the canvas with the same font properties so that each letter you write on the canvas is over the corresponding letter in the element with the text.

The canvas element does not support multi-line text, so you'll have to break the text into words and then keep adding words on a test line which you then measure. If the width taken by the test line is bigger than the maximum allowed width you can have for a line (you get that maximum allowed width by reading the computed width of the element with the text), then you write it on the canvas without the last word added, you reset the test line to be that last word, and you increase the y coordinate at which to write the next line by one line height (which you also get from the computed styles of your element with the text). With each line that you write, you also decrease the opacity of the text with an appropriate step (this step being inversely proportional to the average number of characters per line).

What you cannot do easily in this case is to justify text. It can be done, but it gets a bit more complicated, meaning that you would have to compute how wide should each step be and write the text word by word rather than line by line.

Also, keep in mind that if your text container changes width as you resize the window, then you'll have to clear the canvas and redraw the text on it on each resize.

OK, the code:

HTML:

<article>
  <h1>Interacting Spiral Galaxies NGC 2207/ IC 2163</h1>
  <em class='timestamp'>February 4, 2004 09:00 AM</em>
  <section class='article-content' id='art-cntnt'>
    <canvas id='c' class='c'></canvas>In the direction of <!--and so on-->  
  </section>
</article>

CSS:

html {
  background: url(moving.jpg) 0 0;
  background-size: 200%;
  font: 100%/1.3 Verdana, sans-serif;
  animation: ani 4s infinite linear;
}
article {
  width: 50em; /* tweak this ;) */
  padding: .5em;
  margin: 0 auto;
}
.article-content {
  position: relative;
  color: rgba(0,0,0,0);
  /* add slash at the end to check they superimpose *
  color: rgba(255,0,0,.5);/**/
}
.c {
  position: absolute;
  z-index: -1;
  top: 0; left: 0;
}
@keyframes ani { to { background-position: 100% 0; } }

JavaScript:

var wrapText = function(ctxt, s, x, y, maxWidth, lineHeight) {
  var words = s.split(' '), line = '', 
      testLine, metrics, testWidth, alpha = 1, 
      step = .8*maxWidth/ctxt.measureText(s).width;

  for(var n = 0; n < words.length; n++) {
    testLine = line + words[n] + ' ';
    metrics = ctxt.measureText(testLine);
    testWidth = metrics.width;
    if(testWidth > maxWidth) {
      ctxt.fillStyle = 'rgba(0,0,0,'+alpha+')';
      alpha  -= step;
      ctxt.fillText(line, x, y);
      line = words[n] + ' ';
      y += lineHeight;
    }
    else line = testLine;
  }
  ctxt.fillStyle = 'rgba(0,0,0,'+alpha+')';
  alpha  -= step;
  ctxt.fillText(line, x, y);
  return y + lineHeight;
}

window.onload = function() {
  var c = document.getElementById('c'), 
      ac = document.getElementById('art-cntnt'), 
      /* use currentStyle for IE9 */
      styles = window.getComputedStyle(ac),
      ctxt = c.getContext('2d'), 
      w = parseInt(styles.width.split('px')[0], 10),
      h = parseInt(styles.height.split('px')[0], 10),
      maxWidth = w, 
      lineHeight = parseInt(styles.lineHeight.split('px')[0], 10), 
      x = 0, 
      y = parseInt(styles.fontSize.split('px')[0], 10), 
      text = ac.innerHTML.split('</canvas>')[1];

  c.width = w;
  c.height = h;
  ctxt.font = '1em Verdana, sans-serif';
  wrapText(ctxt, text, x, y, maxWidth, lineHeight);
};
Community
  • 1
  • 1
Ana
  • 35,599
  • 6
  • 80
  • 131
  • thanks so much for the information. your example is pretty cool. but that is way more effort to go to than my project requires ;-) i was hoping there was some built in css trick i didn't know about. i tried to do the jquery implementation, but i needed to know the background colour. i am using jquery themeroller in my site with many switchable themes, so i don't know the foreground or background colour of the text. so your example sets the text to black. – pgee70 Mar 25 '13 at 08:51
  • I noticed an interesting bug (feature?) with this; while you highlight the small description text, it resizes. – TylerH Nov 08 '14 at 19:52
3

We can do it by css like.

body {
  background: #000;
  background-image: linear-gradient(90deg, rgba(255, 255, 255, .3) 0%, rgba(231, 231, 231, .3) 22.39%, rgba(209, 209, 209,  .3) 42.6%, rgba(182, 182, 182, .3) 79.19%, rgba(156, 156, 156, .3) 104.86%);
  
}
Tariqul_Islam
  • 527
  • 7
  • 7