Skip to content
This repository has been archived by the owner on Aug 22, 2019. It is now read-only.

Using SVG Badges

Sue Smith edited this page Aug 26, 2014 · 29 revisions

As an issuer, you can use the OBI tools with either PNG or SVG badges. SVG badges offer a range of potential advantages to issuers:

  • generate SVG badge images dynamically
  • within your site or application code
  • generate baked SVG badges dynamically
  • when you award badges to earners
  • small file sizes for your badge images
  • scaling up and down of badge images with no loss of quality
  • bake assertion data directly into your SVG markup
  • without relying on tools such as Mozilla Backpack baker or Open Badges Bakery
  • build templates you can adapt to suit different badges
  • within markup (with text editor / in code), without graphic design tools
  • animated / interactive displays of your badges more easily achievable
  • courtesy of HTML5, CSS3 and JavaScript coding, all of which runs in the browser
  • easier parsing of your baked badges by displayer implementations
  • extraction process is less labor-intensive than with baked PNGs

In this guide, we look at various aspects of using SVGs in a badging system, from building them dynamically to baking and displaying them.

Contents

SVG Basics

If you're new to SVGs, you should be able to pick up the basics pretty quickly as long as you have experience of using any markup language, for example HTML or XML. SVGs are XML-structured, so you can define the shapes and visual appearance of your badge image using tags. Check out the following simple example:

<svg version="1.1"
	baseProfile="full"
	width="100%" height="100%"
	viewBox="0 0 100 100"
	xmlns="http://www.w3.org/2000/svg">

	<rect width="100%" height="100%" fill="#ffff99" />

	<circle cx="50" cy="35" r="30" fill="#ff0000" fill-opacity="0.5" />
	<circle cx="35" cy="65" r="30" fill="#00ff00" fill-opacity="0.5" />
	<circle cx="65" cy="65" r="30" fill="#0000ff" fill-opacity="0.5" />
</svg>

When included in a Web page, this would occupy whatever space its containing element was given.

This is what the result looks like scaled to 100 x 100px:

daft-svg

SVGs can include complex shapes, paths, gradients, text and more - they can also be scaled up and down as required by the display context. See the SVG section on Mozilla Developer Network for a detailed guide to creating SVGs.

Generating SVGs Dynamically

As you can see from the example above, you could write SVGs out dynamically from your application or site code, defining templates using tag structures as outlines, tweaking the content to suit each badge.

You could even create a basic Web interface for building badge images using SVG templates. When an SVG is presented inline within an HTML page, your JavaScript code can interact with it as it would any other page element - including the SVG content, i.e. the shapes inside it.

You could therefore create a set of controls to let people manipulate the elements in an SVG, presenting the results within the same page, updating in real-time. When the user is finished editing, your page could either write the SVG for the badge image to a file or into the page.

Let's look at a simple example of implementing this so that you can see how achievable it is with a minimal amount of code. The final result looks like this:

svg-badger

For simplicity, we will explore how to allow the user to choose a badge shape and an icon shape, but you could extend this to let the user choose other elements such as color or text.

Let's use the following HTML page outline:

<!DOCTYPE html>
<html>
<head>
<style type="text/css">

</style>
<script type="text/javascript">

</script>
</head>
<body>

</body>
</html>

Let's add an area for the controls:

<div id="controls">
<strong>Badge Shape</strong><br/>
Badge <input type="radio" name="shape" value="badge" onchange="setShape()" checked="checked"/> 
Hexagon <input type="radio" name="shape" value="hexagon" onchange="setShape()"/><br/><br/>
<strong>Icon</strong><br/>
Star <input type="radio" name="icon" value="star" onchange="setIcon()" checked="checked"/> 
Diamond <input type="radio" name="icon" value="diamond" onchange="setIcon()"/><br/><br/>
<input class="button" type="button" value="CREATE BADGE" onclick="createBadge()" title="create this svg badge image"/>
</div>

The interface is very basic, but you could enhance it in any way you choose. After this div let's include a section for the SVG, with some shapes in it - these will display initially, but the user can configure them using the controls:

