Outils pour utilisateurs

Outils du site


issue149:inkscape

Ceci est une ancienne révision du document !


Last time, we looked at some different ways to use JavaScript to modify your SVG file dynamically in a web browser. We concluded with a verbose way to create a new SVG element, set its attributes, and append it to an existing element. In this instalment, we’ll build on those same ideas to do even more with our elements – so go ahead and re-read last month’s column if you need a refresher before we plough on. Our test file last time was made up primarily of an SVG <text> element – picked because it’s one of the few SVG elements that has text content within it, and I wanted to demonstrate how you might go about getting and setting such content. Most SVG elements, however, have either no content or only other elements as their children, so that’s the sort of structure we’ll focus on this time. Here’s the SVG file you’ll need to create as a starting point: <svg xmlns=“http://www.w3.org/2000/svg” viewBox=“0 0 100 100”> </svg>

La dernière fois, nous avons regardé ifférentes façons d'utiliser le Javascript pour modifier dynamiquement votre fichier SVG dans un navigateur Web. Nous finimes avec une manière verbeuse de créer un élément SVG, régler ses attributs et le rattacher à un élément existant. Dans ce numéro, nous prendrons ces mêmes idées pour base et nous en ferons encore plus avec nos éléments - aussi, relisez tout de suite l'article du mois dernier si avez besoin de vous râfraichir la mémoire avant de nous y atteler.

notre fichier test de la dernière fois était constitué prinicpalement d'un élément SVG <text> - choisi parce que c'est un des rares éléments SVG contenant du texte et je voulais montrer comment vous pourriez faire pour obtenir et paramétrer un tel contenu. Cependant, la plupart des éléments SVG ont, soit aucun contenu, soit d'autres éléments vus comme leurs enfants ; aussi, nous allons nous concentrer cette fois-ci sur ce genre de structure. Voici le fichier SVG que nous aurons besoin de créer comme point de départ :

<svg

xmlns=“http://www.w3.org/2000/svg

viewBox=“0 0 100 100”>

</svg

