Advertisement
General

Making Web Icons Smarter

by

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.

clock

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-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.

battery

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...

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

Turns into this:

<div class="image-container">
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100px" height="100px">
        <circle cx="50" cy="50" r="50"/>
    </svg>
</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

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

JS

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

SVG: clock.svg

<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">
    <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" />
    <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"/>
    <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"/>
    <circle class="iconic-clock-axis" cx="192" cy="192" r="9"/>
    <g class="iconic-clock-second-hand" id="iconic-anim-clock-second-hand">
        <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"/>
        <circle class="iconic-clock-second-hand-axis" fill="#D53A1F" cx="192" cy="192" r="4.5"/>
    </g>
    <defs>
        <animateTransform
            type="rotate"
            fill="remove"
            restart="always"
            calcMode="linear"
            accumulate="none"
            additive="sum"
            xlink:href="#iconic-anim-clock-hour-hand"
            repeatCount="indefinite"
            dur="43200s"
            to="360 192 192"
            from="0 192 192"
            attributeName="transform"
            attributeType="xml">
        </animateTransform>

        <animateTransform
            type="rotate"
            fill="remove"
            restart="always"
            calcMode="linear"
            accumulate="none"
            additive="sum"
            xlink:href="#iconic-anim-clock-minute-hand"
            repeatCount="indefinite"
            dur="3600s"
            to="360 192 192"
            from="0 192 192"
            attributeName="transform"
            attributeType="xml">
        </animateTransform>

        <animateTransform
            type="rotate"
            fill="remove"
            restart="always"
            calcMode="linear"
            accumulate="none"
            additive="sum"
            xlink:href="#iconic-anim-clock-second-hand"
            repeatCount="indefinite"
            dur="60s"
            to="360 192 192"
            from="0 192 192"
            attributeName="transform"
            attributeType="xml">
        </animateTransform>
    </defs>
    <script  type="text/javascript">
    <![CDATA[
        var date = new Date;
        var seconds = date.getSeconds();
        var minutes = date.getMinutes();
        var hours = date.getHours();
        hours = (hours > 12) ? hours - 12 : hours;

        minutes = (minutes * 60) + seconds;
        hours = (hours * 3600) + minutes;

        document.querySelector('.iconic-clock-second-hand').setAttribute('transform', 'rotate('+360*(seconds/60)+',192,192)');
        document.querySelector('.iconic-clock-minute-hand').setAttribute('transform', 'rotate('+360*(minutes/3600)+',192,192)');
        document.querySelector('.iconic-clock-hour-hand').setAttribute('transform', 'rotate('+360*(hours/43200)+',192,192)');
    ]]>
    </script>
</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

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

JS

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

SVG: audio-spectrum-analyzer.svg

