Advertisement
  1. Web Design

Making Web Icons Smarter

Scroll to top
12 min read

This article is the first in a three-part series showing the new approaches to iconography Iconic will be delivering. If you like what you see in this article, please consider backing Iconic on Kickstarter.


What Exactly is a Smart Icon?

A smart icon is designed so that elements within it to adjust to input, interaction or associated data. In short, it's dynamic. Smart icons are SVG-based and controlled with a combination of Javascript and CSS.


Why This is Relevant

Web icons up to this point have been static—primarily due to limitations in technology. Different states or variations of an icon would be created simply through providing individual files of each permutation. For example, a battery icon would come in four different versions: charging, full-charge, half-charge and no-charge. Not exactly an optimal approach.

A whole new set of possibilities are now viable with mainstream browser adoption of SVG 1.1. Due to SVG's semantic structure, a smart icon can display its full range of states and variations. This removes the need for image swapping and allows transitions between states to occur fluidly.

Smart icons also give designers the option to display relevant contextual information within an icon. A well designed icon is already a relatively informationally dense object. By adding contextual information to an icon, its informational density is even greater without considerable increased cognitive load. In theory, these types of icons will be able to take on the heavy-lifting of communication, thus reducing the amount of other elements on the screen.


Use-cases for Smart Icons

There are many different directions smart icons can go—some being easier to implement than others. We're still in the discovery process, but so far we've come up with three primary use cases:

Providing contextual information

There are plenty of icons which could provide another layer of information, but simply have not up to this point, due to their static display method. Examples include icons such as the clock, thermometer, aperture, WIFI signal and battery charge.

clockclockclock

Acting as simple data-vis elements (when d3.js is too much)

One of the best use-cases for smart icons is simple data visualization. Icons that fit in this category are the audio spectrum, gauge/meter and loading indicator. Smart icons could dramatically simplify the process of building dashboard displays—think of simply adding four or five icons to your HTML and adjusting the gauge value with a data attribute.

audio-spectrum-analyzeraudio-spectrum-analyzeraudio-spectrum-analyzer

Displaying various states

Many icons often come in a series of variations to convey all their different states. Examples include the battery, WIFI, media playback (e.g., play, pause, etc.) and power (e.g., on, off, standby). Another potential application for smart icons is to roll all of an icon's states into a single SVG. So instead of swapping out image assets when a subject's state changes, you simply change a data attribute to the appropriate state.

batterybatterybattery

Nuts and Bolts

Note: Before going into details, it's important to note that the examples we're showing are simply proof-of-concept prototypes. These prototypes are intended to communicate the functionality we'll be building. None of the following code is final, let alone beta. We're still in the R&D phase of this method and we know there are many issues that still need to be addressed. We will be working on a more concrete direction for this method after the Kickstarter campaign is complete.

Smart icons consist of SVG, Javascript and CSS. Our current thinking is to treat each icon like a small self-contained app with a simple API to adjust elements within the icon. To achieve this reliably, this approach requires the SVG markup to be included in the DOM.

Keep in mind that the SVG mark up needs to be appropriately structured for this approach to work. This is what we feel makes Iconic unique. The icons are being designed and crafted with new concepts in mind. These concepts rely on clear semantics and a well thought out markup structure to work correctly. This is no different from appropriately structured HTML—if your markup is gobbleygoop, it's going to be difficult to do anything sophisticated.

A lot of compelling things can happen once well structured SVG markup is in the DOM. The problem is that adding SVG markup into HTML is a pain. The SVG markup can add a considerable amount of bloat to your code and it becomes harder to distinguish between structural HTML and vector imagery. In order to remove this friction, we suggest injecting SVG markup in the DOM at runtime.

We've made a simple prototype SVG injector which replaces all specified img tags with the markup from the referenced SVG file. So this...

1
2
<div class="image-container">
3
    <img src="images/circle.svg" class="svg-inject" width="300" />
4
</div>

Turns into this:

1
2
<div class="image-container">
3
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100px" height="100px">
4
        <circle cx="50" cy="50" r="50"/>
5
    </svg>
6
</div>

