CSS progress bar text color for contrast filled and empty backgrounds?
Asked Answered
I

7

7

I want to have XHTML+CSS progress bar with contrast colors between filled and empty background areas.

I have a problem with text color. Because filled and empty backgrounds are too contrast (this is a requirement), to remain readable the text should be double-colored to be contrast to both of them. The image should explain it better than words:

Progress bar with dark blue filled area and white empty background http://drdaeman.pp.ru/tmp/20090703/progress-bar-text-example.png Example of the problem http://drdaeman.pp.ru/tmp/20090703/progress-bar-text-problem.png

My current progress bar implementation is trivial, but as example above shows, the text can be hard to read in some cases, which is exactly a problem I want to solve.

My current (simplified) implementation attempt (fails, because overflow: hidden does not work without positioning div.progress which I cannot position because of inner span's width):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  <title>Progress bar test</title>
  <style type="text/css">
    div.progress_bar {
        border: 1px #ccc solid; position: relative;
        text-align: center; height: 32px;
    }
    div.progress_bar .progress {
        height: 32px;
        overflow: hidden; /* This does NOT work! */
    }
    div.progress_bar .progress div {
        position: absolute; width: 100%; height: 32px;
        z-index: 30; overflow: hidden;
        background-color: #44a;
    }
    div.progress_bar span {
        position: absolute; top: 0; left: 0; width: 100%;
        z-index: 20;
        color: #000;
    }
    div.progress_bar .progress span {
        position: absolute; top: 0; left: 0; width: 100%;
        z-index: 40;
        color: #eee;
    }
  </style>
</head>
<body>
  <!-- Can be of any (unknown) width. Think of "width: auto".
       The 400px value is just to keep it small on a big monitor.
       DON'T rely on it! -->
  <div id="container" style="width: 400px;">
    <div class="progress_bar">
      <!-- div.progress is a dark filled area container -->
      <div class="progress" style="width: 51%;">
        <!-- Actually dark filled area -->
        <div style="width: 51%;"></div>
        <!-- Text (white).
             Does not clip, even with overflow: hidden on parent! -->
        <span>This is a test</span>
      </div>
      <!-- Text (black) -->
      <span>This is a test</span>
    </div>
  </div>
</body>
</html>

Live version of the above: http://drdaeman.pp.ru/tmp/20090703/test2.html
Previous attempt: http://drdaeman.pp.ru/tmp/20090703/test.html

The images are GIMP edited prototypes, and not exactly what this code displays.

Add: Thank you all, especially Meep3D, Nosredna and Lachlan! However I still have a problem — in my case progress bar should have no fixed width and take all horizontally available space (width: auto; or width: 100% are acceptable). But without width: 400px rule Lachlan's code breaks. And I'd still like to avoid using JavaScript, if that's possible.

Incommensurable answered 3/7, 2009 at 20:17 Comment(10)
What, exactly, is the problem with 0% and 100%?Usia
Either it's a black text on dark blue background (very hard to read), or white text on white background (impossible to read).Incommensurable
I must be missing something. Why not make it white when it's on dark blue and black when it's on white?Usia
I think there's a problem with 0-50% or 50-100%, not so much only 0 and 100%. Like the picture shows, it's hard to pick a single color that is readable on both background colors.Allude
Is the image from your current implementation? It looks fine at 50%.Usia
You don't have a single color. You have two colors.Usia
@Usia that's what he wants to do. But how? How using only the tools available would he accomplish this rather involved task? Notice that part of the 0 is white and the other part of the same glyph is black.Allude
So the illustration is what he wants, then. It's not what he already has? I thought it was working all along except exactly at 0 and 100 due to some glitch.Usia
Exactly, I think that's what he would like to do but can't get it to work with XHTML and CSS.Allude
I get it. So is this bar updating in real time, or per page load? I've done exactly what you're showing, but I used JavaScript to move the bar.Usia
B
8

As per Meep3D's suggestion, take 2 copies of the text.

Wrap each in a div of the same width as the container. The "upper" div is wrapped with another div which clips at the desired percentage.

Update: removed the fixed widths.
The "upper" div is sized to the inverse percentage of its wrapper.

<html>
<head>
  <style type="text/css">
    #container {
        position: relative;
        border: 1px solid;
        text-align: center;
        width: 400px;
        height: 32px;
    }
    .black-on-white {
        height: 32px;
        color: #000;
    }
    .white-on-black {
        height: 32px;
        color: #fff;
        background-color: #44a;
    }
    .wrapper {
        width: 53%;
        overflow: hidden;
        position: absolute;
        top: 0; left: 0;
    }
    .black-on-white {
        width: 100%;
    }
    .white-on-black {
        width: 188.7%;
    }
  </style>
</head>
<body>
  <div id="container">
    <div class="wrapper">
        <div class="white-on-black">
             <span>This is a test</span>
        </div>
    </div>
    <div class="black-on-white">
        <span>This is a test</span>
    </div>
  </div>
</body>
</html>
Breathtaking answered 4/7, 2009 at 12:43 Comment(2)
Thank you, this is what I tried yesterday too. One problem — in my case progress bar should have no fixed width and take all horizontally available space. But without width: 400px rule everything breaks. And I'd like to avoid using JavaScript, if possible.Incommensurable
Awesome! Thank you very much, it works great. Didn't thought about (1/percent) width.Incommensurable
M
8

What about putting a second copy of the progress bar text inside the div, and set the div's overflow to hidden, so it reveals with it?

--

