Outils pour utilisateurs

Outils du site


issue147:inkscape

Last time, we used a linked JavaScript file to create an SVG file containing a circle that changes color when a button is pressed, when loaded into a web browser. This time, we’ll extend that simple example to show how the combination of SVG and JavaScript is ideal for animated demonstrations, by implementing a set of traffic lights. Our lights are the sort of thing that might appear on an educational site, or in a museum: they’ll cycle through the sequence of colors (red, red and amber, green, amber, red) once when the button is clicked. But first, we need some traffic lights: For simplicity, I’ve based this on the file I created for the previous instalment. That means the red light already has an ID (“redCircle”), and the JS file is already linked. I’ve duplicated the red circle to create the amber and green ones, giving them corresponding IDs of “amberCircle” and “greenCircle”. Because the JS is linked, and the red circle has the same ID as last time, loading the file into a web browser draws the lights okay, and clicking the button toggles the red light to green and back. Clearly there’s more work to be done, but at least the fundamentals are in place.

La dernière fois, nous avons utilisé un fichier JavaScript lié pour créer un fichier SVG contenant un cercle qui change de couleur quand on appuie sur un bouton, lorsqu'il est chargé dans un navigateur Web. Cette fois-ci, nous prolongerons cet exemple simple pour montrer comment la combinaison de SVG et de JavaScript est idéale pour des démonstrations animées, en mettant en œuvre des feux tricolores. Nos feux sont la sorte de chose qui peut apparaître sur un site d'éducation ou dans un musée : ils vont se succéder dans une séquence de couleurs (rouge (red), rouge et jaune (amber), vert (green), jaune, rouge), une fois quand on clique sur le bouton. Mais, d'abord, nous avons besoin de feux tricolores :

Par simplicité, comme base pour ceux-ci, j'ai pris le fichier créé dans l’article précédent. Cela signifie que la lumière rouge a déjà un ID (« redCircle ») et que le fichier JS est déjà lié. J'ai dupliqué le cercle rouge pour créer les jaune et vert, en leur donnant des ID qui correspondent, « amberCircle » et « greenCircle ». Comme le fichier JS est lié et que le cercle rouge a le même ID que la dernière fois, le chargement du fichier dans le navigateur dessine bien les feux, et un clic sur le bouton fait passer le feu du rouge au vert et vice-versa. Clairement, il reste encore beaucoup de travail à faire, mais les fondamentaux sont en place.

For this demo, we want the dull colored lights to turn bright at the correct times. Let’s forget about the timing for now, and deal with the colors first. With a variation on our existing code, we could easily set each light to a specific color by targeting it using its ID, then setting the “style.fill” property directly. A better approach, in this case, is to use classes. We can set a class for each light onto some ancestor object, and use CSS to apply the right fill. Since classes can be combined, we don’t need a “red-and-amber” class; we can just set the “red” and “amber” classes at the same time. But before we get too far ahead of ourselves, we need to set some default colors in CSS, so that we can override them later using classes. Open the file in a text editor, and find the <style> section (or add one, as a child of the <svg> element, if there isn’t one already). Put in some ID selectors, with the base colors you want to use – something like this: <style> #redCircle { fill: #800000; } #amberCircle { fill: #aa4400; } #greenCircle { fill: #008000; } </style>

Pour cette démo, nous voulons que les couleurs ternes des feux deviennent vives aux bons moments. Oublions le séquencement temporel pour l'instant, et occupons-nous d'abord des couleurs. En modifiant notre code existant, nous pourrions facilement paramétrer chaque feu à une couleur particulière en utilisant leurs ID, puis en réglant la propriété « style.fill » directement. L'utilisation des classes est, dans notre cas, une meilleure approche. Nous pouvons paramétrer une classe pour chaque feu sur le même objet ancêtre et utiliser le CSS pour appliquer le bon remplissage. Comme les classes peuvent être combinées, nous n'avons pas besoin d'une classe « rouge et jaune » ; nous pouvons simplement régler les classes « red » et « amber » en même temps.

Mais avant de nous engager trop loin, nous avons besoin de paramétrer des couleurs par défaut dans le CSS, afin de pouvoir ensuite les remplacer en utilisant les classes. Ouvrez le fichier dans un éditeur de texte et trouvez la section <style> (ou ajoutez-en une, comme enfant de l'élément <svg>, si elle n'existe pas déjà). Placez dedans des sélecteurs d'ID, avec les couleurs de base que vous voulez utiliser - quelque chose comme ceci :

<style>

#redCircle {
  fill: #800000;
}

#amberCircle {
  fill: #aa4400;
}

#greenCircle {
  fill: #008000;
}

</style>

