Translate from DOM to SVG Coordinates and Back Again

View: 814    Dowload: 0   Comment: 0   Post by: hanhga   Category: HTML-CSS template   Fields: Other

All the cool kids are using Scalable Vector Graphics. SVGs are great until you want to mix DOM and vector interactions — then life becomes more complicated.

SVGs have their own coordinate system. It is defined via the viewbox attribute, e.g. viewbox="0 0 800 600"which sets a width of 800 units and a height of 600 units starting at (0, 0). If you position this SVG in an 800×600 pixel area, each SVG unit maps directly to a screen pixel.

However, the beauty of vector images is they can be scaled to any size. Your SVG could be scaled in a 400×300 space or even stretched beyond recognition in a 100×1200 space. Adding further elements to an SVG becomes difficult if you don’t know where to put them.

(SVG coordinate systems can be confusing – Sara Soueidan’sviewport, viewBox and preserveAspectRatio article describes the options.)

Simple Separated SVG Synergy

You may be able to avoid translating between coordinate systems entirely.

SVGs embedded in the page (rather than an image or CSS background) become part of the DOM and can be manipulated in a similar way to other elements. For example, given a basic SVG with a single circle:

<svg id="mysvg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet">
  <circle id="mycircle" cx="400" cy="300" r="50" />

we can apply CSS effects:

circle {
  stroke-width: 5;
  stroke: #f00;
  fill: #ff0;

circle:hover {
  stroke: #090;
  fill: #fff;

and attach event handlers to modify their attributes:

var mycircle = document.getElementById('mycircle');

mycircle.addEventListener('click', function(e) {
  console.log('circle clicked - enlarging');
  mycircle.setAttributeNS(null, 'r', 60);
}, false)

SVG to DOM Coordinate Translation

What if we want to overlay a DOM element on top of an SVG item, e.g. a menu or information box on a map? Again, because our HTML-embedded SVG elements form part of the DOM we can use the fabulous getBoundingClientRect() method to return all dimensions in a single call. Open the console in the example above to reveal the clicked circle’s new attributes following a radius increase.

Element.getBoundingClientRect() is supported in all browsers and returns an DOMrect object with the following properties in pixel dimensions:

  • .x and .left – x-coordinate, relative to the viewport origin, of the left side of the element
  • .right – x-coordinate, relative to the viewport origin, of the right side of the element
  • .y and .top – y-coordinate, relative to the viewport origin, of the top side of the element
  • .bottom – y-coordinate, relative to the viewport origin, of the bottom side of the element
  • .width – width of the element (not supported in IE8 and below but is identical to .right minus .left)
  • .height – height of the element (not supported in IE8 and below but is identical to .bottom minus .top)

All coordinates are relative to the browser viewport and will therefore change as the page is scrolled. The absolute location on the page can be calculated by adding window.scrollX to .left and window.scrollY to .top.

DOM to SVG Coordinate Translation

This is the tricky part. Presume you click an SVG and want to create or position an SVG element at that point. The event handler object will give you the DOM .clientX and .clientY pixel coordinates but these must be translated to SVG units.

It’s tempting to think you can calculate the x and y coordinates of an SVG point by applying a multiplication factor to the pixel location. For example, if a 1000 unit-width SVG is placed in a 500px width container, you can multiply any cursor x coordinate by two to get the SVG location. It rarely works!…

  • There is no guarantee the SVG will fit exactly into your container.
  • If the page or element dimensions change – perhaps in response to the user resizing the browser – the x and y factors must be re-calculated.
  • The SVG could be transformed in either 2D or 3D space.
  • Even if you overcome these hurdles, it never quite works as you expect. There’s often a margin of error.

Fortunately, SVGs provide their own matrix factoring mechanisms to translate coordinates. The first step is to create a point on the SVG using the createSVGPoint() method and pass in our screen x and y coordinates:

var svg = document.getElementById('mysvg'),
    pt = svg.createSVGPoint();

pt.x = 100;
pt.y = 200;

We can then apply a matrix transformation. That matrix is created from an inverse of the SVG’s (under-documented!).getScreenCTM() method which maps SVG units to screen coordinates:

var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());

svgP now has .x and .y properties which provide the SVG coordinate location.

We can therefore place a circle at a point clicked on an SVG canvas:

var svg = document.getElementById('mysvg'),
    NS = svg.getAttribute('xmlns');

svg.addEventListener('click', function(e) {
  var pt = svg.createSVGPoint(), svgP, circle;
  pt.x = e.clientX;
  pt.y = e.clientY;
  svgP = pt.matrixTransform(svg.getScreenCTM().inverse());

  circle = document.createElementNS(NS, 'circle');
  circle.setAttributeNS(null, 'cx', svgP.x);
  circle.setAttributeNS(null, 'cy', svgP.y);
  circle.setAttributeNS(null, 'r', 10);
}, false);

The createElementNS() and setAttributeNS() methods are identical to the standard DOM createElement()and setAttribute() methods except they specify an XML namespace URI. In other words, they act on the SVG rather than the HTML. setAttributeNS() can be passed a null namespace URI because it is directly manipulating an SVG element.

DOM to Transformed SVG Element Coordinates

There’s a further complication. What if we click on an SVG element which has been transformed in some way? It could be scaled, rotated or skewed which would affect our resulting SVG coordinate. For example, this <g> layer is 4x larger than the standard unit so coordinates will be one quarter those of the containing SVG:

<g id="local" transform="scale(4)">
  <rect x="50" y="50" width="100" height="100" />

The resulting rectangle appears to be 400 units in size at position 200, 200.

Fortunately, the .getScreenCTM() can be used on any SVG element and the resulting matrix considers all transformations. We can therefore create a simple svgPoint translation function:

var svg = document.getElementById('mysvg'),
    local = svg.getElementById('local');

console.log( svgPoint(svg, 10, 10) ); // returns x, y
console.log( svgPoint(local, 10, 10) ); // = x/4, y/4

// translate page to SVG co-ordinate
function svgPoint(element, x, y) {
  var pt = svg.createSVGPoint();

  pt.x = x;
  pt.y = y;

  return pt.matrixTransform(element.getScreenCTM().inverse());


Translate from DOM to SVG Coordinates and Back Again

All the cool kids are using Scalable Vector Graphics. SVGs are great until you want to mix DOM and vector interactions — then life becomes more complicated.

Posted on 01-09-2016 


To comment you must be logged in members.

Files with category

File suggestion for you
File top downloads
Codetitle - library source code to share, download the file to the community
Copyright © 2018. All rights reserved. codetitle Develope by Vinagon .Ltd