Prozentangaben grafisch gestalten

Exzellenter Artikel
von Philipe Fatio | 2 | 10893 Aufrufe

Anzeige Hier werben

In diesem Artikel wird gezeigt, wie man Prozentzahlen formatieren kann. Dabei wird auch Rücksicht auf semantisches HTML genommen und auf Einschränkungen hingewiesen. Sie können direkt zum fertigen JavaScript oder zum funktionierendem Beispiel springen.

Grundgedanke

Auf einer Seite mit mehreren Paragraphen Text befinden sich mittendrin mehrere Prozentzahlen mit unterschiedlichen Werten. Um diese mit einem klassischen Balken grafisch darzustellen, müsste jede Zahl ihre eigene CSS-Klasse haben, damit die einmalige Prozentzahl richtig dargestellt werden kann. Dies wäre jedoch etwas aufwändig und ineffizient. Daher wäre eine Lösung mit JavaScript angebracht.

Ein Prozentbalken besteht aus zwei Teilen: Der ungefüllte Teil und der gefüllte Teil. Der ungefüllte ist 100% breit und wird zu x% vom gefüllten Teil verdeckt. Unser Ziel ist es, dieses Gerüst zu erstellen und die x% auf den korrekten Wert zu setzen. Mit JS können wir dieses Gerüst erstellen, die Prozentzahl auslesen und die Darstellung dem entsprechend anpassen.

Wenn JS zum Einsatz kommt, muss darauf geachtet werden, dass bei deaktiviertem JS der Inhalt trotzdem einwandfrei zugänglich ist. Daher sind einige Überlegungen nötig.

Die Darstellung als Balken ist im Grunde nur eine Erweiterung zur reinen textlichen Form. Fehlt diese Dekoration, so ist der Inhalt dennoch lesbar und verständlich. Folglich ist eine Lösung mit JS möglich und sinnvoll.

Da es ineffizient wäre, die ganze Seite nach einer ein- bis dreistelligen Zahl, gefolgt von einem Prozentzeichen zu durchsuchen, beschliessen wir, dass jede Zahl, die grafisch dargestellt werden soll, mit einer CSS-Klasse versehen sein muss. Somit suchen wir per JS nach allen Elementen mit einer percent Klasse.

Damit dieses Element nur formatiert wird, wenn JS aktiviert ist, deklarieren wir in der CSS-Datei eine andere Klasse namens percent-on. Per JS werden dann alle percent zu percent-on umgewandelt, damit lediglich Browser mit aktiviertem JS die Formatierung zu Gesicht bekommen.

Umsetzung

Für die grafische Darstellung muss zwischen inline und block unterschieden werden, da sich diese wesentlich unterscheiden:

Inline-Darstellung

Eine Prozentzahl erscheint meist mitten in einer Textzeile wie z.B. "…auf einen Rekordwert von 23% gestiegen…". Inline-Elemente sind bekanntlich nur begrenzt formatierbar in Sachen width. Ideal wäre hier der Gebrauch von display: inline-block, welches die Eigenschaften eines Block-Elements in einem Inline-Element vereinigt. Leider wird diese vorteilhafte CSS-Eigenschaft nur von Safari und Opera unterstützt. Daher muss in diesem Fall eine andere Lösung her.

Mit padding können wir links von der Prozentzahl einen Abstand erzwingen. Wir setzen die Breite des Balken auf 100 Pixel fest und den Abstand vom Balken zur Prozentzahl auf 5 Pixel. Somit kommen wir auf ein padding-left von 105 Pixel. Nun müssen 2 Grafiken zum Einsatz kommen, Hintergrund und Balken, die beide die gewählte Breite von 100 Pixel haben müssen.

Bild zu Prozentangaben grafisch gestalten
Hintergrund
Bild zu Prozentangaben grafisch gestalten
Balken

Da wie gesagt der Balken aus zwei Elementen besteht, muss dass HTML per JS erweitert werden. Das von uns geschriebene HTML:

 
HTML
1
…auf einen Rekordwert von <span class="percent">23%</span> gestiegen…

Nach der Erweiterung durch JS:

 
HTML
1
…auf einen Rekordwert von <span class="percent-on"><span>23%</span></span> gestiegen…