Don’t worry if there’s already content in your <style> block, such as the rules used to style the button – just add the new code to the end. Don’t forget that you also have to remove the “fill” properties from the style attributes on the <circle> elements, otherwise they’ll just override anything set in the <style> block. A good test is to change all the colors in the CSS to “blue” and reload your page – if you still see red, amber or green then you have an override entry on the elements themselves. Now, we need to add the colors that we want to use when each light is turned on. It’s just another set of three styles added to the end of the <style> block, before the closing tag: … .red #redCircle { fill: ff0000; } .amber #amberCircle { fill: #ff6600; } .green #greenCircle { fill: #00dd00; } </style>

Ne vous inquiétez-pas si du contenu existe déjà dans votre bloc <style>, tel que des règles utilisées pour l'aspect du bouton ; ajoutez simplement le nouveau code à la fin. N'oubliez pas que vous devez aussi supprimer les propriétés « fill » dans les attributs de style des éléments <circle> ; autrement, ils vont remplacer n'importe quel autre réglage dans le bloc <style>. Un bon test est de changer toutes les couleurs du CSS en « blue » (bleu) et de recharger la page ; si vous voyez encore du rouge, du jaune et du vert, vous avez alors une entrée de remplacement sur les éléments eux-mêmes.

Maintenant, nous avons besoin d'ajouter les couleurs que nous voulons utiliser quand chaque feu est allumé. C'est simplement un autre jeu de trois styles ajouté à la fin du bloc <style>, avec la balise fermante :

...
.red #redCircle {
  fill: ff0000;
}
.amber #amberCircle {
  fill: #ff6600;
}
.green #greenCircle {
  fill: #00dd00;
}

</style>

Each of these rules is similarly structured, and can be read as “set this fill color for the element with a specific ID, but only if one of its ancestors has a specific class”. With this method we can set classes of “red”, “amber” and “green” on some ancestor element of the lights, such as the parent layer, or even on the root <svg> element, in order to activate the lights. So let’s do that… We’ve already seen how to use document.querySelector() with an ID to retrieve a particular element. To add our classes to the Inkscape layer would simply be a case of finding the right ID for the relevant <g> element. But to demonstrate a different approach, we’ll get a reference to the root <svg> element instead, then add a class to that. If you followed along last time you should already have a JavaScript file with a buttonPressed() function. Let’s replace the content of that function with this: function buttonPressed() { const svg = document.documentElement; svg.classList.toggle(“red”); }

Chacune de ces règles est structurée à l'identique, et peut être lue ainsi : « régler cette couleur de remplissage pour l'élément avec l'ID particulier, mais seulement si un de ses ancêtres a une classe particulière ». Avec cette méthode, nous pouvons régler les classes « red », « amber » et « green » d'un des éléments ancêtres des feux, tel que le calque du parent, ou même de l'élément <svg> racine, de façon à allumer les feux. Voyons cela.

Nous avons déjà vu comment utiliser document.querySelector() avec un ID pour retrouver un élément particulier. L'ajout de nos classes au calque d'Inkscape serait juste un cas de découverte du bon ID pour l'élément <g> correct. Mais pour présenter une autre approche, nous ferons, à la place, référence à l'élément racine < svg >, puis nous y ajouterons une classe. Si vous avez tout suivi la dernière fois, vous devriez déjà avoir un fichier JavaScript avec une fonction buttonPressed(). Remplacez le contenu de cette fonction par ceci :

function buttonPressed() {

const svg = document.documentElement;
svg.classList.toggle("red");

}

The document.documentElement property returns the root element of an XML or XML-alike document. In the case of an SVG file, it returns the <svg> element; for an HTML document, it returns the <html> element, and so on. So you can see that this simple two-line function will toggle the “red” class on the <svg> element, and our CSS is written such that this should alter the color of the red circle. Save the code, reload the image in your browser, and check that clicking the button does, indeed, toggle the shade of red for the first circle. Replace the word “red” with “amber”, save the file, reload the web page, and click the button again. Then do the same with “green” as the class name. Make sure that each light works as expected before moving on.

La propriété de document.documentElement retourne l'élément racine d'un document XML ou assimilé. Dans le cas d'un fichier SVG, elle retourne l'élément <svg> ; pour un document HTML, elle retourne l'élément <html>, et ainsi de suite. Ainsi, vous pouvez voir que cette simple fonction sur deux lignes basculera la classe « red » sur l'élément <svg>, et notre CSS est écrit de telle sorte que ça modifiera la couleur du cercle rouge. Sauvegardez le code, rechargez l'image dans votre navigateur et vérifiez qu'un clic sur le bouton fait véritablement changer la teinte rouge au premier cercle.

Remplacez le mot « red » par « amber », sauvegardez le fichier, rechargez la page Web et cliquez à nouveau sur le bouton. Faites ensuite la même chose avec « green » comme nom de classe. Assurez-vous que chaque feu fonctionne comme attendu, avant de poursuivre.

