Tuesday, May 14, 2013

Some Tests with HTML5 and Scalable Vector Graphics

These are a few tests with HTML and SVG vector graphics. There are a variety of good resources available online on how to use SVG, particularly the Mozilla Development Network (MDN). This is still a relatively new area of web development, and as such not every browser supports the SVG standard, and many don't support the full standard. I'm going to be focusing primarily on the "nitty-gritty" details so I won't be using GUI tools such as InkScape to create SVG (a.k.a. I'm going to be writing raw SVG code in a text editor, or use Javascript to generate/modify SVG elements).

Some good SVG resources:

Basics for Embedded SVG

The great (or bad, depending on your perspective) thing about SVG is that it can be directly embedded into an HTML5 page. This is done using the svg element.

For the outermost SVG element, there are very few basic properties which are important:

You also have access to "general" HTML element attributes such as event handlers, style specifications, etc.

Like most other 2D computer graphics, SVG coordinates are generally defined going from the top-left of the view towards the bottom-right.

<svg id='svg_base' width='256' height='256' viewBox='0 0 1 1'>
 <rect id='block0' x='0' y='0' width='0.5' height='0.5' fill='red'/>
 <rect id='block1' x='0.5' y='0' width='0.5' height='0.5' fill='blue' />
 <rect id='block2' x='0' y='0.5' width='0.5' height='0.5' fill='green' />
 <rect id='block3' x='0.5' y='0.5' width='0.5' height='0.5' fill='black' />
</svg>

I'm not going to go over the basics of differing elements, see this MDN tutorial for more information.

Adding some Javascript

Modifying SVG is just as easy as modifying the DOM with Javascript. You can add object listener handles to each individual element, making manipulating items inside of an "SVG scene" relatively simple. Here's a similar SVG scene in which each rect object has event listeners attached so they can be moved around. I used a trick described here to alter the z-order of elements so the most recently selected element is moved to the front.

var svg_elem = document.getElementById('svg_base');

var selected_elem = null;
var selected_old_x = 0;
var selected_old_y = 0;
var selected_offset_x = 0;
var selected_offset_y = 0;

var mousemovehandler = function(evt)
{
 if(selected_elem !== null)
 {
  var curr_x = evt.clientX;
  var curr_y = evt.clientY;
  selected_elem.setAttribute('x', (curr_x - selected_offset_x) / Number(svg_elem.getAttribute('width')) + selected_old_x);
  selected_elem.setAttribute('y', (curr_y - selected_offset_y) / Number(svg_elem.getAttribute('height')) + selected_old_y);
 }
}; 

var mousedownhandler = function(source, evt)
{
 if(evt.button === 0)
 {
  selected_elem = source;
  selected_old_x = Number(selected_elem.getAttribute('x'));
  selected_old_y = Number(selected_elem.getAttribute('y'));
  selected_offset_x = evt.clientX;
  selected_offset_y = evt.clientY;
  svg_elem.appendChild(source);
  svg_elem.onmousemove = mousemovehandler;
 }
};

document.getElementById('block0').onmousedown = function(evt)
{
 mousedownhandler(document.getElementById('block0'), evt);
}
document.getElementById('block1').onmousedown = function(evt)
{
 mousedownhandler(document.getElementById('block1'), evt);
}
document.getElementById('block2').onmousedown = function(evt)
{
 mousedownhandler(document.getElementById('block2'), evt);
}
document.getElementById('block3').onmousedown = function(evt)
{
 mousedownhandler(document.getElementById('block3'), evt);
}

document.onmouseup = function(evt)
{
 if(evt.button === 0)
 {
  selected_elem = null;
  svg_elem.onmousemove = null;
 }
}

And moving the scene around instead of moving objects in the scene:

var svg_scene = document.getElementById('svg_scene');
var scene_selected = false;
var scene_offset_x = 0;
var scene_offset_y = 0;
var scene_old_x = 0;
var scene_old_y = 0;
// add several random rects
for(var i = 0; i < 1024; ++i)
{
 var x = 512 * (Math.random() - 0.5);
 var y = 512 * (Math.random() - 0.5);
 var height = 12 * Math.random() + 4;
 var width = 12 * Math.random() + 4;
 var elem = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
 elem.setAttribute('x', x);
 elem.setAttribute('y', y);
 elem.setAttribute('height', height);
 elem.setAttribute('width', width);
 elem.setAttribute('style', 'fill: none; stroke: black; stroke-width: 0.125px;');
 svg_scene.appendChild(elem);
}

var scene_panning = function(evt)
{
 if(scene_selected === true)
 {
  var curr_x = evt.clientX;
  var curr_y = evt.clientY;
  var scene_width = Number(svg_scene.getAttribute('width'));
  var scene_height = Number(svg_scene.getAttribute('height'));
  var box_dim = Number(svg_scene.getAttribute('viewBox').split(/\s/)[2]);
  svg_scene.setAttribute('viewBox', ((scene_offset_x - curr_x) * box_dim / scene_width + scene_old_x) + ' ' + ((scene_offset_y - curr_y) * box_dim / scene_height + scene_old_y) + ' ' + box_dim + ' ' + box_dim );
 }
};

svg_scene.onmousedown = function(evt)
{
 if(evt.button === 0)
 {
  scene_selected = true;
  scene_offset_x = evt.clientX;
  scene_offset_y = evt.clientY;
  var old_viewBox = svg_scene.getAttribute('viewBox').split(/\s/);
  scene_old_x = Number(old_viewBox[0]);
  scene_old_y = Number(old_viewBox[1]);
  svg_scene.onmousemove = scene_panning;
 }
};

document.onmouseup = function(evt)
{
 if(evt.button === 0)
 {
  svg_scene.onmousemove = null;
  scene_selected = false;
 }
}

Conclusion

The next step I want to take is creating a manipulatable scene, similar to what I've done in the past using C++ and the Qt framework. SVG graphics are amazingly powerful because as displays go to higher and higher resolutions, the amount of memory required to store raster graphics becomes extremely large. SVG graphics are much smaller because you're storing instructions on how the device can draw a scene. There are other advantages of using SVG as well.

No comments :

Post a Comment