Note: Keep in mind, this injector approach certainly feels like a stopgap measure and we're hoping our work will help push a browser-native standard. Until then, our current thinking is that this is the best approach.

Once the SVG is injected in the DOM, the JavaScript encapsulated within it is executed and it's ready to be used. Some icons will run on their own (like a clock) whereas others will need input to adjust.

Self-running Icon

The clock is a perfect example of an icon that runs on its own. Once injected, it will just go. See it in action

HTML

1
2
<img src="clock.svg" class="svg-inject" alt="clock" />

JS

1
2
$('.svg-inject').svgInject();

SVG: clock.svg

1
2
<svg version="1.1" class="iconic-clock" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="384px" height="384px" viewBox="0 0 384 384" enable-background="new 0 0 384 384" xml:space="preserve">
3
    <path class="iconic-clock-frame"        d="M192,0C85.961,0,0,85.961,0,192s85.961,192,192,192s192-85.961,192-192S298.039,0,192,0zM315.037,315.037c-9.454,9.454-19.809,17.679-30.864,24.609l-14.976-25.939l-10.396,6l14.989,25.964c-23.156,12.363-48.947,19.312-75.792,20.216V336h-12v29.887c-26.845-0.903-52.636-7.854-75.793-20.217l14.989-25.963l-10.393-6l-14.976,25.938c-11.055-6.931-21.41-15.154-30.864-24.608s-17.679-19.809-24.61-30.864l25.939-14.976l-6-10.396l-25.961,14.99C25.966,250.637,19.017,224.846,18.113,198H48v-12H18.113c0.904-26.844,7.853-52.634,20.216-75.791l25.96,14.988l6.004-10.395L44.354,99.827c6.931-11.055,15.156-21.41,24.61-30.864s19.809-17.679,30.864-24.61l14.976,25.939l10.395-6L110.208,38.33C133.365,25.966,159.155,19.017,186,18.113V48h12V18.113c26.846,0.904,52.635,7.853,75.792,20.216l-14.991,25.965l10.395,6l14.978-25.942c11.056,6.931,21.41,15.156,30.865,24.611c9.454,9.454,17.679,19.808,24.608,30.863l-25.94,14.976l6,10.396l25.965-14.99c12.363,23.157,19.312,48.948,20.218,75.792H336v12h29.887c-0.904,26.845-7.853,52.636-20.216,75.792l-25.964-14.989l-6.002,10.396l25.941,14.978C332.715,295.229,324.491,305.583,315.037,315.037z" />
4
    <line class="iconic-clock-hour-hand" id="foo" fill="none" stroke="#000000" stroke-width="18" stroke-miterlimit="10" x1="192" y1="192" x2="192" y2="87.5"/>
5
    <line class="iconic-clock-minute-hand" id="iconic-anim-clock-minute-hand" fill="none" stroke="#000000" stroke-width="12" stroke-miterlimit="10" x1="192" y1="192" x2="192" y2="54"/>
6
    <circle class="iconic-clock-axis" cx="192" cy="192" r="9"/>
7
    <g class="iconic-clock-second-hand" id="iconic-anim-clock-second-hand">
8
        <line class="iconic-clock-second-hand-arm" fill="none" stroke="#D53A1F" stroke-width="4" stroke-miterlimit="10" x1="192" y1="192" x2="192" y2="28.5"/>
9
        <circle class="iconic-clock-second-hand-axis" fill="#D53A1F" cx="192" cy="192" r="4.5"/>
10
    </g>
11
    <defs>
12
        <animateTransform
13
            type="rotate"
14
            fill="remove"
15
            restart="always"
16
            calcMode="linear"
17
            accumulate="none"
18
            additive="sum"
19
            xlink:href="#iconic-anim-clock-hour-hand"
20
            repeatCount="indefinite"
21
            dur="43200s"
22
            to="360 192 192"
23
            from="0 192 192"
24
            attributeName="transform"
25
            attributeType="xml">
26
        </animateTransform>
27
28
        <animateTransform
29
            type="rotate"
30
            fill="remove"
31
            restart="always"
32
            calcMode="linear"
33
            accumulate="none"
34
            additive="sum"
35
            xlink:href="#iconic-anim-clock-minute-hand"
