I am writing a little HTML5+JS tool to generate an SVG image. I have encountered a number of issues in doing so, and while I have workarounds for most of them, in at least one case I feel like there must be a better way. And then there are a couple of things that still just aren't working.
At present, this is for my own use, so cross-browser compatibility isn't a concern; as long as it works in Firefox (first preference) or Chromium, it's all good. I would like to stick it online once it's working right, though, so compatibility caveats would be appreciated.
file://
, no web server involved.https://gist.github.com/perey/1d352a790f749aa05a8b (see it in action)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta charset="utf-8"/>
<title>SVG Generator</title>
<style type="text/css">
figure {
width: 45%;
float: right;
}
#output-pic {
border: thin solid green;
cursor: pointer;
}
form {
width: 45%;
float: left;
}
</style>
<script>
window.onload = function() {
document.getElementById("input-box").oninput = update_text;
document.getElementById("output-pic").onclick = show_svg;
update_text();
}
function update_text() {
var input_elem = document.getElementById("input-box");
var output_elem = document.getElementById("changeable-text");
output_elem.textContent = input_elem.value;
}
function show_svg() {
var svg_win = window.open("", "svg_win");
var embedded_svg = document.getElementById("output-pic");
var transplanted_svg = svg_win.document.importNode(embedded_svg, true);
var blank_root = svg_win.document.documentElement;
svg_win.document.removeChild(blank_root);
svg_win.document.appendChild(transplanted_svg);
}
</script>
</head>
<body>
<figure role="img" aria-labelledby="preview-caption">
<figcaption id="preview-caption">Preview <small>(click for full
size)</small></figcaption>
<svg id="output-pic"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" width="640px" height="480px"
viewBox="0 0 640 480" preserveAspectRatio="xMinYMin">
<title>A test SVG file</title>
<defs>
<style type="text/css">
text {
font-family: serif;
stroke: none;
fill: red;
}
.underline {
stroke: blue;
stroke-width: 1;
fill: none;
marker-mid: url(#arrow);
}
</style>
<marker id="arrow"
viewBox="-3 -3 6 6" orient="auto"
markerUnits="strokeWidth"
refX="0" refY="0"
markerWidth="6" markerHeight="6">
<path d="M0,0 -3,-3 3,0 -3,3 Z"/>
</marker>
</defs>
<text id="changeable-text" text-anchor="middle" font-size="40"
x="320" y="240"></text>
<path class="underline" d="M10,250 h310 310"/>
</svg>
</figure>
<form>
<label>Text: <input id="input-box"/></label>
</form>
</body>
</html>
Opening about:blank
, deleting its document element, and adding the SVG element, feels really hacky. However, nothing else has worked I have only found a slightly better way of constructing a document in a new window (see below).
In particular, I've tried loading a barebones SVG file and adding all the child nodes of the preview SVG, like so:
function show_svg() {
var svg_win = window.open("blank.svg", "svg_win");
var embedded_svg = document.getElementById("output-pic");
var transplanted_svg = svg_win.document.importNode(embedded_svg, true);
var blank_root = svg_win.document.documentElement;
while (transplanted_svg.hasChildNodes()) {
blank_root.appendChild(transplanted_svg.firstChild);
}
svg_win.alert("Done!");
}
However, after this function does its thing, the loaded file then "wipes" all changes made to it and reverts to its pristine state. (The alert
is there to highlight this fact: in Firefox, the alert box itself disappears without user action when the page is "wiped". In Chromium, the alert box hangs about, but the wipe happens after it's dismissed.)
It's not a matter of tying the node reparenting to the new window's Yes it is. I made a mistake when I first tried that. Here's what I did:
onload
handler.
function show_svg() {
var svg_win = window.open("blank.svg", "svg_win");
var embedded_svg = document.getElementById("output-pic");
var transplanted_svg = svg_win.document.importNode(embedded_svg, true);
var blank_root = svg_win.document.documentElement;
svg_win.onload = function () {
while (transplanted_svg.hasChildNodes()) {
blank_root.appendChild(transplanted_svg.firstChild);
}
svg_win.alert("Done!");
}
}
What I should've done is put the definition of blank_root
inside the onload
handler. That works.
Still feels like there should be a way to construct a new document from scratch, though. "Modifying a blank SVG" is better than "modifying the about:blank
HTML", but is that really the best way?
(This only seems to be an issue with Firefox, not with Chromium.)
The marker-mid
styling works fine in the preview image, but not in the opened SVG. I have no idea why. Edit: Modifying an SVG file instead of about:blank
doesn't have this issue. I'm off to file a bug, but I already suspect they're going to say "don't try and dynamically convert a HTML file into an SVG file".
I have no idea how to do this. A few tantalising hints seem to say that it's something to do with Blobs, but I've found nothing that addresses saving a generated SVG file client-side, and I don't understand what they're doing well enough to make it work for me.
Any help, suggestions, advice, or corrections?
SVG images can be written directly into the HTML document using the <svg> </svg> tag. To do this, open the SVG image in VS code or your preferred IDE, copy the code, and paste it inside the <body> element in your HTML document. If you did everything correctly, your webpage should look exactly like the demo below.
The quick way: img elementTo embed an SVG via an <img> element, you just need to reference it in the src attribute as you'd expect. You will need a height or a width attribute (or both if your SVG has no inherent aspect ratio). If you have not already done so, please read Images in HTML.
I have solved my own problems, using modern HTML5 APIs.
The new show_svg()
function looks like this:
function show_svg(evt) {
var svg = document.getElementById("output-pic");
var serializer = new XMLSerializer();
var svg_blob = new Blob([serializer.serializeToString(svg)],
{'type': "image/svg+xml"});
var url = URL.createObjectURL(svg_blob);
var svg_win = window.open(url, "svg_win");
}
The browser's own Save functionality will work on this new window, and it doesn't involve any modifications to other files that "feel" weird or hacky. (It does seem a bit odd to serialise the SVG only to view it in the browser again, but this nonetheless seems to be The Right Thing under HTML5.)
The only unresolved problem is the disappearing markers—in fact, the problem gets worse, as now <use>
elements don't work either! However, they're still there and functional in the code, so once the SVG is saved to a file, everything in the file works fine. And I've filed a bug with Mozilla, too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With