SVG and the DOM

Dr. Greg Bernstein

Updated September 12, 2021

SVG and the DOM

HTML, SVG, and the DOM

Consider part of the file guitarCSS.html

<body>
    <h1>Guitar Fretboard Diagrams</h1>
    <p>Here we demonstrate a basic E major Guitar chord drawn
    with SVG and styled with CSS. </p>

    <svg width="300" height="200" xmlns="http://www.w3.org/2000/svg">
        <line class="GString"  x1="10" x2="10" y1="10" y2="170" />
        <line class="GString"  x1="30" x2="30" y1="10" y2="170" />
        <line class="GString"  x1="50" x2="50" y1="10" y2="170" />
        <line class="GString"  x1="70" x2="70" y1="10" y2="170" />
        <line class="GString"  x1="90" x2="90" y1="10" y2="170" />
        <line class="GString"  x1="110" x2="110" y1="10" y2="170" />

        <line class="GFret"  x1="10" x2="110" y1="10" y2="10" />
        <line class="GFret"  x1="10" x2="110" y1="50" y2="50" />
        <line class="GFret"  x1="10" x2="110" y1="90" y2="90" />
        <line class="GFret"  x1="10" x2="110" y1="130" y2="130" />
        <line class="GFret"  x1="10" x2="110" y1="170" y2="170" />

        <circle cx="30" cy="70" r="10" />
        <circle cx="50" cy="70" r="10" />
        <circle cx="70" cy="30" r="10" />
    </svg>
</body>

Try some DOM queries on the SVG

  • document.querySelectorAll('circle')
  • document.querySelectorAll('line.GString') with class

Results

Work just like HTML DOM queries!

SVG queries

SVG Element creation

SVG Elements are DOM Elements

From MDN SVGElement Reference

All of the SVG DOM interfaces that correspond directly to elements in the SVG language derive from the SVGElement interface.

SVG element inheritance diagram

SVG Element Creation

SVG Attributes

  • SVG usually have a lot of attributes to set. Use setAttribute()

  • Example:

square = document.createElementNS("http://www.w3.org/2000/svg", "rect");
square.setAttribute("x", 20);
square.setAttribute("y", 50);
square.setAttribute("width", 150);
square.setAttribute("height", 60);

Random Square Generation

Script within randomSquares.html

var mySVG = document.getElementById("MyDrawing");
var maxSize = 50,
  maxX = 500,
  maxY = 300;

function randomSquare() {
  let x = Math.random()*(maxX - maxSize);
  let y = Math.random()*(maxY - maxSize);
  let width = Math.random()*maxSize;
  let square = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  square.setAttribute("x", x);
  square.setAttribute("y", y);
  square.setAttribute("width", width);
  square.setAttribute("height", width);
  let colorStr = `rgb(${255*Math.random()}, ${255*Math.random()}, ${255*Math.random()})`;
  square.setAttribute("fill", colorStr);
  square.setAttribute("fill-opacity", 0.7);
  return square;
}

for (let i = 0; i < 30; i++) {
  mySVG.appendChild(randomSquare());
}

Random Squares Rendered

Random square output

SVG and DOM Events

SVG DOM Events

SVGElement inherits from EventTarget

So supports a wide variety of events.

Mouse Press Detection Example.

  • Make the Guitar Chord SVG interactive
  • A First Step towards some type of custom control component.
  • Listen for mousedown events

HTML and SVG

From guitarHitTest.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>SVG DOM Events</title>
    <style><!-- Not shown here --></style>
</head>
<body>
    <h1>Guitar Fretboard Diagrams</h1>
    <p>Here we demonstrate some basic SVG DOM events.</p>
    <p>You clicked: <span id="Xcoord"></span>, <span id="Ycoord"></span>,
        String: <span id="GString"></span>, Fret: <span id="Fret"></span>.</p>
    <svg id="GChord" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
        <line class="GString" x1="10" x2="10" y1="10" y2="170" />
        <line class="GString" x1="30" x2="30" y1="10" y2="170" />
        <line class="GString" x1="50" x2="50" y1="10" y2="170" />
        <line class="GString" x1="70" x2="70" y1="10" y2="170" />
        <line class="GString" x1="90" x2="90" y1="10" y2="170" />
        <line class="GString" x1="110" x2="110" y1="10" y2="170" />
        <line class="GFret" x1="10" x2="110" y1="10" y2="10" />
        <line class="GFret" x1="10" x2="110" y1="50" y2="50" />
        <line class="GFret" x1="10" x2="110" y1="90" y2="90" />
        <line class="GFret" x1="10" x2="110" y1="130" y2="130" />
        <line class="GFret" x1="10" x2="110" y1="170" y2="170" />
        <circle cx="30" cy="70" r="10" />
        <circle cx="50" cy="70" r="10" />
        <circle cx="70" cy="30" r="10" />
    </svg>
    <script>// Shown later...   </script>