Das padding-left wird auf das innere span-Element angewendet. Nun wird die Hintergrundgrafik auf das äussere Element angewendet und ohne horizontale Wiederholung links ausgerichtet. Die vertikale Wiederholung ist optional. Die Balkengrafik wird im inneren Element angebracht und zwar mit einer negativen horizontalen Ausrichtung um -77 Pixel (100 - 23 = 77). Diese negative Ausrichtung werden wir mit JS automatisieren. Möchte man, dass der Balken immer die Höhe des Textes hat, dann sollte man repeat-y verwenden. Möchte man jedoch, dass der Balken lediglich z.B. 10 Pixel hoch ist, so sollten die zwei Grafiken die Massen 100x10 haben und im CSS no-repeat verwenden.

Bild zu Prozentangaben grafisch gestalten
Durch negative Positionierung wird der gewünschte Effekt erzielt

Das CSS würde etwa wie folgt aussehen:

 
css
1
2
3
4
5
6
7
8
.percent-on {
    background: url(inline-background.gif) repeat-y 0 0;
}

.percent-on span {
    padding-left: 105px;
    background: url(inline-bar.gif) repeat-y 0 0;
}

Diese Variante hat einige Nachteile: Sie ist nur begrenzt formatierbar, da es ein Inline-Element ist. Des Weiteren müssen wir mit festen Grössen arbeiten, damit die Grafiken genau passen. Dies führt dazu, dass bei einer Vergrösserung der Schriftgrösse keine proportionale Vergrösserung stattfinden kann. Auch die Verwendung von Grafiken ist in mancher Hinsicht ein Nachteil, jedoch von Nöten, da man nur mit Hintergrundfarben ganze Elemente füllen kann. Mit display: inline-block hätte man all diese Probleme nicht.

Block-Darstellung

Bei der Block-Darstellung stehen uns viele Möglichkeiten zur Formatierung zur Verfügung. Wir können ohne Grafiken arbeiten und mit der Masseinheit em arbeiten, damit die Proportionen bei der Veränderung der Schriftgrösse erhalten bleiben. Wir müssen lediglich per JavaScript das HTML anpassen. Wir gehen von folgendem HTML aus:

 
HTML
1
2
3
<h4>Umsatzsteigerung</h4>

<p class="percent">66%</p>

Dieses wird dann per JS zu folgendem umgeformt und sollte nicht von Hand geschrieben werden:

 
HTML
1
2
3
4
5
6
7
<h4>Umsatzsteigerung</h4>

<p class="percent-on">
    <span class="bg">&nbsp;</span>
    <span class="bar">&nbsp;</span>
    <span class="value">66%</span>
</p>

Die zwei span Elemente erlauben uns viele Designmöglichkeiten. Beide span-Elemente müssen mit display: block versehen werden und um die gewünschte Höhe durch ein negatives margin-bottom nach unten verschoben werden. Die Höhe sollte man lediglich durch line-height definieren, damit die Prozentzahl gleichzeitig vertikal zentriert wird. So könnte dies in etwa aussehen:

 
css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.percent-on {
    line-height: 2em;    /* Höhe des Balken   */
    width: 15em;         /* Breite des Balken */
    text-align: center;
}

.percent-on .bg {
    display: block;
    margin-bottom: -2em; /* negativer Wert von line-height          */
    background: #ccc;    /* Farbe für den ungefüllten Balkenbereich */
}

.percent-on .bar {
    display: block;
    background: #f80;    /* Farbe für den gefüllten Balkenbereich */
    margin-bottom: -2em; /* negativer Wert von line-height        */
}

Nun müssen wir lediglich per JS die prozentuale Breite des Balken setzen.

Eine weiter Möglichkeit besteht darin, die Darstellung anhand von Grafiken schöner zu gestalten. Hierbei muss man leider auf die Beibehaltung der Proportionen bei Schriftgrössenänderungen verzichten. Ein Beispiel hierfür mit folgenden Grafiken:

Bild zu Prozentangaben grafisch gestalten
Hintergrundgrafik
Bild zu Prozentangaben grafisch gestalten
Balkengrafik

Per CSS wird nun alles richtig positioniert und die Prozentzahl durch text-indent rechts dargestellt:

 
css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
.percent-on {
    height: 10px;
    width: 298px;
    background: url(bg2.png);
    padding: 3px 3px 4px;
}