Okay, that’s admittedly a pretty terse SVG file, even by the standards of this series, but that’s because it doesn’t actually have any content. Instead we’re going to create all the content dynamically, using the browser’s developer tools as we did last time. So save the file, load it into a browser, open the developer tools, switch to the Console tab, and clear any existing messages. Phew! Now that we’re ready to proceed, let’s begin by creating a square using the ‘simple’ approach from last time: var svg = document.querySelector(“svg”); svg.innerHTML = '<rect id=“s1” x=“10” y=“10” width=“50” height=“50” fill=“red” />'; This ‘innerHTML’ approach is simple, and can create complex nested structures, but it doesn’t return a ‘handle’ that we can use to further manipulate the created content. What if we now want our red square to be blue? We’ll need to do something like this, relying on the fact that we gave the square an ID: var square1 = document.querySelector(“#s1”); square1.setAttribute(“fill”, “blue”);

The more long-winded approach we took last time gives us a JavaScript object representing our element which we then use to set all the attributes. But we can hang on to that handle to use later on, if we wish. Let’s add another red square, this time using the verbose method (below): Now if we want to change the color of the second square, we can simply use the same ‘square2’ variable we used when creating it, even though it’s now been added to the page: square2.setAttribute(“fill”, “yellow”); I’ll spare you a screenshot – I’m sure you can guess what it looks like now.

So far, we’ve mostly revised the content from last month, but in doing so you’ve got a nice arrangement of two squares, a yellow one on top of a blue one. But why are they in that order? Why isn’t the blue one on top? You might think it’s because we created the blue one first, and the yellow one second – and to some extent you’d be right. But there is a little more to it than that. The reason isn’t that we created the squares in a particular chronological order, but rather that they ended up in the XML structure in a particular document order. When we added the second square we used the appendChild() method, which inserts it as the last child of the selected parent, so our XML structure ends up looking roughly like this: <svg> <rect id=“s1” /> <rect id=“s2” /> </svg>

The blue <rect>, with ID “s1” is first in the document, so it gets drawn first. The yellow <rect> (“s2”) is second in the document, so gets drawn second. SVG uses what’s called the “painter’s model” in which later objects in the document are painted on top of earlier objects – so the yellow square is rendered on top of the blue square. If you’re familiar with HTML and CSS, you might imagine that you could override this ordering using the “z-index” CSS property. Unfortunately, that approach doesn’t work for SVG. The SVG2 spec does add z-index but, as with many of the useful additions in SVG2, no browser yet supports it. Currently, if you want to put things into a particular stacking order, you have no choice but to rearrange the content of your SVG document.

So how would we go about putting the blue square on top of the yellow one? It’s a two step operation: first we remove the blue square from the document, but keep it hanging around in memory; then we insert it back into the document, at the end. Given that we’ve already assigned the blue <rect> to the “square1” variable, we can use these two lines of JavaScript to achieve our goal: square1.remove(); svg.appendChild(square1);

So now we know how to add an object to the top of the image, and how to move an object from a lower level up to the top. What about inserting a new object at the top of the document, so that it appears right at the bottom of the stack? If appendChild() adds it to the end of the document, surely insertChild() will put it at the start? Uh-oh! That’s not so good. The problem is that there’s no XML method called insertChild(), regardless of how much sense it would make. Instead, you have to insert your node into the document before another reference node – in other words, you have to specify that you want to insert it before the existing first child. Given that our yellow square is first in the document, and we already have a handle to it in our “square2” variable, we can run the following JavaScript line in the console to inject “square3” into the “svg” parent, before “square2”: svg.insertBefore(square3, square2);

This is great when you’ve already got a handle to the first child element, but that’s not always the case. Perhaps it was inserted dynamically by some other code, or you’ve just lost track of which element is which. You can always append a new node at the end of the parent’s list of children, so it would be useful to have an equivalent bit of code to insert a new node at the start of the list. Every XML element has a “firstElementChild” property that can be used to retrieve a handle to its first child (skipping any text content) without needing to know anything more about it. We can use this to insert another element at the bottom of the stack (top right). There’s still a bit of a gap between the yellow and the blue squares. Time to insert an element in the middle of the list of child nodes. As a reminder of where we stand at the moment, switching to the “Inspector” (Firefox) or “Elements” (Chrome) tab in the developer tools will show you the current state of your XML document:

To mix things up a little, we’re not going to create a brand new square this time – instead we’ll create a copy of an existing one. In browser terms, we’re going to create a “clone” of the node, but don’t confuse it with Inkscape’s concept of clones – the two are completely different things (Inkscape’s “clones” are actually implemented as SVG <use> elements). First, let’s clone our purple square – which we’ve still got assigned to the “square4” variable – and assign the clone to a highly-imaginatively named variable: var square5 = square4.cloneNode(true); square5.id = “s5”; square5.setAttribute(“x”, 20); square5.setAttribute(“y”, 20);

All we needed to do was call the cloneNode() method of the node we wish to duplicate. The “true” parameter ensures that we clone not only the node itself, but any descendants it may have – if we had passed “false” instead, we would get only a duplicate of the node itself. In this case, the results are identical, since our <rect> has no children. But consider cloning a <text> or <g> element, where the content inside is just as important as the node itself, and you can see why passing “true” is usually the safest option. You’ll also note that I’ve changed the ID of the cloned element. We’re about to put it back into the same document and, although browsers don’t enforce it, the XML rules specifically prohibit duplicate IDs in a single document. For the sake of correctness, therefore, we change the ID while the cloned node is still just a fragment in memory that hasn’t yet been inserted into the SVG.

To insert the clone, we’re just going to use the same insertBefore() method we used earlier. But, this time, our reference element (the one we’re inserting before) will be the blue <rect>. Of course we could use the reference we already have to it (square1), or get a fresh reference using document.querySelector(), but instead, we’re going to do something more generic. We’ve already seen a generic way to insert before the first child, now we’re going to write some equally generic code to insert just before the last child: svg.insertBefore(square5, svg.lastElementChild);

As a reminder, the last child is the one that’s drawn on top (the blue square), so inserting just before the last child actually puts the cloned purple square below the blue square in the z-order. The “firstElementChild” and “lastElementChild” properties are useful shortcuts, but you don’t always want to use the first or last child as your reference point. For more general purpose requirements, XML nodes have a “children” property, which returns a collection of all the children. A “collection”, for what it’s worth, can be described as an object that’s a bit like an array except different enough to be annoying. So don’t expect to have access to all the array methods, but you can read the “length” property, and reference individual child nodes using a square bracket syntax: How many child nodes are there? console.log(svg.children.length); Remove the third one svg.children[2].remove(); Remove the penultimate node, regardless of how many there are svg.children[svg.children.length - 2].remove(); Remembering that array (and collection) indexes start at zero, it should be clear why the third child has an index of 2. For the same reason the index of the last child is always “children.length – 1”, so the penultimate node will be “children.length – 2”. Of course if there are fewer than two child nodes present, this call will fail – a real program would have to check the length first, before trying to remove the penultimate child. Consider all this in terms of Inkscape: when you move things up and down in the z-order within Inkscape, or move entire layers up and down, what you’re actually doing is removing nodes from the document and reinserting them at a different position. If you’ve got multiple items selected, or a group or layer containing lots of other items, they all have to be removed and reinserted. If you ever find yourself wondering why Inkscape is taking a long time to paste something, now you’ve got an idea of how involved this process actually is! With the JS you’ve learnt so far, you have enough knowledge to write some code that will move objects around the canvas, as well as up and down in the z-order. You can use document.querySelector() to get a JS handle to an element in your drawing, and setAttribute() to dynamically change its parameters. Next time, we’ll look at how you might use some of these features to animate your SVG image.**

issue149/inkscape.1569932311.txt.gz · Dernière modification : 2019/10/01 14:18 de d52fr