36
            repeatCount="indefinite"
37
            dur="3600s"
38
            to="360 192 192"
39
            from="0 192 192"
40
            attributeName="transform"
41
            attributeType="xml">
42
        </animateTransform>
43
44
        <animateTransform
45
            type="rotate"
46
            fill="remove"
47
            restart="always"
48
            calcMode="linear"
49
            accumulate="none"
50
            additive="sum"
51
            xlink:href="#iconic-anim-clock-second-hand"
52
            repeatCount="indefinite"
53
            dur="60s"
54
            to="360 192 192"
55
            from="0 192 192"
56
            attributeName="transform"
57
            attributeType="xml">
58
        </animateTransform>
59
    </defs>
60
    <script  type="text/javascript">
61
    <![CDATA[

62
        var date = new Date;

63
        var seconds = date.getSeconds();

64
        var minutes = date.getMinutes();

65
        var hours = date.getHours();

66
        hours = (hours > 12) ? hours - 12 : hours;
67
68
        minutes = (minutes * 60) + seconds;
69
        hours = (hours * 3600) + minutes;
70
71
        document.querySelector('.iconic-clock-second-hand').setAttribute('transform', 'rotate('+360*(seconds/60)+',192,192)');
72
        document.querySelector('.iconic-clock-minute-hand').setAttribute('transform', 'rotate('+360*(minutes/3600)+',192,192)');
73
        document.querySelector('.iconic-clock-hour-hand').setAttribute('transform', 'rotate('+360*(hours/43200)+',192,192)');
74
    ]]>
75
    </script>
76
</svg>

Input-based Icon

When an icon is responding to input or data, it requires a little more work, but the basics are unchanged. See it in action

HTML

1
2
<img src="audio-spectrum-analyzer.svg" class="svg-inject" alt="audio spectrum analyzer" />

JS

1
2
$('.svg-inject').svgInject();

SVG: audio-spectrum-analyzer.svg

1
2
<svg id="audio-spectrum-analyzer" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="320px" width="210px" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 210 320" onclick="toggleAudio()">  
3
  <g id="eq-bars" height="320px" width="210px" fill="#010101" transform="">
4
    <rect x="00" y="150" height="20" width="10" />
5
    <rect x="20" y="140" height="40" width="10"  />
6
    <rect x="40" y="100" height="120" width="10"  />
7
    <rect x="60" y="120" height="80" width="10"  />
8
    <rect x="80" y="60" height="200" width="10"  />
9
    <rect x="100" y="20" height="280" width="10"  />
10
    <rect x="120" y="70" height="180" width="10"  />
11
    <rect x="140" y="120" height="80" width="10"  />
12
    <rect x="160" y="140" height="40" width="10"  />
13
    <rect x="180" y="150" height="20" width="10"  />
14
    <rect x="200" y="155" height="10" width="10"  />
15
  </g>
16
17
  <defs>
18
    <style type="text/css"><![CDATA[

19
      svg#audio-spectrum-analyzer {

20
        margin: 0 auto;

21
      }

22
    ]]></style>
23
  </defs>
