Ceci est une ancienne révision du document !
So far in this series, we’ve used some JavaScript to change the fill or stroke color of an object in an SVG file when loaded in a web browser. But JavaScript in SVG is the same language, powered by the same engine, as JavaScript in HTML. That makes it a powerful tool for doing far more than just tweaking some colors.
First, a quick reminder of the structure of an XML tag, of the sort you might find in an SVG file: <tagName id=“uniqueID” attributeName=“attributeValue”>textContent</tagName>
Let’s look at each part of this individually: • tagName – The name of the tag or element. In SVG, this might be a ‘g’ for a group, or ‘rect’ for a rectangle or square, for example. • id – This is just an attribute that happens to be named ‘id’, but the rules of XML dictate that IDs must be unique within a document. That makes them handy for targeting with the querySelector() function. • attributeName – Each tag may have zero or more attributes which contain additional data associated with the element. In XML languages, these always take the form of attributeName=“attributeValue”, whereas HTML (confusingly) allows for some attributes that have no value associated with them. Each attributeName must be unique within the element, but may appear many times across different elements. The attributeValue will vary depending on what the attribute is actually used for. • textContent – This is not so common in XML. Usually, an element will contain zero or more child elements before the closing tag (the </tagName> in this example), but a few elements allow for plain text to be included. In SVG, the most common cases are <text> and <tspan> elements, where the plain text holds the text string that will be rendered.
There are also a couple of variations to be aware of. Self-closing tags take the form <tagName … />. By definition these can have no children or text content. XML documents also make use of namespaces, which are defined in the main tag for the document (e.g. the <svg> tag), and may then crop up appended to tags and attributes with a colon. You won’t see these often: usually a default namespace is declared, in which case namespaces need to be added only to tags and attributes that are from ‘foreign’ XML languages.
The theory is fine, but let’s see how these parts manifest themselves with yet another super-simplified SVG file:
<svg xmlns=“http://www.w3.org/2000/svg” viewBox=“0 0 100 100”>
<text id="text" x="50" y="50" text-anchor="middle"> This is <tspan id="ts1">some</tspan> <tspan id="ts2">SVG text</tspan> </text>
</svg>
Breaking this down, we have an <svg> tag containing a <text> tag with some further content. The <svg> tag has a couple of attributes. The first defines the default namespace, and is required so that the browser knows this is a document conforming to the W3C’s SVG spec, and not some other type of file that happens to have a tag name called ‘svg’. The second attribute sets up the coordinate space we’ll be using in this file – I usually stick with “0 0 100 100” for my hand-created files, as I can then treat my values as percentages within the image.
The <text> tag also has some attributes. The ID is self-explanatory. The others set the ‘anchor point’ for the text to the middle of the image (50, 50), and indicate that the anchor point should be in the middle of the text (i.e. the text is centered, not left- or right-aligned).
Finally the <text> tag contains a mixture of text content and a couple of <tspan> elements with IDs, which will allow us to specifically target those parts of the text via JavaScript.
Save the file and load it into a web browser – preferably Firefox or Chrome, as they have better developer tools than most others. From the previous articles, you already know how to add JavaScript to your SVG file, either directly in Inkscape or by linking to an external JS file, but we won’t be doing that today. For the rest of this article, we’re going to rattle through a few ways you can affect your SVG, but we’ll do so within the browser’s developer tools. Any of these commands or techniques can be added to your own JavaScript if you want to create something less ephemeral.
Press F12 or use the menu to open your browser’s developer tools. Somewhere along the top should be a row of tabs (though they’re not always clearly styled as such). Make sure you have the “Console” tab selected. If the panel is already filled with text, find the button in the console’s toolbar to clear it, for clarity. Click inside the console area to give it the focus, and type the following (followed by the Enter key):
var t = document.querySelector(“#text”);
The console will display the string “undefined” at this point. That’s nothing to worry about, it just indicates that the line you entered didn’t return a value. But what it has done is find the element with an ID of “text” and assign it to the variable “t”. You can confirm that by typing the letter “t” on its own, then pressing Enter. The console should show a representation of the <text> element, looking something like that shown above.
Let’s use some JavaScript we already know to reduce the size of the font a little. Type this into the console:
t.style.fontSize = “10px”;
The SVG content should react as soon as you press the Enter key. Type the letter “t” again and you’ll see that the element now has a “style” attribute with the font-size property set. Notice that we set “fontSize” in JS, but the CSS in the attribute shows “font-size”. If you tried to use the latter in JavaScript, it would be interpreted as trying to subtract the “size” variable from the “font” variable, and would throw an error. As a general rule, any CSS property containing embedded hyphens is available as a JavaScript property by removing the hyphens and capitalising the first letter of all but the first word.
Breaking down the line above, you know that “t” is a JavaScript representation of our XML node. The browser exposes various properties and methods (functions tied to a specific object) on that node, including the “style” property. This property, in turn, has a “fontSize” property, which we’ve set to a value of “10px”. But the browser treats the “style” property a little differently to most JavaScript properties, and instead also applies any changes to the “style” attribute in the XML. In this instance, it doesn’t matter whether you change the attribute or the property – but that’s not usually the case.
To change most attributes, therefore, you can’t just set a correspondingly named JavaScript property. Instead, you have to use the setAttribute() method that we’ve looked at previously. Here’s how we might move the text up a little:
t.setAttribute(“y”, 20);
Type “t” again to see the XML, and you’ll notice the “y” attribute now has a value of “20”. We can also retrieve that value using the getAttribute() method:
t.getAttribute(“y”); Returns “20” Remembering that the y-axis in SVG runs from the top of the screen to the bottom, you might be inclined to try some code like this to move the text down by 10 units: var yPos = t.getAttribute(“y”); t.setAttribute(“y”, yPos + 10); Gah! Where did the text go!? Actually it’s still there, but it’s been positioned so far down in the image that it’s dropped out of the 100×100 viewBox, so isn’t visible. But why is that, when we just wanted to adjust the value from 20 to 30? The problem is that XML is a text-based system, and doesn’t really have a concept of different data types. All attributes are therefore text strings, regardless of the value you put in, so our call to getAttribute() returns the string “20”, not the number 20. JavaScript then tries to be ‘helpful’ by determining that we’re trying to ‘add’ the number 10 to the string “20”. Since you can’t add a number to a string, it automatically converts the number into a string (“10”), then concatenates the two, to give a result of “2010”. That’s the value we end up putting into the attribute in our setAttribute() call, so our text ends up being moved to a y-position of 2010 units! We can fix this by converting the value returned from getAttribute() into a number. We only want an integer value, so the parseInt() function is the tool to use – but there is also a parseFloat() if you need to deal with decimal fractions. parseInt() has a second parameter for the number base that you should always provide (with a value of 10 for a decimal conversion) to avoid some rare-but-odd corner case bugs when converting certain strings. Entering the following lines into the console should get us the result we were looking for: t.setAttribute(“y”, 20); var yPosNumeric = 0; yPos = t.getAttribute(“y”); yPosNumeric = parseInt(yPos, 10); t.setAttribute(“y”, yPosNumeric + 10); You can run the last three lines repeatedly to move your text down by 10 units each time. Now we know how to get and set attributes, but you can also remove them entirely. This will get rid of the “style” attribute we indirectly created earlier, returning the text to its ‘natural’ size: t.removeAttribute(“style”); There’s no equivalent createAttribute() call - setting the value of a non-existent attribute using setAttribute() will automatically create it. Let’s get our style back by manipulating the attribute rather than the property: t.setAttribute(“style”, “font-size: 10px;”); As well as working with attributes, you can also dynamically change the text content of an element. Let’s type a few lines into the console to alter the first <tspan>: var ts1 = document.querySelector(“#ts1”); ts1.style.fill = “#ff0000”; ts1.style.fontStyle = “italic”; ts1.textContent = “a bit of”; [relevant image shown above] Being able to change the text content via JavaScript opens up a world of possibilities, including images with descriptions that can be switched between different languages, or ones that populate with data requested from a server somewhere such as live graphs and stock tickers. That degree of sophistication is a little beyond this series, but here’s a trivial example that prompts the user to enter their name, then updates the text on the page accordingly: ts1.textContent = prompt(“What is your name?”) + “'s”; [relevant image shown below] Modifying the properties, attributes and text content of existing elements is useful, but to have complete control over a document we also need to be able to add and remove elements using JavaScript. The removal part is trivial, provided you can get a reference to the element using querySelector() or some other mechanism. Let’s delete our first <tspan> entirely: ts1.remove(); Adding a new element to the page can be trivially easy, or it can be rather convoluted. Let’s start with the easy method, by adding another <tspan> to the <text> element, which is still assigned to our “t” variable: t.innerHTML += '<tspan id=“ts3” style=“fill: red;”>!!!</tspan>'; Even though we’re working on an SVG file, which is a form of XML document, we still have to use the “innerHTML” property. This returns all the descendants of the specified node as a string – basically a string of HTML (or XML in this case) much like the ones you type into a text editor. The “+=” operator essentially retrieves a value, adds or concatenates something to it, and puts the result back into the same place. In our case it has the effect of appending a new <tspan> to the end of the existing content. Let’s do something similar, but with a more complex approach… var ns = “http://www.w3.org/2000/svg”; var newTS = document.createElementNS(ns, “tspan”); newTS.id = “ts4”; newTS.setAttribute(“style”, “fill: blue”); newTS.textContent = “!!!”; t.appendChild(newTS); That’s a lot more lines to explain: • We set up a variable, “ns”, that will hold our SVG namespace. Usually this is done once at the top of the JavaScript so you can use it in multiple places. • We create a new <tspan> element. If you’ve ever done this in HTML, you might be familiar with document.createElement(), but, in the XML world, we need to use a namespace-aware equivalent, createElementNS(), and pass the namespace as the first parameter. • We give the element an ID to make it easier to get hold of later. We could have used setAttribute() for this, but the browser has an implicit mapping between the property and attribute in this case, in the same manner as we saw earlier with the ‘style’ property. • Now we can set an attribute on the new element. We would need to repeat a line like this for each attribute we wish to set. • We’ve created a <tspan>, so we won’t see much unless we also give it some text content. • Finally, we append it as a child of the object referred to by the “t” variable – our <text> element. Clearly that’s a lot more typing than the innerHTML version, so why would you ever want to take this approach? Precisely because it’s verbose, splitting the element, attributes and text content into separate lines, it lends itself to some types of looping or manipulation that can otherwise become unwieldy when using just a single chunk of text. Consider trying to plot a graph using SVG. Each point on the graph might be represented by a <circle> requiring several attributes: x, y, r and fill, for example. These values will be determined by some data source, and may need to be manipulated to get them into the right format for SVG. All of that is a little easier to arrange, and can lead to clearer code, if you deal with each attribute separately. Certainly it can be done with the “innerHTML” approach, but as the code and SVG content become more complex, an approach that relies on building and manipulating strings can become harder to follow, and less robust. Next time, we’ll build on the techniques used in this instalment, to further investigate ways to manipulate the individual elements in an SVG document through JavaScript.