Update: I am also not a javascript expert, but I am sure that you can find out the width of an object and then set the offset based upon that if the width is flexible as you say.

Mansur answered 3/7, 2009 at 20:20 Comment(2)
This is what you want to do. Have two versions of the bar over top each other and progressively reveal one of them.Usia
I've got the overall idea — it seems to be a right way to go, but the progress has no fixed width and automatically stretches to all available space, so putting a copy in inner (filled area) div with example width: 50% don't work.Incommensurable
B
8

As per Meep3D's suggestion, take 2 copies of the text.

Wrap each in a div of the same width as the container. The "upper" div is wrapped with another div which clips at the desired percentage.

Update: removed the fixed widths.
The "upper" div is sized to the inverse percentage of its wrapper.

<html>
<head>
  <style type="text/css">
    #container {
        position: relative;
        border: 1px solid;
        text-align: center;
        width: 400px;
        height: 32px;
    }
    .black-on-white {
        height: 32px;
        color: #000;
    }
    .white-on-black {
        height: 32px;
        color: #fff;
        background-color: #44a;
    }
    .wrapper {
        width: 53%;
        overflow: hidden;
        position: absolute;
        top: 0; left: 0;
    }
    .black-on-white {
        width: 100%;
    }
    .white-on-black {
        width: 188.7%;
    }
  </style>
</head>
<body>
  <div id="container">
    <div class="wrapper">
        <div class="white-on-black">
             <span>This is a test</span>
        </div>
    </div>
    <div class="black-on-white">
        <span>This is a test</span>
    </div>
  </div>
</body>
</html>
Breathtaking answered 4/7, 2009 at 12:43 Comment(2)
Thank you, this is what I tried yesterday too. One problem — in my case progress bar should have no fixed width and take all horizontally available space. But without width: 400px rule everything breaks. And I'd like to avoid using JavaScript, if possible.Incommensurable
Awesome! Thank you very much, it works great. Didn't thought about (1/percent) width.Incommensurable
M
1

You could:

  • Find a grey which suits
  • Use JavaScript to change the colour between white and black dynamically, depending on where it is
  • Make the middle colour of the background gradient closer to white, and always use dark text
  • Put the progress outisde the box:
[#########              ] 50 % 
Microfilm answered 3/7, 2009 at 20:19 Comment(1)
Thank you. I'm considering those suggestions as a "fallback" options if I'll fail to implement what I exactly want. Still, if it's possible, I'd really like to implement it the way it's shown on the picture in my question.Incommensurable
E
1

You could use a text shadow for your "percentage" text. The only downside to this is that it would only work in the latest browsers. Only Firefox 3.5, Safari (all versions), and Chrome 2+ support it.

Here is a demo of using text-shadow in a way that would make your progress readable.
http://www.w3.org/Style/Examples/007/text-shadow#white

If you're willing to use more JavaScript, you could try this jQuery plugin:

http://kilianvalkhof.com/2008/javascript/text-shadow-in-ie-with-jquery/

The article says it works in IE only, however it works in Chrome 3 (what I'm using), Firefox 3.5, Internet Explorer, and Safari. It may work in older browsers but I haven't tested it.

Eupheemia answered 3/7, 2009 at 20:33 Comment(0)
U
1

Meep3D has the correct answer. Two versions of the box. Reveal n% of the top one.

More options:

  • Put a translucent box under the number that either darkens the area for a white number or lightens the area for a black number.
  • Use red and white as backgrounds and a black number. (Problem here is red is associated with error, so you can play with other combinations of three colors that are all high contrast against each other.)
Usia answered 3/7, 2009 at 20:34 Comment(0)
C
1

You need 2 values styled differently. And fixed width

let counter = 0

const increment = () => {
  counter++
}

let interval = setInterval(() => {
  increment();
  document.querySelectorAll('.value').forEach(node => {
    node.textContent = `${counter}`  
  });
  document.querySelector('.progress-bar').style.width = `${counter}%`
  if (counter >= 100) clearInterval(interval);
}, 50)
.progress-wrapper{
  margin: 20px auto;
  width: 400px; 
  height: 20px;
  background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 50%, #ccc 50%, #ccc 75%, transparent 75%, transparent);
  animation: progress-bar-stripes 2s linear infinite;
  background-size: 40px 40px;
  position: relative;
  border-radius: 5px;
  overflow: hidden;
 
}
.progress-bar{
  z-index: 3;
  overflow: hidden;
  position: absolute;
  height: 20px;
  background-color: #8178d9; 
  text-align: center;
  transition: width 0.5s ease;
}
.progress-value-1, .progress-value-2{
  margin: 0;
  position: absolute;
  width: 400px; 
  color: #8178d9;
  text-align: center;
  z-index: 2;
  font-weight: bold;
}
.progress-value-2{
  color: #fff;
  z-index: 1;
}
@keyframes progress-bar-stripes {
  from {
    background-position: 40px 0;
  }
  to {
    background-position: 0 0;
  }
}
<div class="container">
  <div class="progress-wrapper">
    <div class="progress-bar">
      <p class="progress-value-2">
        <span class="value"></span>%
      </p>
    </div>
    <p class="progress-value-1">
      <span class="value"></span>%
    </p>
  </div>
</div>

https://codepen.io/kosachevlad/pen/dypEjBa

Calandracalandria answered 25/1, 2021 at 9:36 Comment(0)
C
0

This answer with the use of clip-path: inset(0 0 0 50%); is great.

The use of a background linear gradient with a background-clip as described in this answer is also interesting.

Consol answered 27/10, 2022 at 18:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.