24
25
  <script type="application/javascript"><![CDATA[

26
    var context;

27
    if (typeof AudioContext !== "undefined") {

28
      context = new AudioContext();

29
    }

30
    else if (typeof webkitAudioContext !== "undefined") {

31
      context = new webkitAudioContext();

32
    }

33
    else {

34
      throw new Error('AudioContext not supported. :(');

35
    }

36


37
    var eqHeight = document.querySelector('svg#audio-spectrum-analyzer > g#eq-bars').getAttribute('height').replace('px', '');
38
    var bars = document.querySelectorAll('svg#audio-spectrum-analyzer rect');
39
40
    var playing = false;
41
42
    var audioFileUrl = document.querySelector('svg#audio-spectrum-analyzer').getAttribute('data-audiofile');
43
    if (audioFileUrl === undefined) {
44
      throw new Error('Audio File not defined');
45
    }
46
47
    var soundSource;
48
    var fft;
49
    var fftSmoothing = 0.6;
50
    var fftMaxValue = 256;
51
    var samples = 128;
52
    var sampleIntervalID;
53
    var ampFactor = 1.25;
54
    var numBars = bars.length;
55
    var soundBuffer;
56
57
58
    var request = new XMLHttpRequest();
59
    request.open("GET", audioFileUrl, true);
60
    request.responseType = "arraybuffer";
61
62
    // Our asynchronous callback
63
    request.onload = function () {
64
      var audioData = request.response;
65
66
      // The Audio Context handles creating source
67
      // buffers from raw binary data
68
      soundBuffer = context.createBuffer(audioData, true /*make mono*/ );
69
70
    };
71
    request.send();
72
73
    function sampleAudio() {
74
      var data = new Uint8Array(fft.frequencyBinCount);
75
      fft.getByteFrequencyData(data);
76
77
      // Calc bin size to sum freqs into.
78
      // Carve off some of the high-end, lower energy bars (+2)
79
      var bin_size = Math.floor(data.length / (numBars + 2));
80
81
      // Sum up and average the samples into their bins
82
      for (var i = 0; i < numBars; ++i) {
83
84
        // Sum this bin
85
        var sum = 0;
86
        for (var j = 0; j < bin_size; ++j) {
87
          sum += data[(i * bin_size) + j];
88
        }
89
90
        // Duck some of the low-end power
91
        if (i === 0) {
92
          sum = sum * 0.75;
93
        }
94
95
        // Calculate the average frequency of the samples in the bin
96
        var average = sum / bin_size;
97
        var scaled_average = Math.max(10, ((average / fftMaxValue) * eqHeight) * ampFactor);
98
99
        // Update eq bar height
100
        bars[i].setAttribute('height', scaled_average);
101
102
        // Center bar
103
        bars[i].setAttribute('y', (eqHeight - scaled_average) / 2);
104
      }
105
    }
106
107
    function playSound() {
108
      // create a sound source
109
      soundSource = context.createBufferSource();
110
111
      // Add the buffered data to our object
112
      soundSource.buffer = soundBuffer;
113
114
      // Create the FFT
115
      fft = context.createAnalyser();
116
      fft.smoothingTimeConstant = fftSmoothing;
117
      fft.fftSize = samples;
118
119
      soundSource.connect(fft);
120
      fft.connect(context.destination);
121
122
      soundSource.noteOn(context.currentTime);
123
124
      // Start the FFT sampler
125
      sampleIntervalID = setInterval(sampleAudio, 30);
126
127
      playing = true;
128
    }
129
130
    function stopSound() {
131
      // Stop the FFT sampler
132
      clearInterval(sampleIntervalID);
133
134
      if (soundSource) {
135
        soundSource.noteOff(context.currentTime);
136
      }
137
      playing = false;
138
    }
139
140
    var toggleAudio = function () {
141
      if (!playing) {
142
        playing = true;
143
        playSound();
144
      }
145
      else {
146
        stopSound();
147
        playing = false;
148
      }
149
    }
150
151
    window.addEventListener('load', function () {
152
      window.toggleAudio = toggleAudio;
153
    }, false);
154
155
  ]]></script>
156
157
</svg>

Adding motion (the icing on the cake)

A smart icon becomes even better with motion. There are many ways to do this. We're currently using SVG animation elements as this allows considerable functionality built right into the browser—meaning less code in the SVG. Support is still a little wonky (we ran into issues in Safari 6), but it's getting better by the day. See it in action

HTML

1
2
<img src="thermometer.svg" class="svg-inject" alt="thermometer" />
3
<ul class="menu">
4
  <li><a href="#1">Hot</a></li>
5
  <li><a href="#0.66">Warm</a></li>
6
  <li><a href="#0.33">Chilly</a></li>
7
  <li><a href="#0">Cold</a></li>
8
</ul>

JS

1
2
$('.svg-inject').svgInject();

SVG: themometer.svg

1
2
<svg version="1.0" class="iconic-thermometer"  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="40px"
3
 height="128px" viewBox="0 0 40 128" enable-background="new 0 0 40 128" xml:space="preserve">
