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.
-
What, exactly, is the problem with 0% and 100%?Nosredna– Nosredna2009年07月03日 20:19:26 +00:00Commented Jul 3, 2009 at 20:19
-
Either it's a black text on dark blue background (very hard to read), or white text on white background (impossible to read).drdaeman– drdaeman2009年07月03日 20:20:22 +00:00Commented Jul 3, 2009 at 20:20
-
I must be missing something. Why not make it white when it's on dark blue and black when it's on white?Nosredna– Nosredna2009年07月03日 20:21:35 +00:00Commented Jul 3, 2009 at 20:21
-
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.colithium– colithium2009年07月03日 20:22:37 +00:00Commented Jul 3, 2009 at 20:22
-
1@Nosredna 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.colithium– colithium2009年07月03日 20:24:05 +00:00Commented Jul 3, 2009 at 20:24
7 Answers 7
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>
2 Comments
width: 400px rule everything breaks. And I'd like to avoid using JavaScript, if possible.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.
2 Comments
width: 50% don't work.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 %
1 Comment
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.
Comments
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.)
Comments
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>
Comments
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.