</body>
</html>

Desired Functionality

Want to know what string and fret the user clicks on:

Guitar event screenshot

Main Concepts

  • “Grab” items from DOM for manipulation or event registration, generally with ID or class information.

  • Define an event handling function. Here we also need to clean up some coordinates and such.

  • Register the event handling function with the relevant DOM element.

Hit Detection Code

var mySVG = document.getElementById("GChord"),
  myX = document.getElementById("Xcoord"),
  myY = document.getElementById("Ycoord"),
  myGString = document.getElementById("GString"),
  myFret = document.getElementById("Fret");
var rect = mySVG.getBoundingClientRect(); // For coordinate adjustment

function handleMouseClick(event) {
  console.log(event);
  let localX = event.clientX - rect.x;
  let localY = event.clientY - rect.y
  if (localX < 120) {
    let gstring = 6 - Math.round((localX - 10) / 20);
    myGString.innerHTML = String(gstring);
  }
  if (localY <= 170) {
    let fretNum = Math.round((localY+10)/40);
    myFret.innerHTML = String(fretNum);
  }
  myX.innerHTML = `x=${localX.toFixed(1)}`;
  myY.innerHTML = `y=${localY.toFixed(1)}`;
}

mySVG.addEventListener('mousedown', handleMouseClick);

SVG and HTML Coordinates

References

Doodle Program HTML

<section class="doodle">
    <h2>Doodle Fun 2!</h2>
    <p><button id="Clear">Clear</button></p>
    <svg id="MyDrawing" version="1.1" baseProfile="full" width="500" height="300"
        xmlns="http://www.w3.org/2000/svg" />
</section>

Doodle Program CSS

#MyDrawing {
    cursor: crosshair;
}

svg {
    border: solid blue;
    background-color: white;
}
section.doodle h2 {
    text-align: center;
}
section.doodle {
    border: dashed;
    margin: 1em;
    padding: 0.5em;
    background-color: antiquewhite;
}

Doodle Program JavaScript

mySvg = document.getElementById("MyDrawing");
let maxSize2 = 50;
clear = document.getElementById("Clear");

mySvg.addEventListener("mousedown", function (event) {
    let domPt = mySvg.createSVGPoint(); // Creates DOMPoint
    domPt.x = event.clientX; // set to event coordinates
    domPt.y = event.clientY;
    // Gets the "screen" coordinate transform matrix
    // invert it, apply to our "domPt".
    let svgPt = domPt.matrixTransform(mySvg.getScreenCTM().inverse());
    console.log("Mouse down!");
    console.log(domPt);
    console.log(svgPt);
    console.log(mySvg.getScreenCTM());
    // Create element with random properties based at click location
    let width = Math.random() * maxSize2;
    let square = document.createElementNS("http://www.w3.org/2000/svg", "rect");
    square.setAttribute("x", svgPt.x);
    square.setAttribute("y", svgPt.y);
    square.setAttribute("width", width);
    square.setAttribute("height", width);
    let colorStr = `rgb(${255 * Math.random()}, ${255 * Math.random()}, ${255 * Math.random()})`;
    square.setAttribute("fill", colorStr);
    square.setAttribute("fill-opacity", 0.7);
    mySvg.appendChild(square);
});

clear.addEventListener("click", function () {
    mySvg.innerHTML = "";
});

Explanation of Coordinate Conversion 1

  • domPt = mySvg.createSVGPoint();
  • Creates a DOMPoint
  • This object knows how to transform itself given a transform matrix using the matrixTransform() method
  • Set the coordinates from the click location: domPt.x = event.clientX; domPt.y = event.clientY;

Explanation of Coordinate Conversion 2

  • Get the SVG to HTML DOM transform matrix mySvg.getScreenCTM()
  • Invert it to go from HTML to SVG: mySvg.getScreenCTM().inverse()
  • Transform the point svgPt = domPt.matrixTransform(mySvg.getScreenCTM().inverse());
// reveal.js plugins