4
    <path class="iconic-thermometer-container" d="M20,6c2.757,0,5,2.243,5,5v80.305v3.234l2.701,1.777C31.646,98.912,34,103.279,34,108c0,7.72-6.28,14-14,14

5
            s-14-6.28-14-14c0-4.721,2.354-9.088,6.298-11.684L15,94.539v-3.234V11C15,8.243,17.243,6,20,6 M20,0C13.935,0,9,4.935,9,11

6
            v80.305c-5.501,3.62-9,9.829-9,16.695c0,11.028,8.972,20,20,20c11.028,0,20-8.972,20-20c0-6.865-3.499-13.075-9-16.695V11

7
            C31,4.935,26.065,0,20,0L20,0z"/>
8
    <line class="iconic-thermometer-shaft iconic-thermometer-hot" id="iconic-anim-thermometer-shaft" fill="none" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="20" y1="108" x2="20" y2="11" />
9
    <circle class="iconic-thermometer-well iconic-thermometer-hot" cx="20" cy="108" r="12" />
10
11
    <animate
12
        id="shaft-animate"
13
        attributeName= "y2"
14
        begin= "indefinite"
15
        dur="1s"
16
        xlink:href="#iconic-anim-thermometer-shaft"
17
        fill="freeze"
18
        calcMode="spline"
19
        keySplines="0.42 0 0.58 1"
20
        keyTimes="0;1"
21
        restart="whenNotActive"
22
    />
23
24
    <script type="text/ecmascript">
25
    <![CDATA[

26


27
        var shaft = document.querySelector('#iconic-anim-thermometer-shaft');

28
        var well = document.querySelector('.iconic-thermometer-well');

29
        var yOrigin2 = parseFloat(shaft.getAttribute('y2'));

30
        var yOrigin1 = parseFloat(shaft.getAttribute('y1'));

31
        var yPos = yOrigin2;

32
        var tempClass;

33


34
        window.addEventListener('hashchange', function() {

35
        var hash = window.location.hash.substr(1);

36
        goto(hash);

37
        }, false);

38


39
        function goto(amount) {

40
            var shaftAnim = document.querySelector('#shaft-animate');

41


42
                shaft.setAttribute('y2', yPos)

43


44
        amount = parseFloat(amount)

45
        if( isNaN( amount ) ) return;

46


47
                if(amount>.9) {
48
                    tempClass="iconic-thermometer-hot";
49
                } else if(amount>.5) {
50
                    tempClass="iconic-thermometer-warm";
51
                } else if(amount>.2) {
52
                    tempClass="iconic-thermometer-chilly";
53
                } else {
54
                    tempClass="iconic-thermometer-cold";
55
                }
56
57
        amount = 1 - amount;
58
        amount = Math.min(Math.max(0, amount), 1);
59
60
        var ry = ( amount * ( yOrigin1-yOrigin2 ) ) + yOrigin2;
61
62
                /*
63
                 * Unfortunately, Safari doesn't make life easy on us. We need to remove and re-initialize
64
                 * the animation element for animations to start from the last end point.
65
                 */
66
        var ns =  shaftAnim.cloneNode(true);
67
        ns.setAttribute( 'from', yPos )
68
        ns.setAttribute( 'to', ry );
69
70
        shaftAnim.parentNode.replaceChild(ns, shaftAnim);
71
72
                well.setAttribute('class', 'iconic-thermometer-well ' + tempClass);
73
                shaft.setAttribute('class', 'iconic-thermometer-shaft ' + tempClass);
74
        ns.beginElement();
75
        yPos = ry;
76
77
        }
78
    ]]>
79
    </script>
80
</svg>

Conclusion

Iconography has a significant role to play in interface design. The more relevant information our icons can provide, the more powerful they become. We truly believe that smart iconography can be a compelling tool for designers to add another layer of meaning to their icons. Not every icon is suited for this approach—like all good things it requires moderation. However, when used appropriately, it can be a tremendous new tool.


Back Iconic on Kickstarter

The goal of Iconic is to help provide new approaches to iconography. There's a lot more to Iconic than just smart icons and we're looking forward to sharing another interesting feature with you next week.

iconic-logoiconic-logoiconic-logo

Please consider backing Iconic on Kickstarter.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.