.percent-on .bg {
    display: none;
}

.percent-on .bar {
    height: 10px;
    display: block;
    margin-bottom: -10px;
    background: url(bar2.png);
}

.percent-on .value {
    display: block;
    text-indent: 305px; /* den Wert rechts des Balkens platzieren */
}

Kombination beider Darstellungen

Um beide Darstellungen nun zu kombinieren, empfiehlt es sich unterschiedliche Klassennamen zu verwenden, damit man per CSS beide Varianten problemlos definieren kann. Daher setzen wir fest: Prozentzahlen, die als inline dargestellt werden sollen, müssen mit der Klasse percentinline versehen werden. Analog dazu sind Zahlen, die als Blockelemente dargestellt werden sollen, als percentblock zu deklarieren.

JavaScript

Als Nächstes kommen wir zu unserem JavaScript. Dabei müssen wir wie folgt vorgehen:

  1. Alle Elemente mit der Klasse percentinline bzw. percentblock suchen.
  2. Überprüfen ob ein gültiger Prozentwert vorhanden ist.
  3. Das HTML an die inline- bzw. block-Darstellung anpassen.
  4. Die Prozentzahl auslesen und das entsprechende HTML-Element dieser anpassen.

Somit haben wir auch 4 Funktionen, die wir alle in ein Objekt packen, welches wir PercentageDecorator nennen. Die jeweiligen Funktionen sind:

  • initialize: durchsucht das Dokument nach den Prozent-Elementen
  • percentValue: liest den Wert aus
  • modifyInline: verändert das HTML für Inline-Prozente
  • modifyBlock: verändert das HTML für Block-Prozente

Somit steht das Grundgerüst unserer JS-"Klasse". Die folgende Schreibweise führt dazu, dass lediglich ein new PercentDecorator() nötig ist. Vergleichbar mit einer Klasse in PHP mit ihren Methoden.

 
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var PercentageDecorator = function() {
    this.initialize();
}

PercentageDecorator.prototype = {
    initialize: function() {},
    percentValue: function() {},
    inlineModify: function() {},
    blockModify: function() {}
};
initialize

Hier beginnt alles. Wir suchen zunächst per Regular Expressions nach allen Elementen, die entweder percentinline oder percentblock als Klasse haben. Anschliessend überprüfen wir den Rückgabewert der Funktion percentValue, ob es sich wirklich um eine Zahl handelt. Wenn ja, dann hängen wir den CSS-Klasse ein -on an und rufen die Funktion auf, um das HTML zu verändern.

 
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
initialize: function() {
    var elements = document.getElementsByTagName("*"),
    i = -1, element, regex, value;
    while(++i < elements.length) {
        element = elements[i];
        if(regex = /percent(inline|block)/.exec(element.className)) {
            if(value = this.percentValue(element)) {
                element.className = element.className.replace(new RegExp("percent"+regex[1]), "percent"+regex[1]+"-on");
                this[regex[1]+"Modify"](element, value);
            }
        }
    }
}
percentValue

Diese Funktion wird in initialize aufgerufen und hat als Argument das Element, welches nach einer Prozentzahl durchsucht werden soll. Es kann sein, dass die gesuchte Zahl in anderen Elementen eingepackt ist, wie z.B. in einem <em> oder <strong>. Deshalb durchforsten wir das Element bis wir auf einen Text stossen. Da wir hier mit parseFloat arbeiten und dieses keine Kommazahlen akzeptiert, ersetzen wir alle Kommas durch Punkte. Nun wird versucht, den String in eine Zahl umzuwandeln. Das %-Zeichen wird dabei einfach ignoriert. Falls dabei keine Zahl herauskommt, wird false zurückgegeben und der Balken wird nicht angezeigt. Wird hingegen ein Wert grösser als 100 ermittelt, so wird 100 zurückgegeben, da es ansonsten zu Anzeigefehlern durch das CSS kommt.

 
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
percentValue: function(e) {
    var value = false;
    while(e.firstChild) {
        if(e.firstChild.nodeType == 3) {
            value = e.firstChild.nodeValue;
            break;
        } else {
            e = e.firstChild;
        }
    }
    value = value.replace(/,/,".");
    value = parseFloat(value);
    if(isNaN(value)) return false;
    return (value > 100) ? 100 : value;
}
inlineModify