Our traffic light sequence includes one step in which two lights must be illuminated at once – requiring us to set two classes. In an ideal world, the classList.toggle() method would be flexible enough to take a parameter of “red amber”, and toggle both classes. But we’re stuck in a less than ideal world – one in which the classList methods all work with a single class at a time – so to toggle both classes requires the method to be called twice: function buttonPressed() { const svg = document.documentElement; svg.classList.toggle(“red”); svg.classList.toggle(“amber”); }

La séquence de nos feux tricolores comprend un état où deux feux doivent être éclairés en même temps, nous obligeant à régler deux classes. Dans un monde idéal, la méthode classList.toggle() serait assez flexible pour accepter un paramètre « red amber » et faire basculer les deux classes. Mais nous sommes prisonniers de notre monde qui n'est pas idéal, dans lequel les méthodes classList fonctionnent toutes avec une seule classe à la fois ; aussi, pour faire basculer deux classes, nous devons appeler la méthode deux fois :

function buttonPressed() {

const svg = document.documentElement;
svg.classList.toggle("red");
svg.classList.toggle("amber");

}

In our demo we don’t actually want to toggle lights on and off – we just want to set a fixed selection of lights for each step, without having to also turn off lights from the previous step, or call the same method multiple times. Using the classList interface actually makes life more difficult for us, when all we want to do is set the “class” attribute to a specific value. Luckily for us, browsers provide a function for setting the value of an attribute. It’s got the sensible name of setAttribute() and its arguments are the name of the attribute to set, and the value to set it to. Let’s use it to turn on both the red and amber lights: svg.setAttribute(“class”, “red amber”); If you try this in your code you’ll find that you can turn the lights on, but as we’re no longer using a toggling function you can’t turn them off again without reloading the page. But we’re not really interested in toggling – we want a sequence of particular lights. For that, however, we need a little foray into the history of JavaScript…