<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()">  
  <g id="eq-bars" height="320px" width="210px" fill="#010101" transform="">
    <rect x="00" y="150" height="20" width="10" />
    <rect x="20" y="140" height="40" width="10"  />
    <rect x="40" y="100" height="120" width="10"  />
    <rect x="60" y="120" height="80" width="10"  />
    <rect x="80" y="60" height="200" width="10"  />
    <rect x="100" y="20" height="280" width="10"  />
    <rect x="120" y="70" height="180" width="10"  />
    <rect x="140" y="120" height="80" width="10"  />
    <rect x="160" y="140" height="40" width="10"  />
    <rect x="180" y="150" height="20" width="10"  />
    <rect x="200" y="155" height="10" width="10"  />
  </g>

  <defs>
    <style type="text/css"><![CDATA[
      svg#audio-spectrum-analyzer {
        margin: 0 auto;
      }
    ]]></style>
  </defs>

  <script type="application/javascript"><![CDATA[
    var context;
    if (typeof AudioContext !== "undefined") {
      context = new AudioContext();
    }
    else if (typeof webkitAudioContext !== "undefined") {
      context = new webkitAudioContext();
    }
    else {
      throw new Error('AudioContext not supported. :(');
    }

    var eqHeight = document.querySelector('svg#audio-spectrum-analyzer > g#eq-bars').getAttribute('height').replace('px', '');
    var bars = document.querySelectorAll('svg#audio-spectrum-analyzer rect');

    var playing = false;

    var audioFileUrl = document.querySelector('svg#audio-spectrum-analyzer').getAttribute('data-audiofile');
    if (audioFileUrl === undefined) {
      throw new Error('Audio File not defined');
    }

    var soundSource;
    var fft;
    var fftSmoothing = 0.6;
    var fftMaxValue = 256;
    var samples = 128;
    var sampleIntervalID;
    var ampFactor = 1.25;
    var numBars = bars.length;
    var soundBuffer;


    var request = new XMLHttpRequest();
    request.open("GET", audioFileUrl, true);
    request.responseType = "arraybuffer";

    // Our asynchronous callback
    request.onload = function () {
      var audioData = request.response;

      // The Audio Context handles creating source
      // buffers from raw binary data
      soundBuffer = context.createBuffer(audioData, true /*make mono*/ );

    };
    request.send();

    function sampleAudio() {
      var data = new Uint8Array(fft.frequencyBinCount);
      fft.getByteFrequencyData(data);

      // Calc bin size to sum freqs into.
      // Carve off some of the high-end, lower energy bars (+2)
      var bin_size = Math.floor(data.length / (numBars + 2));

      // Sum up and average the samples into their bins
      for (var i = 0; i < numBars; ++i) {

        // Sum this bin
        var sum = 0;
        for (var j = 0; j < bin_size; ++j) {
          sum += data[(i * bin_size) + j];
        }

        // Duck some of the low-end power
        if (i === 0) {
          sum = sum * 0.75;
        }

        // Calculate the average frequency of the samples in the bin
        var average = sum / bin_size;
        var scaled_average = Math.max(10, ((average / fftMaxValue) * eqHeight) * ampFactor);

        // Update eq bar height
        bars[i].setAttribute('height', scaled_average);

        // Center bar
        bars[i].setAttribute('y', (eqHeight - scaled_average) / 2);
      }
    }

    function playSound() {
      // create a sound source
      soundSource = context.createBufferSource();

      // Add the buffered data to our object
      soundSource.buffer = soundBuffer;

      // Create the FFT
      fft = context.createAnalyser();
      fft.smoothingTimeConstant = fftSmoothing;
      fft.fftSize = samples;

      soundSource.connect(fft);
      fft.connect(context.destination);

      soundSource.noteOn(context.currentTime);

      // Start the FFT sampler
      sampleIntervalID = setInterval(sampleAudio, 30);

      playing = true;
    }

    function stopSound() {
      // Stop the FFT sampler
      clearInterval(sampleIntervalID);

      if (soundSource) {
        soundSource.noteOff(context.currentTime);
      }
      playing = false;
    }

    var toggleAudio = function () {
      if (!playing) {
        playing = true;
        playSound();
      }
      else {
        stopSound();
        playing = false;
      }
    }

    window.addEventListener('load', function () {
      window.toggleAudio = toggleAudio;
    }, false);

  ]]></script>

</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

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

JS

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

SVG: themometer.svg