Bei der Inline-Methode müssen wir lediglich alles im Element in ein <span> packen. Danach wird der Hintergrund anhand der Prozentzahl, die als Argument übergeben wird, angepasst.

 
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
inlineModify: function(e, value) {
    var span = document.createElement("span");
    var childs = e.childNodes;
    var i = -1;
    while(++i < childs.length) {
        span.appendChild(childs[i]);
    }
    span.style.backgroundPosition = (-100+value)+"px 50%";
    e.appendChild(span);
}
blockModify

Zunächst müssen wir den bestehenden Inhalt in ein <span> packen. Danach müssen wir zwei <span>-Elemente erstellen, die ein non breaking space (bekannt als &nbsp;) enthalten, damit das Element überhaupt formatiert werden kann. Die Klassen müssen gesetzt werden und dann in das Block-Element eingefügt werden. Das Element, das als Balken fungiert, muss in seiner Breite an den Prozentwert angepasst werden, welches als Argument übergeben wurde.

 
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
blockModify: function(e, value) {
    var childs = e.childNodes;
    var span3 = document.createElement("span");
    span3.className = "value";
    var i = -1;
    while(++i < childs.length) {
        span3.appendChild(childs[i]);
    }
    
    var nbsp1 = document.createTextNode("\u00a0");
    var span1 = document.createElement("span");
    span1.className = "bg";
    span1.appendChild(nbsp1);
    e.appendChild(span1);
    
    var nbsp2 = document.createTextNode("\u00a0");
    var span2 = document.createElement("span");
    span2.className = "bar";
    span2.style.width = value+"%";
    span2.appendChild(nbsp2);
    e.appendChild(span2);
    
    e.appendChild(span3);
}
Das vollständige JavaScript

Hier nun das vollständige Script, welches beim vollständigen Laden der Seite ausgeführt wird.

 
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
var PercentageDecorator = function() {
    this.initialize();
}

PercentageDecorator.prototype = {
    initialize: function() {
        var elements = document.getElementsByTagName("*"),
        i = -1, element, regex, value;
        while(++i < elements.length) {
            element = elements[i];
            if(regex = /percent(inline|block)/.exec(element.className)) {
                if(value = this.percentValue(element)) {
                    element.className = element.className.replace(new RegExp("percent"+regex[1]), "percent"+regex[1]+"-on");
                    this[regex[1]+"Modify"](element, value);
                }
            }
        }
    },
    
    percentValue: function(e) {
        var value = false;
        while(e.firstChild) {
            if(e.firstChild.nodeType == 3) {
                value = e.firstChild.nodeValue;
                break;
            } else {
                e = e.firstChild;
            }
        }
        value = value.replace(/,/,".");
        value = parseFloat(value);
        if(isNaN(value)) return false;
        return (value > 100) ? 100 : value;
    },
    
    inlineModify: function(e, value) {
        var span = document.createElement("span");
        var childs = e.childNodes;
        var i = -1;
        while(++i < childs.length) {
            span.appendChild(childs[i]);
        }
        span.style.backgroundPosition = (-100+value)+"px 50%";
        e.appendChild(span);
    },
    
    blockModify: function(e, value) {
        var childs = e.childNodes;
        var span3 = document.createElement("span");
        span3.className = "value";
        var i = -1;
        while(++i < childs.length) {
            span3.appendChild(childs[i]);
        }
        
        var nbsp1 = document.createTextNode("\u00a0");
        var span1 = document.createElement("span");
        span1.className = "bg";
        span1.appendChild(nbsp1);
        e.appendChild(span1);
        
        var nbsp2 = document.createTextNode("\u00a0");
        var span2 = document.createElement("span");
        span2.className = "bar";
        span2.style.width = value+"%";
        span2.appendChild(nbsp2);
        e.appendChild(span2);
        
        e.appendChild(span3);
    }
};

var oldOnLoad = window.onload;
window.onload = function() {
    if(typeof oldOnLoad == "function") oldOnLoad();
    new PercentageDecorator();
}

Beispiel

Zum Schluss noch ein Beispiel, wie alles kombiniert funktioniert:

 
HTML
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.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>Percentage</title>
        <style type="text/css" media="screen">
body {
    font: .8em/1.35 Tahoma, Arial, sans-serif;
}

