header yorick

How to create an animated sonar display in SVG

Here you can find a stepwise explanation of how to create an animated sonar in the SVG format.

Note: SVG animations do not work in Internet Explorer, use Chrome or Firefox instead.

The basic structure

First the basics. We are creating the SVG manually, so we have to declare a couple of parameters first. We declare the SVG namespace with xlink (xmlns:xlink) because we will use it later. We define an ID, height and width and finally set the viewBox such that we have a centered coordinate system, which is useful because we will work with circles and angular coordinates later on.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<svg
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   version="1.1"
   id="sonar"
   width="100%"
   height="100%"
   viewBox="-55 -55 110 110">
   
</svg>

First we create a black background using a rectangle. Then we create a circle to define the boundary of our display. The circle, with radius 50, will be centered around the origin of our coordinate system and have a dark green (#012501) background . The edge of the circle and, later on, all foreground elements, will be in lime (#00FF00).

<rect id="bg" x="-55" y="-55" width="110" height="110" style="fill:black"/>

<circle id="circle" cx="0" cy="0" r="50" style="stroke:lime;stroke-width:0.5;fill:#012501"/>

The spinning probe

To make the characteristic spinning sonar probe, we first create an arc that spans a quarter of a circle.

<path id="arc" d="M0 -50 A50 50 0 0 1 50 0 L0 0 z" style="stroke:none;fill:lime"/>

Now we define a transparency gradient that we use to fill the arc, to make it look more realistic. Since it is not possible to define angular gradients in SVG, we use a linear gradient that runs diagonally as an approximation. (For a more refined simulation of angular gradients, see this link.) Note that the actual start of the gradient is set to 0.3 to slightly 'push' the gradient towards the right: this is to create a more visually appealing spinning animation later on.

<defs>
   <linearGradient id="Gradient1" x1="0" x2="1" y1="0" y2="1">
      <stop offset="0" stop-color="#012501" stop-opacity="0"/>
      <stop offset="0.3" stop-color="#012501" stop-opacity="0"/>
      <stop offset="1" stop-color="lime"/>
   </linearGradient>
</defs>

<path id="arc" d="M0 -50 A50 50 0 0 1 50 0 L0 0 z" style="stroke:none;fill:url(#Gradient1)"/>

Finally, we still have to make the spinner actually spin, so for that we use a basic animated rotation, centered around the middle of the display. The animation takes 5 seconds to rotate over 360 degrees and is looped infinitely.

<path id="arc" d="M0 -50 A50 50 0 0 1 50 0 L0 0 z" style="stroke:none;fill:url(#Gradient1)">
   <animateTransform
      attributeType="xml"
      attributeName="transform"
      type="rotate"
      from="0 0 0"
      to="360 0 0"
      dur="5"
      repeatCount="indefinite"
   />
</path>

Some static elements

To make the display a bit more visually appealing, we add some more elements. Nothing really difficult here, just some basic line definitions and transformations/translations to enhance the display.

<defs>
   <line id="angle" x1="0" x2="0" y1="-51" y2="-49" style="stroke:lime;stroke-width:0.5"/>
   <line id="hor" x1="-1" x2="1" y1="0" y2="0" style="stroke:lime;stroke-width:0.5"/>
   <line id="ver" x1="0" x2="0" y1="-1" y2="1" style="stroke:lime;stroke-width:0.5"/>
</defs>

<circle id="circle" cx="0" cy="0" r="40" style="stroke:lime;stroke-width:0.5;fill:none"/>
<circle id="circle" cx="0" cy="0" r="30" style="stroke:lime;stroke-width:0.5;fill:none"/>
<circle id="circle" cx="0" cy="0" r="20" style="stroke:lime;stroke-width:0.5;fill:none"/>
<circle id="circle" cx="0" cy="0" r="10" style="stroke:lime;stroke-width:0.5;fill:none"/>

<use xlink:href="#angle"/>
<use xlink:href="#angle" transform="rotate(10)"/>
<use xlink:href="#angle" transform="rotate(20)"/>
<use xlink:href="#angle" transform="rotate(30)"/>
<use xlink:href="#angle" transform="rotate(40)"/>
<!-- etc... -->

<line id="horizontal" x1="-50" x2="50" y1="0" y2="0" style="stroke:lime;stroke-width:0.5"/>
<line id="vertical" x1="0" x2="0" y1="-50" y2="50" style="stroke:lime;stroke-width:0.5"/>

<use xlink:href="#hor" y="-45"/>
<use xlink:href="#hor" y="-35"/>
<use xlink:href="#hor" y="-25"/>
<!-- etc... -->

<use xlink:href="#ver" x="-45"/>
<use xlink:href="#ver" x="-35"/>
<use xlink:href="#ver" x="-25"/>
<!-- etc... -->

The blinking dots

Now that our display is starting to look like one, it is time to add perhaps the most crucial element: the blinking dots. After all, what would our sonar be good for if it was unable to pick up any signals from nearby vessels!

Because we want to (be able to) create multiple dots and because we need to use some math to calculate when a dot should light up, we will create the dots with a Javascript function (setDot). The function takes two parameters: the x and y coordinates of the dot to be placed. We use the SVG's ID to access its DOM and define the right namespace, so we can add elements and animations to it later. First, we check if the given coordinates fall within the display circle. If that is the case, we calculate the angle the coordinates make with the positive x-axis. Because we know the spinner starts from the positive x-axis and takes 5 seconds to make a 360° turn, we can derive the time at which the spinner passes the dot and use it as the starting time of the blinking animation. With that information, we can build the circle element and make it blink, by adding an animation for the fill-opacity attribute.

<script type="text/javascript">
<![CDATA[
   function setDot(x,y) {
      var svg = document.getElementById("sonar");
      var ns = "http://www.w3.org/2000/svg";
      
      var dist = Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
      if (dist < 50) {
         var ang = Math.atan2(y,x);
         var b = (5*ang)/(2*Math.PI);
         if (b < 0 ) { b = b + 5; }
		 
         var dot = document.createElementNS(ns, "circle");
         dot.setAttribute("cx", x);
         dot.setAttribute("cy", y);
         dot.setAttribute("r", 1);
         dot.setAttribute("stroke", "none");
         dot.setAttribute("fill", "lime");
         dot.setAttribute("fill-opacity", 0);
         svg.appendChild(dot);
         
         var animation = document.createElementNS(ns, "animate");
         animation.setAttribute("attributeName","fill-opacity");
         animation.setAttribute("from", 1);
         animation.setAttribute("to", 0);
         animation.setAttribute("begin", b);
         animation.setAttribute("dur", 5);
         animation.setAttribute("values", "1;0;0");
         animation.setAttribute("keyTimes", "0;0.5;1");
         animation.setAttribute("repeatCount", "indefinite");
         dot.appendChild(animation);
      }
   }
]]>
</script>

Now that we have defined the function to create and animate dots, we can call it multiple times to put the dots on the display. Below are four manually chosen coordinates, but of course you can put more or less dots on the screen or write a short loop to create dots at random coordinates. Note that these function calls have to be placed after all other content. This is to prevent the dots from appearing before (and thus behind) earlier elements.

<script type="text/javascript">
   setDot(25,25);
   setDot(10,-30);
   setDot(15,-35);
   setDot(-40,10);
</script>

In this example we use static dots, but of course you can add an extra animation to have a dot move across the display. However, if you want to do this, keep in mind that you might have to precalculate the trajectory and set some more values and keyTimes to the fill-opacity animation in order to make the dots appear at the right times.

Adding the sound effect

As an (animated) picture, the sonar display is now complete. However, since you can do so much more with SVGs, in this last part I will explain how to add that little bit of extra flavour to your SVG in the form of sound effects. Now remember, this is all optional, so if you don't like to surprise people with sudden sounds coming from their internets, you can just leave this part out.

If you do like sound effects, the first step is to find a suitable sound for your sonar. I used the one below, which I found here, but if you search for something along the lines of 'sonar ping', I'm sure you can find dozens that you might like better.

To create sonar ping sounds we will add an <audio> element to our SVG. The code below is added to the setDot function that we defined earlier. Since the <audio> element is not part of the standard SVG specification, we use the xhtml namespace to create it. We set the source and type attributes and append the element to the SVG DOM. We then use setTimeout to play the audio at the correct time, and a nested setInterval to loop it.

var audions = "http://www.w3.org/1999/xhtml";
			
var audio = document.createElementNS(audions, "audio");
audio.setAttribute("src", "SONAR.mp3");
audio.setAttribute("type", "audio/mpeg");
svg.appendChild(audio);
setTimeout( function(){
   audio.play();
   setInterval( function(){
      audio.currentTime = 0;
      audio.play();
   }, 5000);
}, b*1000-500);		// -500 for the small delay in the audio

To view the final result of the SVG with sound, click the button on the left. For the complete source code, click the button on the right (or inspect the SVG).