One of the many annoying things about creating SVGs is the constant switching back-and-forth between code and on-screen rendering. One good way around this is multiple monitors, but that still requires moving the mouse cursor from one screen to the other. Why is it a big deal? Developers switch screens all the time when coding. That's true, but when you're dealing with SVGs, especially when writing paths, this gets repeated a lot and can be a huge pain in the ass.
The other way is to use a site like
JsFiddle. It comes with several features which can be nice... but I'm a big fan of keeping things simple.
With that in mind, today we'll be using jQuery to create a quick-and-dirty application that I'm going to call the
SVG Maker. The user will fill in things like width, height, and the inner XML, and the preview will show accordingly.
We begin with some standard HTML, and linkage to jQuery. In styling, we set the font.
<!DOCTYPE html>
<html>
<head>
<title>SVG Maker</title>
<style>
body
{
font-family: arial;
font-size: 14px;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
</script>
</head>
<body>
</body>
</html>
In the body, we add two divs, each styled using the CSS class
panel, which we will create later.
<!DOCTYPE html>
<html>
<head>
<title>SVG Maker</title>
<style>
body
{
font-family: arial;
font-size: 14px;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
</script>
</head>
<body>
<div class="panel">
</div>
<div class="panel">
</div>
</body>
</html>
This will be followed by the addition of labels and inputs. We have
txtWidth and
txtHeight, which accept only numerical values above 10, with a default of 100. HTML5 is just wonderful, eh? We also have a textbox
txtCode. Finally, we have a button,
btnPreview.
<html>
<head>
<title>SVG Maker</title>
<style>
body
{
font-family: arial;
font-size: 14px;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
</script>
</head>
<body>
<div class="panel">
<label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
<br />
<label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
<br />
<label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
<br />
<button id="btnPreview">Preview</button>
<br />
</div>
<div class="panel">
</div>
</body>
</html>
Perhaps most importantly, we have an SVG,
svgResult. It has a
black outline for visibility.
<html>
<head>
<title>SVG Maker</title>
<style>
body
{
font-family: arial;
font-size: 14px;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
</script>
</head>
<body>
<div class="panel">
<label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
<br />
<label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
<br />
<label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
<br />
<button id="btnPreview">Preview</button>
<br />
</div>
<div class="panel">
<svg id="svgResult" style="outline: 1px solid rgb(0, 0, 0)" xmlns="http://www.w3.org/2000/svg" />
</div>
</body>
</html>
Here it is. Nothing to shout about, eh? That will be improved on, soon.
Here's some CSS.
panel gives the dashboard and preview area some definition with a nice
orange outline. Bear in mind that this isn't meant to look pretty. It's meant to be useful visually, which isn't necessarily (and quite often isn't) the same thing.
<style>
body
{
font-family: arial;
font-size: 14px;
}
.panel
{
outline: 2px solid rgb(200, 100, 0);
width: 90%;
padding: 1em;
margin: 1em auto 0 auto;
}
</style>
We'll then style labels, inputs and buttons. Here, I set the
display properties to
inline-block and set
width and
float properties, so as to align them properly. It's not that important from an aesthetic point of view because it
still doesn't look pretty, but at least it doesn't look like a mess, which
is important from a usability point of view.
<style>
body
{
font-family: arial;
font-size: 14px;
}
.panel
{
outline: 2px solid rgb(200, 100, 0);
width: 90%;
padding: 1em;
margin: 1em auto 0 auto;
}
label
{
display: inline-block;
width: 10em;
float: left;
}
input[type="number"]
{
width: 3em;
}
button
{
display: inline-block;
width: 10em;
float: right;
}
</style>
And that's how it all looks like now.
We'll want some JavaScript here. Specifically, create the
renderSVG() function.
<script>
function renderSVG()
{
}
</script>
We want this function to run when the page loads.
<script>
function renderSVG()
{
}
$(document).ready(function() {
renderSVG();
});
</script>
And we also want to bind the execution to the click event of
btnPreview, when the page loads.
<script>
function renderSVG()
{
}
$(document).ready(function() {
renderSVG();
$("#btnPreview").click(function() {
renderSVG();
});
});
</script>
Now it's time to work on
renderSVG(). Declare
width,
height and
content. Set them to the values of these HTML elements. In the case of
width and
height, we want to coerce these values into numerical ones, using the
parseInt() function.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var content = $("#txtCode").val();
}
Here, we ensure that
svgResult's
width and
height attributes match the values of
width and
height.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var content = $("#txtCode").val();
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
}
Then we make sure that
svgResult's HTML content is
content. But we can't do it directly because it's an SVG. We have to first replace its HTML content with its HTML content, if that makes sense,
then set it using
content.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var content = $("#txtCode").val();
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(content);
}
Take a gander. When you reload the page now, the SVG is 100 by 100 pixels because those are the default values.
Change the number, click the Preview button, and watch the SVG change in size.
Add in some XML content, maybe a nice
orange circle. Click Preview, and
presto!
Adding a grid
It would be helpful to have some visual aids while creating the SVG. To that end, we'll add a customizable grid.
In the UI, we have
txtGrid, a numerical input with a minimum value of 10. This means that the smallest grid we can have here is 10 by 10 pixels.
<label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
<br />
<label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
<br />
<label for="txtGrid">Grid:</label><input type="number" id="txtGrid" min="0" value="10" />
<br />
<label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
<br />
And here's the new input.
In the JavaScript function
renderSVG(), we handle this inclusion by declaring
grid, and setting it to the value of the HTML element
txtGrid. We also declare
gridLines as an empty string. This will eventually be XML content. To that end, we will want to prepend
gridlines to
content when rendering the SVG.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var grid = parseInt($("#txtGrid").val());
var content = $("#txtCode").val();
var gridLines = "";
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}
We only want to create the grid if
gridLines is greater than 0.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var grid = parseInt($("#txtGrid").val());
var content = $("#txtCode").val();
var gridLines = "";
if (grid > 0)
{
}
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}
We'll then use a
For loop to iterate from
grid to
width, using the value of
grid as an incrementor...
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var grid = parseInt($("#txtGrid").val());
var content = $("#txtCode").val();
var gridLines = "";
if (grid > 0)
{
for (var i = grid; i < width; i += grid)
{
}
}
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}
...and with each iteration, use
i to add the XML for vertical line tags to
gridLines.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var grid = parseInt($("#txtGrid").val());
var content = $("#txtCode").val();
var gridLines = "";
if (grid > 0)
{
for (var i = grid; i < width; i += grid)
{
gridLines += '<line x1="' + i + '" y1="0" x2="' + i + '" y2="' + height + '" stroke="rgb(200,200,200)" stroke-width="1" />';
}
}
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}
Look at this!
Grey vertical lines.
Then we repeat this, using
height this time, for horizontal line tags.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var grid = parseInt($("#txtGrid").val());
var content = $("#txtCode").val();
var gridLines = "";
if (grid > 0)
{
for (var i = grid; i < width; i += grid)
{
gridLines += '<line x1="' + i + '" y1="0" x2="' + i + '" y2="' + height + '" stroke="rgb(200,200,200)" stroke-width="1" />';
}
for (var i = grid; i <= height; i += grid)
{
gridLines += '<line x1="0" y1="' + i + '" x2="' + width + '" y2="' + i + '" stroke="rgb(200,200,200)" stroke-width="1" />';
}
}
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}
Now we have a grid!
Adding objects
Time for a little value-add! We have the bare minimum - an interface to change the size and content of the SVG, and a grid to help draw. But what would add even more value?
Well, the ability to add pre-determined objects such as circles and lines to the SVG.
For that, let's add a series of buttons to the interface. Also add a checkbox with a label,
cbAppend.
<div class="panel">
<label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
<br />
<label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
<br />
<label for="txtGrid">Grid:</label><input type="number" id="txtGrid" min="0" value="10" />
<br />
<label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
<br />
<label for="cbAppend">Append?</label><input type="checkbox" id="cbAppend" />
<br />
<button>Add Line</button>
<button>Add Rect</button>
<button>Add Ellipse</button>
<button>Add Circle</button>
<button>Add Path</button>
<button>Add Text</button>
<br />
<hr />
<button id="btnPreview">Preview</button>
<br />
</div>
See the buttons and checkbox? Good.
Add an event to the first button. It will be a function call to
addNode(), with the argument "line".
<button onClick="addNode('line')">Add Line</button>
<button>Add Rect</button>
<button>Add Ellipse</button>
<button>Add Circle</button>
<button>Add Path</button>
<button>Add Text</button>
In the JavaScript, create two functions,
addNode() and
getDefaultNode(). Both of them have the parameter
nodeType.
function renderSVG()
{
var width = parseInt($("#txtWidth").val());
var height = parseInt($("#txtHeight").val());
var grid = parseInt($("#txtGrid").val());
var content = $("#txtCode").val();
var gridLines = "";
if (grid > 0)
{
for (var i = grid; i < width; i += grid)
{
gridLines += '<line x1="' + i + '" y1="0" x2="' + i + '" y2="' + height + '" stroke="rgb(200,200,200)" stroke-width="1" />';
}
for (var i = grid; i <= height; i += grid)
{
gridLines += '<line x1="0" y1="' + i + '" x2="' + width + '" y2="' + i + '" stroke="rgb(200,200,200)" stroke-width="1" />';
}
}
$("#svgResult").attr("width", width);
$("#svgResult").attr("height", height);
$("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}
function getDefaultNode(nodeType)
{
}
function addNode(nodeType)
{
}
$(document).ready(function() {
renderSVG();
$("#btnPreview").click(function() {
renderSVG();
});
});
Just for clarity, let's start on
getDefaultNode(). We use a
Switch statement on
nodeType, because there are many possible values. For now, there is only "line". Notice we don't use a
break statement here because all these cases return a value.
function getDefaultNode(nodeType)
{
switch (nodeType)
{
case "line":
default: return "";
}
}
In the case of "line", an
orange line tag is returned.
function getDefaultNode(nodeType)
{
switch (nodeType)
{
case "line": return '<line x1="0" y1="0" x2="300" y2="200" stroke="rgb(250,100,0)" stroke-width="2" />';
default: return "";
}
}
Now for
addNode(). We declare
node, and use
getDefaultNode(), passing in
nodeType as the argument, to get the XML value which we then assign to this variable. Then we declare
content, which is the text content of
txtCode, and
append, which is a Boolean depending on whether
cbAppend is checked.
function addNode(nodeType)
{
var node = getDefaultNode(nodeType);
var content = $("#txtCode").val();
var append = $("#cbAppend").prop("checked");
}
If
append is
true, that means we want
node to be appended to
content. We'll set
txtCode's text content as that. If not, then we prepend
node to
content.
function addNode(nodeType)
{
var node = getDefaultNode(nodeType);
var content = $("#txtCode").val();
var append = $("#cbAppend").prop("checked");
if (append)
{
$("#txtCode").val(content + node);
}
else
{
$("#txtCode").val(node + content);
}
}
After that, the updated code should be in
txtCode. We'll run
renderSVG().
function addNode(nodeType)
{
var node = getDefaultNode(nodeType);
var content = $("#txtCode").val();
var append = $("#cbAppend").prop("checked");
if (append)
{
$("#txtCode").val(content + node);
}
else
{
$("#txtCode").val(node + content);
}
renderSVG();
}
See what happens when I click the Add Line button? The code appears in the text box and an
orange line appears in the SVG!
You'll notice that right now, it's not immediately apparent whether the line tag is appended or prepended. Let's just add the rest of the code. These are all the other tags that can be added. You may want to add your own.
function getDefaultNode(nodeType)
{
switch (nodeType)
{
case "line": return '<line x1="0" y1="0" x2="300" y2="200" stroke="rgb(250,100,0)" stroke-width="2" />';
case "rect": return '<rect width="200" height="100" x="10" y="10" rx="20" ry="20" fill="rgb(250,100,0)" />';
case "ellipse": return '<ellipse rx="100" ry="50" cx="120" cy="80" fill="rgb(250,100,0)" stroke="rgb(250,100,0)" stroke-width="3" />';
case "circle": return '<circle r="45" cx="50" cy="50" fill="rgb(250,100,0)" />';
case "path": return '<path d="M150 5 L75 200 L225 200 Z" fill="none" stroke="rgb(250,100,0)" stroke-width="3" />';
case "text": return '<text x="5" y="15" fill="rgb(250,100,0)">abc 123</text>';
default: return "";
}
}
In the HTML, add the function calls.
<button onClick="addNode('line')">Add Line</button>
<button onClick="addNode('rect')">Add Rect</button>
<button onClick="addNode('ellipse')">Add Ellipse</button>
<button onClick="addNode('circle')">Add Circle</button>
<button onClick="addNode('path')">Add Path</button>
<button onClick="addNode('text')">Add Text</button>
Now add a rect tag and then a line, by clicking the buttons. Note that the line is prepended to the rect because the checkbox is not checked.
Check the checkbox and add a path tag. There, now you see that the path is appended.
How useful is it?
Well. I think it's useful. And of course I would, I made it! I have consciously avoided adding too many features to it precisely because the objective was to keep the interface as simple as possible while still incorporating features that are frequently used. Obviously, this varies from individual to individual.
<text>Till next time!</text>
T___T