.percentblock-on.gfx {
    height: 10px;
    width: 298px;
    background: url(../../../image/bg2.png);
    padding: 3px 3px 4px;
}

.percentblock-on.gfx .bg {
    display: none;
}

.percentblock-on.gfx .bar {
    height: 10px;
    display: block;
    margin-bottom: -10px;
    background: url(../../../image/bar2.png);
}

.percentblock-on.gfx .value {
    display: block;
    text-indent: 305px;
}

.percentblock-on.nogfx {
    line-height: 2em;
    width: 15em;
    text-align: center;
}

.percentblock-on.nogfx .bg {
    display: block;
    margin-bottom: -2em;
    background: #ccc;
}

.percentblock-on.nogfx .bar {
    display: block;
    background: #f80;
    margin-bottom: -2em;
}

.percentinline-on {
    background: url(../../../image/inline-background.gif) repeat-y 0 50%;
}

.percentinline-on span {
    padding-left: 105px;
    background: url(../../../image/inline-bar.gif) repeat-y -100px 50%;
}
        </style>
        <script type="text/javascript" charset="utf-8">
            var PercentageDecorator = function() {
                this.initialize();
            }
            
            PercentageDecorator.prototype = {
                initialize: function() {
                    var elements = document.getElementsByTagName("*"),
                    i = -1, element, regex, value;
                    while(++i < elements.length) {
                        element = elements[i];
                        if(regex = /percent(inline|block)/.exec(element.className)) {
                            if(value = this.percentValue(element)) {
                                element.className = element.className.replace(new RegExp("percent"+regex[1]), "percent"+regex[1]+"-on");
                                this[regex[1]+"Modify"](element, value);
                            }
                        }
                    }
                },
                
                inlineModify: function(e, value) {
                    var span = document.createElement("span");
                    var childs = e.childNodes;
                    var i = -1;
                    while(++i < childs.length) {
                        span.appendChild(childs[i]);
                    }
                    span.style.backgroundPosition = (-100+value)+"px 50%";
                    e.appendChild(span);
                },
                
                blockModify: function(e, value) {
                    var childs = e.childNodes;
                    var span3 = document.createElement("span");
                    span3.className = "value";
                    var i = -1;
                    while(++i < childs.length) {
                        span3.appendChild(childs[i]);
                    }
                    
                    var nbsp1 = document.createTextNode("\u00a0");
                    var span1 = document.createElement("span");
                    span1.className = "bg";
                    span1.appendChild(nbsp1);
                    e.appendChild(span1);
                    
                    var nbsp2 = document.createTextNode("\u00a0");
                    var span2 = document.createElement("span");
                    span2.className = "bar";
                    span2.style.width = value+"%";
                    span2.appendChild(nbsp2);
                    e.appendChild(span2);
                    
                    e.appendChild(span3);
                },
                
                percentValue: function(e) {
                    var value = false;
                    while(e.firstChild) {
                        if(e.firstChild.nodeType == 3) {
                            value = e.firstChild.nodeValue;
                            break;
                        } else {
                            e = e.firstChild;
                        }
                    }
                    value = value.replace(/,/,".");
                    value = parseFloat(value);
                    if(isNaN(value)) return false;
                    return (value > 100) ? 100 : value;
                }
                
            };
            
            var oldOnLoad = window.onload;
            window.onload = function() {
                if(typeof oldOnLoad == "function") oldOnLoad();
                new PercentageDecorator();
            }
        </script>
    </head>
    <body>
        <h1>Prozentangaben grafisch gestalten</h1>
        <h2>Mit Grafiken</h2>
        <p class="percentblock gfx">33,33%</p>
        <h2>Ohne Grafiken</h2>
        <p><strong>Tipp:</strong> Ändern Sie die Textgrösse ihres Browsers um die zu sehen, wie dieses Beispiel skalierbar ist.</p>
        <p class="percentblock nogfx">50%</p>
        <h2>Mitten im Textfluss</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore <span class="test percentinline">75%</span> et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
    </body>
</html>
Über den Autor: Philipe Fatio
hat keine Beschreibung angegeben. Eine Beschreibung kann man unter dem Punkt "Profil bearbeiten" im Kontrollzentrum eintragen.
Profilseite betrachten