Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
First, I have to come clean. When [Scott tweeted about wanting a web app version of Grunticon](https://twitter.com/scottjehl/status/345618924219539458) a couple of weeks ago, I had never used Grunticon. Actually, still to this day I've never used Grunticon. Shocking...I know.
Of course, I had heard of it. I am a big proponent of using Grunt and use it everywhere to help automate my life. And I follow enough people on Twitter/read enough blogs/listen to enough podcast to have gotten ample exposure to Grunticon and it sounded really cool. So when I saw Scott's tweet I took to action.
Before reading on, if you're not familiar with [Grunticon](http://filamentgroup.com/lab/grunticon/) or [Grumpicon](http://www.grumpicon.com/), check those out first.
Here are kind of the steps on how Grumpicon works:
1. Handle drag and dropped SVG files
2. Read the SVGs
3. Covert SVGs to data URIs
4. Covert SVGs to PNG data URIs
5. Make CSS files and the preview.html file
6. Create a downloaded zip with all the files and PNG images
Sounds easy enough, right?
##Handle drag and dropped SVG files
The drag and drop API has a few nifty events for handling dropping files onto a web app. From Grumpicon we really just use two: `drop` and `dragover`. (Okay, so technically `dragleave` is also used just for removing the page highlighting.) With the dragover event we can add a highlight to the page and tell it to "copy" the file when dropped (instead of opening it). The drop event has a `dataTransfer` property that contains `files` which is a FileList array on all the files dropped. From there we can do what we want with the files.
Some pseudo code from Grumpicon (check out the [Grumpicon GitHub repo](https://github.com/filamentgroup/grumpicon) for the actual code):
Views.UploadView = Backbone.View.extend({
events: {
"drop": "fileDrop",
"dragover": "fileDrag"
},
fileDrag: function(e) {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
// add a class to the body to highlight
},
fileDrop: function(e) {
e.preventDefault();
e.stopPropagation();
// note, jQuery doesn't normalize the "drop" event
// so we have to access originalEvent to get to the dataTransfer property
this.addFiles(e.originalEvent.dataTransfer.files);
},
addFiles: function(fileList) {
// do something with the files
}
});
##Read the SVGs
To do anything with the dropped SVGs we need to access the content of the files. Grunticon generates data URIs with escaped SVG text as background images, we just need to read the text of the files. This is where the File API comes in to play and specifically the `FileReader` object.
Reading a file is a lot like create a new image. First you create a `new FileReader()`, then you set an `onload` event to do something with the content once it has been reader, and finally you tell the reader to read a file as text.
More pseudo code:
read: function() {
var model = this,
file = model.get("file"),
reader = new FileReader();
reader.onload = function(e) {
var svgText = e.target.resutls;
//do stuff with the svgText
};
reader.readAsText(file);
}
##Covert SVGs to data URIs
This step is actually super simple. Just escape the `svgText` read from the file, tack on `"data:image/svg+xml;charset=US-ASCII,"` to the beginning and blammo.
##Covert SVGs to PNG data URIs
Another amazing HTML5 API saves the day. The `canvas` has a nice little method called `toDataURL()` which takes the contents of a canvas and generates a PNG data URI...exactly what we want. So the only trick here is drawing the SVG onto a canvas.
Luckily, it was [canvg](https://code.google.com/p/canvg/) to the rescue. This small library makes drawing a SVG on a canvas super easy. Then `toDataURL()` can be called on the canvas.
canvg(canvas, svgText);
var pngDataUri = canvas.toDataURL();
##Make CSS files and the preview.html file
The 3 CSS files and the HTML file are just made on the page using Underscore templates using the data from the dropped SVGs and the data URIs generated.
##Create a downloaded zip with all the files and PNG images
When I first started thinking about how to make Grunticon work in the browser, this was the step I was most unsure of. Creating zips? Node sure, but on the client...
[JSZip](http://stuk.github.io/jszip/) - another library to the rescue. It's a truly remarkable library. You can add all type of files to your zip, including images from a data URI (perfect for our needs), and even create folders.
var view = this,
zip = new JSZip(),
// create folder
img = zip.folder("png");
view.collection.each(function(model) {
// add images to the folder
img.file(model.get("name") + ".png",
model.get("pngDataUri").replace("data:image/png;base64,","") + "\n",
{base64: true});
});
// add the other files
zip.file("icons.data.svg.css", $("#svg-css-results").text());
zip.file("icons.data.png.css", $("#png-css-results").text());
zip.file("icons.fallback.css", $("#fallback-css-results").text());
zip.file("grunticon.loader.txt", ...);
zip.file("preview.html", ...);
// create the zip
var content = zip.generate();
// make the browser download it
location.href = "data:application/zip;base64," + content;
So that's basically it. To see the actual source, check out the [Grumpicon repo](https://github.com/filamentgroup/grumpicon) on GitHub.
P.S. I had an amazing time working on this app with the team from Filament Group. It's the most fun I've had developing anything in a quite a while!
---
###All of JS Libraries used (Thank you open source!)
* [RequireJS](http://requirejs.org/)
* [Backbone](http://backbonejs.org/), [Underscore](http://underscorejs.org/), and [jQuery](http://jquery.com/)
* [canvg](https://code.google.com/p/canvg/)
* [JSZip](http://stuk.github.io/jszip/)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.