<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"
 height="128px" viewBox="0 0 40 128" enable-background="new 0 0 40 128" xml:space="preserve">
    <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
            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
            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
            C31,4.935,26.065,0,20,0L20,0z"/>
    <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" />
    <circle class="iconic-thermometer-well iconic-thermometer-hot" cx="20" cy="108" r="12" />

    <animate
        id="shaft-animate"
        attributeName= "y2"
        begin= "indefinite"
        dur="1s"
        xlink:href="#iconic-anim-thermometer-shaft"
        fill="freeze"
        calcMode="spline"
        keySplines="0.42 0 0.58 1"
        keyTimes="0;1"
        restart="whenNotActive"
    />

    <script type="text/ecmascript">
    <![CDATA[

        var shaft = document.querySelector('#iconic-anim-thermometer-shaft');
        var well = document.querySelector('.iconic-thermometer-well');
        var yOrigin2 = parseFloat(shaft.getAttribute('y2'));
        var yOrigin1 = parseFloat(shaft.getAttribute('y1'));
        var yPos = yOrigin2;
        var tempClass;

        window.addEventListener('hashchange', function() {
        var hash = window.location.hash.substr(1);
        goto(hash);
        }, false);

        function goto(amount) {
            var shaftAnim = document.querySelector('#shaft-animate');

                shaft.setAttribute('y2', yPos)

        amount = parseFloat(amount)
        if( isNaN( amount ) ) return;

                if(amount>.9) {
                    tempClass="iconic-thermometer-hot";
                } else if(amount>.5) {
                    tempClass="iconic-thermometer-warm";
                } else if(amount>.2) {
                    tempClass="iconic-thermometer-chilly";
                } else {
                    tempClass="iconic-thermometer-cold";
                }

        amount = 1 - amount;
        amount = Math.min(Math.max(0, amount), 1);

        var ry = ( amount * ( yOrigin1-yOrigin2 ) ) + yOrigin2;

                /*
                 * Unfortunately, Safari doesn't make life easy on us. We need to remove and re-initialize
                 * the animation element for animations to start from the last end point.
                 */
        var ns =  shaftAnim.cloneNode(true);
        ns.setAttribute( 'from', yPos )
        ns.setAttribute( 'to', ry );

        shaftAnim.parentNode.replaceChild(ns, shaftAnim);

                well.setAttribute('class', 'iconic-thermometer-well ' + tempClass);
                shaft.setAttribute('class', 'iconic-thermometer-shaft ' + tempClass);
        ns.beginElement();
        yPos = ry;

        }
    ]]>
    </script>
</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-logo

Please consider backing Iconic on Kickstarter.

Related Posts
  • Web Design
    Case Studies
    How They Did It: CrowdpilotCrowdpilot thumb
    Crowdpilot's landing page shows off just how simple and awesome SVG is in combination with JavaScript and CSS animations. In this tutorial, you'll learn how to recreate Crowdpilot's diagonal message rotator and curtain, plus we'll talk a bit about "flat" design and what it means to design digitally native elements.Read More…
  • Game Development
    Implementation
    Write Once, Publish Everywhere With HaxePunk: Making a GamePreviewretinaimage
    You've probably had this experience before: you hear about an awesome game, but then you find out that it's only coming out on the one platform that you don't own. It doesn't have to be this way. In this tutorial, you will learn how to use Haxe to make a game in one development platform that can target multiple gaming platforms, including Linux, Mac, Windows, iOS, Android, and Flash.Read More…
  • Web Design
    HTML/CSS
    How to Animate Festive SVG Icons With CSSAnimated icons retina
    'Tis the season, so in this tutorial, I'll walk through creating some CSS animated, holiday-themed, SVG icons. There are some great icons on Iconmelon, a site which hosts many free vector icon sets for you to sink your teeth into. The icons I'm using come courtesy of designer Sam Jones. So grab yourself a cup of eggnog, pull your laptop up to the yule log, and let's gets started!Read More…
  • Design & Illustration
    Web Design
    SVG Files: From Illustrator to the WebSvg thumb retina
    Scalable Vector Graphics (SVG) is a vector image format which began life back in 1998. It was always developed with the web in mind, but only now has the web actually started to catch up. There's no denying its relevance today, so let's have a look at the basics of taking SVG from Illustrator to the web browser.Read More…
  • Code
    JavaScript & AJAX
    WebGL With Three.js: BasicsThreejs webgl retina preview
    3D graphics in the browser have been a hot topic ever since it was first introduced. But if you were to create your apps using plain WebGL, it would take ages. This is exactly why some really useful libraries have recently came about. Three.js is one of the most popular, and in this series I will show you how best to use it in order to create stunning 3D experiences for your users. Before we begin, I do expect you to have a basic understanding of 3D space before you start reading this tutorial, as I won't be explaining stuff like coordinates, vectors, etc.Read More…
  • Web Design
    General
    Manipulating SVG Icons With Simple CSSIconic retina1
    This article is the second 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 KickstarterRead More…