<div id="badge">
<svg version="1.1"
	baseProfile="full"
	width="100%" height="100%"
	viewBox="0 0 100 100"
	xmlns="http://www.w3.org/2000/svg">
	
	<defs>
	<linearGradient id="badgeGradient" x1="0%" y1="0%" x2="100%" y2="0%">
		<stop offset="0%" style="stop-color:#660099; stop-opacity:1" />
		<stop offset="50%" style="stop-color:#00a67e; stop-opacity:1" />
		<stop offset="100%" style="stop-color:#660099; stop-opacity:1" />
	</linearGradient>
	<radialGradient id="starGradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
		<stop offset="0%" style="stop-color:#ffff00;" />
		<stop offset="100%" style="stop-color:#ffaa00;" />
	</radialGradient>
	</defs>

	<polygon id="shape" points="10 10 10 75 50 95 90 75 90 10" fill="url(#badgeGradient)" />
	<path id="icon" fill="url(#starGradient)" d="M50 30 L 55 45 L 70 45 L 58 54 L 62 70 L 50 60 L 38 70 L 42 54 L 30 45 L 45 45 z"/>

</svg>
</div>

Don't worry if the SVG content includes elements you haven't seen before - there is nothing too complex in there. It includes a couple of gradient definitions, then two shapes using those gradients as fills. Notice also that the shapes have ID attributes - we will use these to refer to them in JavaScript.

Your own SVGs may be much more or much less complex than this!

For reference, the CSS code for the example page above is as follows:

body, html {background:#666666; font-family: sans-serif; color:#660000;}
#controls, #badge {padding: 1%; margin:1%; float:left;}
#controls {background:#ffff66; width:25%;}
#badge {background:#ffffff; width:40%;}
.button {background:#006600; color:#ffffff; border-radius:10px; border:1px solid #ffffff; padding:5px; font-weight:bold; cursor:pointer;}

Let's now look at the functions we listed to execute when the user chooses between the shapes. Starting with the badge icon shape, we called setIcon, so add it to the script section as follows:

function setIcon(){
	//find out which icon shape the user picked
	var selected=document.querySelector('input[name = "icon"]:checked').value;
	//the shape id is "icon" in the SVG
	if(selected==='star')
	document.getElementById('icon').setAttribute('d', 'M50 30 L 55 45 L 70 45 L 58 54 L 62 70 L 50 60 L 38 70 L 42 54 L 30 45 L 45 45 z');
	else if(selected==='diamond')
	document.getElementById('icon').setAttribute('d', 'M50 30 L 55 45 L 70 50 L 55 55 L 50 70 L 45 55 L 30 50 L 45 45 z');
}

In this case the shape is a path, with its outline defined via the d attribute. We set this to describe whichever shape the user picked. The setShape function is similar:

function setShape(){
	var selected=document.querySelector('input[name = "shape"]:checked').value;
	if(selected==='badge')
	document.getElementById('shape').setAttribute('points', '10 10 10 75 50 95 90 75 90 10');
	else if(selected==='hexagon')
	document.getElementById('shape').setAttribute('points', '25 10 75 10 90 50 75 90 25 90 10 50');
}

Don't worry too much about the complexity of defining your shapes using point values - if you prefer you can define a few shapes in a third-party application and copy the resulting markup into your code. You can also stick to the simpler shapes you can define without using point values, such as rectangles, circles and ellipses, or you can embed other images inside your SVGs.

In the examples above we alter the attributes of elements inside the SVG - you could also allow the user to add/ remove elements with createElement and removeChild, or you could simply toggle visibility of shapes in your SVG using setAttribute on the fill-opacity and/or stroke-opacity properties of the relevant SVG shapes. For example, if your SVG contained the following shape:

<rect id="bgshape" x="30" y="30" width="40" height="40" fill="#ff0000"/>

...and you had a control to toggle the shape on and off in your page:

Background shape <input type="checkbox" checked="checked" onclick="toggleShape(this)"/>

...you could use the following approach to turn visibility of the shape on and off:

function toggleShape(elem){
	if(elem.checked) 
		document.getElementById('bgshape').setAttribute('fill-opacity', '1.0');
	else
		document.getElementById('bgshape').setAttribute('fill-opacity', '0');
}

Since you can use JavaScript DOM manipulation on the content of your SVG badge, you can give the user detailed control over it with relatively straightforward coding. On the other hand, if you want your users to have a more complex level of configuration on the badge, you can do that too - potentially using only client-side scripting.

Once the user has chosen their badge SVG design, we call createBadge (when they click the button) - for demonstration you can write the SVG out to an alert:

function createBadge(){
	alert(document.getElementById('badge').innerHTML);
}

The markup inside the containing element represents the SVG image - your site or application could write this out to a file (see below) then use it for a badge by including it in the badge class JSON. The detail of how you do this will depend on the technologies you are using.

Writing to a File

In the example above we output the user-defined SVG to an alert for demonstration. Let's briefly look at how you could instead write it to a file. We could include the controls in a form element as follows:

<form action='/badge' method='post' enctype='multipart/form-data'>
<!--shape and other controls-->
</form>

The action would represent a server-side script we would send the form content to. Instead of the button via which we output the SVG in an alert, we could use a submit button to send the form data to the server-side script:

<input class="button" type="submit" value="CREATE BADGE" title="create this svg badge image" onclick="getSVG()"/>

You could use the user choices to build the SVG as an image file in a couple of different ways, for example by rebuilding the SVG code according to the user choices in the server-side script. In the example above, since we've been writing the updated SVG into the page each time the user changes the shapes, we could send the SVG markup itself to the server-side script. For example, your form could include a hidden field:

<input type="hidden" name="svgdata" id="svgdata"/>

Then you could write the updated SVG into this when the user clicks the submit button to create their badge. For example, the function we call when the submit button is clicked:

function getSVG(){
	document.getElementById('svgdata').value=document.getElementById('badge').innerHTML;
	return true;
}

The server-side script would then be able to access the SVG markup and write it to a file. The following example demonstrates how part of this process could look in a node.js app, but the task could be achieved in many server-side languages:

app.post('/badge', function(req, res) { 
	//svg badge sent from designer page - write it out
	var svg=req.body.svgdata; 
	var fileName='badge-name.svg';//generate or get from user
	var imagePath = __dirname+"/badges/"+fileName;
	fs.writeFile(imagePath, svg, function (err) {
  	if (err){ 
  		console.log(err);
  		res.send('Whoops! Image not created.');
	}
	else
		res.send('<html><head></head><body>'+
		'<div id="container"><h1>Badge Image Created</h1><p>'+
		'<a href="http://yoursite.com'+imagePath+'">- badge image -</a></p>'+
		'<div id="badge">'+svg+'</div></div></body></html>');
	});
});

In this example code we write the SVG out to an image file with an arbitrary name - in reality you could generate the file name programmatically, or let the user choose it. Then we include the SVG image and a link to the new file in the page. Here is the result with a little CSS:

svg-created

As an issuer, your next step would be including the SVG as the image for a badge class, by using the url as image in the badge class JSON.

As you can imagine, with a few additions to the example we explored above, you could create a simple interface for creating badge images as SVGs - and create badge classes using those SVGs as images.

All you need to issue badges is a badge class JSON file linking to a badge image, plus another JSON file including the issuer organization information. You can then award a badge to an earner by creating an assertion file in which you link to your badge class. By using SVGs for your badge images, you can automate much of the badge issuing process with a relatively small amount of code. As you will see below, the badge baking task is also easier when you use SVGs.

Baking SVGs

If you do use SVGs as your badge images, you can create baked badges when you award them to earners, as with PNG badges. However, with SVGs you can carry out the baking process without relying on the Open Badges bakery services (although they do handle SVGs).

With a minimal amount of code, you can bake the data for an issued badge (either signed or hosted assertion) into your SVGs within your own application or site code. This is because the process of baking an SVG badge simply involves including additional markup in the SVG file - so there's no need to rely on external libraries or tools.

Awarding your SVG Badge

Let's assume that you have your badge class defined as a JSON file on your server, plus an issuer organization JSON file linked from the badge class. If you need guidance implementing these steps, see Assertion Information for the Uninitiated. You could use a Web interface to allow people to create the badge class JSON as well as creating the SVG badge image.

To award the SVG badge to an earner, we need to create a badge assertion JSON file (or JSON Web Signature) and link it to the badge class. If you wanted to allow your users to enter email addresses to award the badge to, you could extend the node.js example we used above when writing the new badge image into the page:

res.send('<html><head></head><body>'+
	'<div id="container"><h1>Badge Image Created</h1><p>'+
	'<a href="http://yoursite.com'+imagePath+'">- badge image -</a></p>'+
	'<div id="badge">'+svg+'</div></div><h2>Award the badge to:</h2>'+
	'<form action="/award" method="post" enctype="multipart/form-data">'+
	'<input type="email" name="earner" required/>'+
	'<input type="hidden" value="badge-name" name="badge"/>'+
	'<input type="Submit" value="Award"/></form></div></body></html>');

This is the additional HTML output:

<h2>Award the badge to:</h2>
<form action="/award" method="post" enctype="multipart/form-data">
<input type="email" name="earner" required/>
<input type="hidden" value="badge-name" name="badge"/>
<input type="Submit" value="Award"/></form>

svg-award

The code sends the earner email plus badge name to another page/ script, named award. In a node.js app this may appear as follows:

app.post('/award', function(req, res) {
	var earner=req.body.earner;
	var badge=req.body.badge;
	//create the assertion and bake it into the badge

});

First we retrieve the earner email address and badge name. If we use the same name for the badge class JSON and badge image SVG files (with .json and .svg extensions), we can use this value to refer to both. To create the assertion, we define a file name and path:

var fileName='new-badge-award.json';//generate
var filePath=__dirname+"/awards/"+fileName;

This example is just for demonstration - in reality you may generate the assertion file name within your code. This would work where the issued badges are going to live in a folder named awards. Next we could build the badge assertion JSON, using the earner email address and badge class:

var assertion=JSON.stringify(
	{
		uid: 'abcde12345',
		recipient: {
			identity: earner,
			type: 'email',
			hashed: false,
			},
		badge: __dirname+"/badges/"+badge+".json",
		verify: {
			type: 'hosted',
			url: filePath
		},
		issuedOn: Date.now()
	},
	null, 4
);

Again, you may be generating the uid within your code. You could also hash the earner email address for additional security. For a hosted assertion, we could then write this JSON out to a file:

fs.writeFile(filePath, assertion, function (err) {
	if (err){
		console.log(err);
  		res.send('Whoops! Assertion not created.');
  	}
  	else {
  		//read badge and create baked badge file using hosted assertion details

  	}
});

Baking your SVG Badge

If the assertion file is successfully created, we have awarded the badge to the earner. However, an additional step we can take is baking the badge - which makes it more portable. In the last else, we can start by reading the SVG file in:

var badgeSVGPath=__dirname+"/badges/"+badge+".svg";
fs.readFile(badgeSVGPath, 'utf8', function (err, data) {
	if (err) {
		console.log(err);
		res.send('Whoops! No SVG Badge.');
	}
	else {
		//add assertion information to SVG XML

	}
});

Once we have the SVG markup, we can append the necessary information to it (inside the last else, using cheerio for XML manipulation in this case):

$ = cheerio.load(data, {
	xmlMode: true
});
$('svg').attr('xmlns:openbadges', 'http://openbadges.org');
$('svg').prepend('<openbadges:assertion verify="'+filePath+'"><![CDATA['+assertion+']]></openbadges:assertion>');

To bake an SVG badge, we add this attribute to the svg element:

xmlns:openbadges="http://openbadges.org"

...and this element inside the svg element, including the assertion JSON (simplified here):

<openbadges:assertion verify="http://yoursite.com/new-badge-award.json">
<![CDATA[
{"uid":"123456789abcdefghi987654321jklmnopqr",
...
}]]>
</openbadges:assertion>

The verify attribute points to the new assertion file, with the full assertion JSON included in the body of the element, wrapped in CDATA tags. See the below section if you plan on using signed assertions rather than hosted. For more on the structures we include when baking badges, see the Baking Specification.

Finally, we write the baked badge out as a new SVG image file:

var svgOut=$.xml();
var bakedSVGFilename='new-baked-badge.svg';//generate
var bakedPath=__dirname+"/awards/"+bakedSVGFilename;
fs.writeFile(bakedPath, svgOut, function(err) {
	if(err) res.send('Whoops! Badge not baked.');
	else res.send(svgOut);
	//email baked badge to earner

});

Again, you may wish to generate the file name in your code. We write the new SVG out to the page for demonstration - you may wish to email the badge to the earner at this point.

Signed Badges

The example above outlines how you could award and bake hosted SVG badges. If you use signed badges, your code will need to generate a JSON Web Signature for the assertion, hosting a public key corresponding to the private key you use for signing. The data you bake into the SVG is also different - the openbadges:assertion element should have no body, but the signature as verify attribute:

<openbadges:assertion verify="abcdefghi123456789.abcdefghi123456789abcdefghi123456789._abcdefghi123456789-abcdefghi123456789"/>

For more on signed assertions, see Creating Signed Assertions

Displaying Baked SVG Badges

Since you can interact with the content of an SVG in client-side scripts, this means that a Web page can retrieve the assertion data from a baked SVG badge. Let's look at an example of how a Web page displaying baked SVG badges could interact with the assertion data baked into it, presenting selected items within the page alongside the badge.

We will build a Web page in which a baked SVG badge is displayed in an object element. We will use jQuery to create an interactive effect on the badge - when the user hovers over it, we will show the date it was issued and the name from the badge class. To achieve this, we will need to extract the assertion data from the baked badge SVG image, then process it to retrieve the data items we want to display.

svg-display

We will assume that we have a baked badge SVG image file, a hosted badge assertion JSON file for the award, plus the badge class and issuer organization JSON files all hosted online. Let's start our display page with the following outline:

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body, html {background:#000000; font-family:sans-serif;}
.badgeHolder {width:500px; background:#ffffff; margin:auto; 
	position:relative; border-radius:15px;}
span {position:absolute; top:0; left:0; right:0; bottom:0; 
	background-color:rgba(255, 255, 255, 0.5); z-index:9999; 
	color:#660000; text-align:center; padding:3%; 
	border: 2px solid #ffffff; border-radius:15px; 
	margin:40% 25% 40% 25%;}
</style>
</head>
<body>
<div class='badgeHolder'>
<img svg="baked-svg-badge.svg" class="svg">
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
//show badge assertion data
</script>
</body>
</html>

We have an img element with the SVG file for a baked badge in it. We also have the jQuery script and a section for more functions. The page head has some CSS to make it match the image above - you can alter it in any way you choose.

In the script section, let's build the SVG content into the page so that we can manipulate the shapes inside it:

jQuery(document).ready(function() {
		//write the SVG into the page
		jQuery('img.svg').each(function(){
			var $img = jQuery(this);
			var imgURL = $img.attr('src');
			$(".badgeHolder").load(imgURL + " svg", function() {
				//get assertion data

		});
	});
});

The rest of the code will be placed in here. Remember we saw above that a baked SVG badge has an openbadges:assertion element containing the assertion JSON, inside CDATA tags as follows:

<svg version="1.1" baseProfile="full" width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:openbadges="http://openbadges.org">
<openbadges:assertion verify="http://yoursite.com/the-badge-award.json"><![CDATA[{ "uid": "abcde12345", "recipient": { "identity": "earner@example.org", "type": "email", "hashed": false }, "badge": "http://yoursite.com/badge-class.json", "verify": { "type": "hosted", "url": "http://yoursite.com/the-badge-award.json" }, "issuedOn": 1408406400 }]]></openbadges:assertion>
<!--shapes etc here-->
</svg>

Let's retrieve the assertion:

var assertion=JSON.parse(document.getElementsByTagName('openbadges:assertion')[0]
	.innerHTML.replace("<![CDATA[", "").replace("]]>", ""));

We find the openbadges:assertion element content and get rid of the CDATA sections so that we are left with the assertion JSON. It will have the following structure:

{
	"uid": "abcde12345",
	"recipient": {
		"identity": "earner@example.org",
		"type": "email",
		"hashed": false
	},
	"badge": "http://yoursite.com/badge-class.json",
	"verify": {
		"type": "hosted",
		"url": "http://yoursite.com/the-badge-award.json"
	},
	"issuedOn": 1408406400
}

We have this in the assertion variable, so let's build the issue date and badge name into the page, displaying them when the user hovers over the badge image. Let's start with the date the badge was issued, which is included as a UNIX timestamp in the issuedOn field:

//get the assertion issued date
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var badgeDate = new Date((assertion.issuedOn)*1000);
var issuedDate = badgeDate.getDate() +' '+months[badgeDate.getMonth()]+' '+badgeDate.getFullYear();

We retrieve the field from the assertion object and prepare the date to display in a readable way. Next let's get the badge name, which we can get from the badge class JSON. Note that the current assertion specification indicates that the badge class should be included as a URL in the badge field, however proposals under discussion indicate a badge class object included inline instead.

Since the assertion we are dealing with includes the badge class URL, we need to load that file and retrieve the badge name from there. We can do so in various ways, for example using JSONProxy - downloaded and included in the page as follows:

<script src="jsonp.js"></script>

Then we can use the jsonp function after the point in the script where we retrieved the issue date:

var badgeName; 
$.jsonp({
	url: assertion.badge, 
	success: function(data){
	//build data into the page
	
	}
});

We load the badge class file from the URL in the badge field in the assertion.

Inside the success function, we can now retrieve the name and build it, along with the date, into the page display as a hover effect on the badge image:

badgeName = data.name; 
//hover effect
$(".badgeHolder").hover(function() { 
	//overlay
	$(this).append(
		$("<span>Badge: <strong>"+badgeName+
		"</strong><br/>Issued: <strong>"+issuedDate+
		"</strong></span>"));
	}, function() {
		$(this).find("span:last").remove();
});

The containing badgeHolder element will have the hover effect applied to it. When the user hovers over it, we build a span element including the badge name and issue date, which will disappear when the mouse moves away.

You can see a demo of this trivial example here:

We build a couple of elements into the display, but as you can imagine, you could display any of the data items in the assertion, the badge class and even the issuer organization JSON (which is included by URL in the badge class).

Displaying Signed Badges

The example above demonstrates how you might approach displaying baked badges for hosted assertions. If you are using SVG badges with signed assertions baked into them, you will need to decode the assertion from the signature stored in the openbadges:assertion verify attribute.

Depending on the source you are retrieving your baked badges for display from, you may also wish to carry out verification steps - see Verifying Badges for Display, which outlines the process for both hosted and signed badges.

Animated and Interactive SVG Badges

We have looked at how you could enhance the appearance of a baked SVG badge by displaying parts of the embedded assertion data alongside it, but you could potentially take your display implementation even further.

Since you can use JavaScript DOM manipulation and CSS on the elements inside an SVG, you could create animated and/or interactive effects with the shapes in the SVG. You could even build the assertion data into your display pages as part of the SVG itself, rather than as an overlay, which we did above.

Let's look at another example page in which we manipulate the content of the SVG itself and apply animated interactive effects on it. We'll start by manipulating the content of the SVG and using CSS3 animation on hover. After that we'll look at SVG animation.

Let's use an img element to show the SVG in the page again:

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body, html {background:#000000; font-family:sans-serif;}
.badgeHolder {width:500px; background:#ffffff; margin:auto; border-radius:15px;}
</style>
</head>
<body>
<div class='badgeHolder'>
<img class="svg" src="baked-svg-badge.svg"/>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="jsonp.js"></script>
<script type="text/javascript">
//write the SVG into the page
jQuery('img.svg').each(function(){
	var $img = jQuery(this);
	var imgURL = $img.attr('src');
	$(".badgeHolder").load(imgURL + " svg", function() { 
		//get assertion data
		var assertion=JSON.parse(document.getElementsByTagName('openbadges:assertion')[0]
			.innerHTML.replace("<![CDATA[", "").replace("]]>", "")); 
		//get the assertion issued date
		var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
		var badgeDate = new Date((assertion.issuedOn)*1000);
		var issuedDate = badgeDate.getDate() +' '+months[badgeDate.getMonth()]+' '+badgeDate.getFullYear();
		//get badge name
		var badgeName;
		$.jsonp({
			url: assertion.badge, 
			success: function(data){
				badgeName = data.name; 
				//write into shape

			}
		});
	});
});
</script>
</body>
</html>

We again use jQuery and JSONProxy to apply the interactive effect and include some initial CSS code, which we will add to. This time we'll write the badge name and date into the SVG itself. We'll use a text element to append these pieces of text information as the last element inside the SVG, which will make it appear on top of the existing elements:

$('svg').append("<text x='50' y='50' text-anchor='middle' font-size='5' font-weight='bold' fill-opacity='0'><tspan class='title'>"+badgeName+"</tspan><tspan dy='10' class='date'>"+issuedDate+"</tspan></text>");
$("body").html($("body").html());//refresh to display amendments

We use staggered tspan elements for the two text strings. Initially the text isn't seen - we will show it on hover. We'll use the class names to style the two sections of text - with the following added to the CSS for general styling of the new elements:

tspan.title {fill:#660000;}
tspan.date {fill:#006600; font-style:italic;}

Let's use CSS rules to style the new SVG elements, as well as adjusting the properties of the existing shapes:

svg path, svg polygon, svg text {
	transition: 1s ease-in-out;
}
svg:hover path {
	fill-opacity: 0.3;
	stroke:#ffffff;
}
svg:hover polygon {
	fill-opacity: 0.5;
}
svg:hover text {
	fill-opacity: 1.0;
}

As you can imagine, you would need to know something about the content of an SVG badge in order to achieve this. In this case we apply properties to any path and polygon elements in the shape, as well as the text element we know is there (because we just added it 😉). You could theoretically keep your CSS as generic as possible so that it would work for a wide variety of SVG badges regardless of the shapes they contain.

The effect defined above fades the existing SVG content on hover, applies a stroke and fades the new text element into view, with a transition making the effect elapse over a specific duration.

In supporting browsers, you could enhance the effect to slide the text in with a translate transform, by starting the text below the visible shape using its y attribute:

$('svg').append("<text x='50' y='110' text-anchor='middle' font-size='5' font-weight='bold' fill-opacity='0'><tspan class='title'>"+badgeName+"</tspan><tspan dy='10' class='date'>"+issuedDate+"</tspan></text>");

Then you could add a translate transform to the text hover section in the CSS:

svg:hover text {
	fill-opacity: 1.0;
	transform:translateY(-60px);
}

Although it will only work in certain browsers, this slides the text up into view on hover.

SVG effect start SVG effect midway SVG effect end

You can see a demo of the effect here: http://suesmith.github.io/svg-display-interactive.html

As you can imagine, you could apply many different CSS interaction and animation effects to the content of the SVG badge, including keyframe animations. Next up we'll look at SVG animation (rather than CSS).

SVG Animation

We have seen a little of how you could use CSS animations when displaying baked SVG badges - let's look at how you can use the SVG animation elements animate and animateTransform to apply more effects.

We'll start with the same approach as before for writing the badge into the page:

<!DOCTYPE html>
<html>
<head>
<style type="text/css">

</style>
</head>
<body>
<div class='badgeHolder'>
<img class="svg" src="baked-svg-badge.svg"/>
</div>
<div id="stuff"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="jsonp.js"></script>
<script type="text/javascript">
jQuery(document).ready(function() {

	//put svg inline
	jQuery('img.svg').each(function(){
		var $img = jQuery(this);
		var imgURL = $img.attr('src');
		$(".badgeHolder").load(imgURL + " svg", function() {
		
			//get assertion data
			var assertion=JSON.parse(document.getElementsByTagName('openbadges:assertion')[0]
				.innerHTML.replace("<![CDATA[", "").replace("]]>", "")); 
			//get the assertion issued date
			var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
			var badgeDate = new Date((assertion.issuedOn)*1000);
			var issuedDate = badgeDate.getDate() +' '+months[badgeDate.getMonth()]+' '+badgeDate.getFullYear();
			
			//get badge name
			var badgeName; 
			$.jsonp({
				url: assertion.badge, 
				success: function(data){
					badgeName = data.name; 
					//build name and date into SVG
					$('svg').append("<text x='50' y='110' text-anchor='middle' font-size='5' font-weight='bold' fill-opacity='0'>"+
						"<tspan class='title'>"+badgeName+"</tspan>"+
						"<tspan dy='10' class='date'>"+issuedDate+"</tspan>"+
						"</text>");
					$("body").html($("body").html());//refresh to display amendments		

				}
			});
	});
});
});
</script>

</body>
</html>

To show how you can combine CSS and SVG animation, we'll use a few of the CSS properties we used last time:

body, html {background:#000000; font-family:sans-serif;}
.badgeHolder {width:500px; background:#ffffff; margin:auto; border-radius:15px;}
svg path, svg polygon {
	transition: 2s ease-in-out;
}
svg:hover path {
	fill-opacity: 0.7;
}
svg:hover polygon {
	fill-opacity: 0.4;
}
tspan.title {fill:#660000;}
tspan.date {fill:#006600; font-style:italic;}

We don't include the translate transform because we will implement a different effect to slide the badge name and date into view. We'll change the line in which we build the name and date into the SVG to achieve a slightly different end result. Let's make the text slide up and then down, in a kind of bounce, by adding an animate element inside the text element. This will be the result:

<text x="50" y="110" text-anchor='middle' font-size='5' font-weight='bold' fill-opacity='0.0'>
	<tspan class='title'>SVG Coder</tspan>
	<tspan dy='10' class='date'>14 August 2014</tspan>
	<animate attributeType="XML"
		attributeName="y"
		values="110;30;50"
		dur="2s" 
		begin="badge.mouseenter"
		fill="freeze"/>
</text>

The y attribute is given three values, the first below the visible badge area, the second near the top and the third in the middle. We start the animation when the mouse enters an element with "badge" as its ID - let's make that the SVG element by adding this attribute (inside the jsonp success function):

$('svg').attr("id", "badge");

We will also fade the text into view, so our text element will contain another animate element:

<animate attributeType="XML"
	attributeName="fill-opacity"
	from="0.0" to="1.0"
	dur="2s" 
	begin="badge.mouseenter"
	fill="freeze"/>

This will elapse at the same time and over the same duration as the animation for the text sliding up the badge. So that the effect is reversed when the user moves their mouse off the badge, we will also include another two animate elements:

<animate attributeType="XML"
	attributeName="y"
	values="50;30;110"
	dur="2s" 
	begin="badge.mouseleave"
	fill="freeze"/>
<animate attributeType="XML"
	attributeName="fill-opacity"
	from="1.0" to="0.0"
	dur="2s" 
	begin="badge.mouseleave"
	fill="freeze"/>

Let's add these to the SVG by altering the line in which we add the text element:

$('svg').append("<text x='50' y='110' text-anchor='middle' font-size='5' font-weight='bold' fill-opacity='0'>"+
	"<tspan class='title'>"+badgeName+"</tspan>"+
	"<tspan dy='10' class='date'>"+issuedDate+"</tspan>"+
	"<animate attributeType='XML'"+
		"attributeName='y'"+
		"values='110;30;50'"+
		"dur='2s'"+
		"begin='badge.mouseenter'"+
		"fill='freeze'/>"+
	"<animate attributeType='XML'"+
		"attributeName='fill-opacity'"+
		"from='0.0' to='1.0'"+
		"dur='2s'"+
		"begin='badge.mouseenter'"+
		"fill='freeze'/>"+
	"<animate attributeType='XML'"+
		"attributeName='y'"+
		"values='50;30;110'"+
		"dur='2s'"+
		"begin='badge.mouseleave'"+
		"fill='freeze'/>"+
	"<animate attributeType='XML'"+
		"attributeName='fill-opacity'"+
		"from='1.0' to='0.0'"+
		"dur='2s'"+
		"begin='badge.mouseleave'"+
		"fill='freeze'/>"+
	"</text>");

To show the level of manipulation your scripts can have over SVG elements, let's also add an animation to another element. When the user clicks the path shape, let's rotate it using the following animateTransform element:

<animateTransform attributeType="XML"
	attributeName="transform"
	type="rotate"
	from="0 50 50" to="360 50 50"
	dur="2s" 
	begin="click"
	fill="freeze"/>

Add it in your script, after we appended the text element:

$('path').append("<animateTransform attributeType='XML'"+
	"attributeName='transform'"+
	"type='rotate'"+
	"from='0 50 50' to='360 50 50'"+
	"dur='2s'"+
	"begin='click'"+
	"fill='freeze'/>");

You can see a demo of the effect here: http://suesmith.github.io/svg-display-animated.html

The path shape spins when the user clicks it and the assertion data slides into view when the user rolls the mouse over the badge area.

Display Spin

The example we've built here is a trivial one, but you can begin to see how you could present baked SVG badges - including the assertion data - within animated and interactive pages, all within your client side code. For more on SVG animation, see the MDN section.

Clone this wiki locally