En fait, dans notre démo, nous ne voulons pas que les feux s'allument et s'éteignent, nous voulons simplement une sélection déterminée de feux à chaque étape, sans avoir à éteindre les feux de l'étape précédente, ou appeler plusieurs fois la même méthode. L'utilisation de l'interface classList nous rend vraiment la vie plus difficile, alors que tout ce que nous voulons faire, c'est donner une valeur particulière à l'attribut « class ». Heureusement, les navigateurs fournissent une fonction pour le réglage de la valeur d'un attribut. Elle a reçu le nom bien choisi de setAttribut() (régler l'attribut) et ses arguments sont le nom de l'attribut à régler et la valeur à lui donner. Utilisons-la pour allumer les feux rouge et jaune :

svg.setAttribute(“class”, “red amber”);

Si vous l'essayez dans votre code, vous trouverez que vous pouvez allumer les feux, mais, comme nous n'utilisons plus la fonction de bascule, vous ne pouvez plus les éteindre sans recharger la page. Mais nous ne sommes pas vraiment intéressés par le va-et-vient, nous voulons une séquence particulière des feux. Pour ça, nous avons besoin de faire une petite incursion dans l'histoire de JavaScript.

Back in the early days of the web, JavaScript was executed as part of the same “thread” as the browser code itself. This meant that the browser would effectively hand over control to the script, and couldn’t update its UI, or respond to input, until the JS code relinquished that control. You might remember the bad old days when a rogue web page could hang the browser, preventing you from doing anything else with either the page itself or the browser UI. So, JavaScript doesn’t contain any instructions to pause execution of the script, as doing so would block the browser entirely. That means we can’t sequence our lights with something as simple as this pseudo-code: svg.setAttribute(“class”, “red”); pause(3000); svg.setAttribute(“class”, “red amber”); pause(3000); svg.setAttribute(“class”, “green”); …

Dans les premiers temps du Web, JavaScript était exécuté comme faisant partie du même « thread » (séquence de calcul) que le code du navigateur lui-même. Cela signifiait que le navigateur transmettait vraiment le pilotage au script, et ne pouvait mettre à jour son interface utilisateur, ou répondre à une saisie, avant que JS ne rende ce pilotage. Vous vous souvenez peut-être de ces jours terribles où une page Web malfaisante pouvait bloquer le navigateur, vous empêchant de faire quoi que ce soit, sur la page elle-même ou dans l'interface du navigateur. Aussi, JavaScript ne contient aucune instruction pour mettre l'exécution du script en pause, car le faire, ce serait bloquer tout le navigateur. Ça signifie que nous ne pouvons pas séquencer nos feux avec quelque chose d'aussi simple que ce pseudo-code :

svg.setAttribute(“class”, “red”); pause(3000); svg.setAttribute(“class”, “red amber”); pause(3000); svg.setAttribute(“class”, “green”); …

Instead JavaScript has a function called setTimeout(). This is a mechanism for queuing up a function call for later on. It doesn’t pause execution of the current function, but asks the browser to run another function after at least a certain amount of time has passed. It takes two parameters: a function or reference to a function, and the minimum timeout in milliseconds. With this, we can write a series of functions that call each other in sequence, to create our demo: function buttonPressed() { const svg = document.documentElement; svg.setAttribute(“class”, “red”); setTimeout(redAmber, 3000); } function redAmber() { const svg = document.documentElement; svg.setAttribute(“class”, “red amber”); setTimeout(green, 3000); } function green() { const svg = document.documentElement; svg.setAttribute(“class”, “green”); }

À la place, Javascript a une fonction appelée setTimeout(). C'est un mécanisme pour mettre en file d'attente un appel de fonction pour plus tard. Elle ne provoque pas de pause dans l'exécution en cours, mais demande au navigateur de lancer une autre fonction quand, au minimum, une certaine quantité de temps s'est écoulée. Elle utilise deux paramètres : une fonction ou une référence à une fonction, et le temps limite minimum en millisecondes. Avec elle, nous pouvons écrire une série de fonctions qui s'appellent les unes les autres en séquence, pour créer notre démo :

function buttonPressed() {

const svg = document.documentElement;
svg.setAttribute("class", "red");
setTimeout(redAmber, 3000);

}

function redAmber() {

const svg = document.documentElement;
svg.setAttribute("class", "red amber");
setTimeout(green, 3000);

}

function green() {

const svg = document.documentElement;
svg.setAttribute("class", "green");

}

That covers the first three steps of the sequence. I’ll leave it as an exercise for the reader to extend it to the full five steps described at the start of the article. With that, our traffic lights demo is almost complete. Clicking the button will begin the sequence, which will stop automatically at the end. All that’s left is a little more work in Inkscape to make the lights look better. Provided you don’t change the CSS we added, or remove the link to the JS file, you’re free to tweak the design as much as you like without fear of the interactivity being broken. Here’s my result, partway through its cycle, after a little work in Inkscape with a few gradients and some lines.

Voilà pour les trois premières étapes de la séquence. Je laisserai, à titre d'exercice pour le lecteur, sa complexe extension aux cinq étapes décrites du début de l'article.

Avec elle, notre démo de feux tricolores est presque complète. En cliquant sur le bouton, la séquence commence et elle s'arrêtera automatiquement à la fin. Il reste un peu de travail dans Inkscape pour améliorer l'aspect des feux. Partant du principe que vous ne modifiez pas le CSS que nous avons ajouté, ni ne supprimez le lien vers le fichier JS, vous êtes libre d'ajuster le dessin comme il vous chante sans craindre de détruire l'interactivité. Voici mon résultat, au milieu de son cycle, après un peu de travail sur quelques dégradés et des lignes dans Inkscape.

Comparing this to the first image in this article shows just how much impact a little extra design work can achieve. But if I’d done all that design work first and only then started adding JavaScript, I would have had to deal with a much more complex SVG file for the few manual edits required. If you possibly can, it’s usually better to focus on the core aspects of your animations or interactions, and get those working on a simple version of your file first. If you’re careful, editing your file in Inkscape shouldn’t break your code and interactions. But because work like this requires flitting back and forth between Inkscape and a text editor, both working on the same file and therefore able to interfere with each other’s contributions, I strongly advise taking very frequent backups as you work. Next time, we’ll have a quick look at some other options for manipulating SVG using JavaScript, moving beyond a few changes of fill and stroke color into altering other aspects of your images.

En le comparant à celui de la première page de cet article, nous voyons l'impact que peut avoir un peu de travail supplémentaire de dessin. Mais si j'avais fait tout de suite tout le travail de dessin puis commencé à ajouter le JavaScript, j'aurai dû gérer un fichier SVG beaucoup plus compliqué pour les quelques ajouts manuels nécessaires. S'il vous est possible de le faire, il est préférable habituellement de se concentrer sur les aspects primordiaux de vos animations et interactions et de vous assurer d'abord qu'elles marchent sur une version simple.

Si vous faites attention, la modification de votre fichier dans Inkscape ne détruit pas vos codes et interactions. Mais, parce qu'un tel travail nécessite d'aller et venir entre Inkscape et l'éditeur de texte, les deux travaillant sur le même fichier, et par conséquent, risquant d'avoir des interférences dans les contributions de l'un et de l'autre, je vous recommande chaudement de faire des sauvegardes régulières pendant que vous travaillez.

La prochaine fois, nous regarderons d'autres options pour manipuler Inkscape en utilisant du Javascript, dépassant les simples modifications des couleurs de remplissage et de contour pour changer d'autres aspects de vos images.

issue147/inkscape.txt · Dernière modification : 2019/08/12 15:32 de andre_domenech