How I Built an HTML5 Comic Book Reader
Join the DZone community and get the full member experience.
Join For Freefollowing up on my sunday blog post on comics, i thought it would be fun to share a little experiment i built this weekend. comic books are available in a compressed format typically called cbrs or cbzs. these aren't a special format, just simple compressed archives. cbrs are rar files and cbzs are zips. while there doesn't appear to be good support for rar files (i've only found a java library to list the contents), the zip format is much more widely used and easy to work with. in fact, you can find an excellent javascript implementation: zip.js i thought it might be fun to try using that to build my own web-based cbz reader. here's how i did it.
first, i added drag/drop support to my application so that users could simply drag in their local cbz files. instead of a div, i made the entire page a target for drop events:
$(document).on("drop", drophandler);
the drophandler needs to do a few things. first - it needs to figure out the type of drop. don't forget that people can drag/drop blocks of text from other applications. what i want is to listen for files. even better - i need to ensure that one file is dropped, not multiple. here's the snippet for that logic.
function drophandler(e) { e.stoppropagation(); e.preventdefault(); if(!e.originalevent.datatransfer.files) return; var files = e.originalevent.datatransfer.files; var count = files.length; if(!count) return; //only one file allowed if(count > 1) { doerror("you may only drop one file."); return; } handlefile(files[0]); }
ok, now for the fun part. my application needs to try to decompress the zip file to the file system. in order to do that i am making use of the html5 file api. earlier on i did a quick request for some temporary file storage - which is pretty simple:
window.webkitstorageinfo.requestquota(window.temporary, 20*1024*1024, function(grantedbytes) { window.webkitrequestfilesystem(window.temporary, grantedbytes, oninitfs, errorhandler); }, errorhandler);
having the file system means i can extract the images out of the zip into the directory and refer to them later. we have access to the file from the drop event, so it is a simple matter of:
- pass the file data to zip.js
- extract the files
- save the files (again, this is a temporary file system)
- store a reference to them so i can display them to the user
here is the function that handles all of that.
function handlefile(file) { zip.workerscriptspath = "js/"; zip.createreader(new zip.blobreader(file), function(reader) { console.log("did create reader"); reader.getentries(function(entries) { console.log("got entries"); $("#introtext").hide(); //start a modal for our status var modalstring = 'parsed the cbz - saving images. this takes a <b>long</b> time!'; $("#statusmodaltext").html(modalstring); $("#statusmodal").modal({keyboard:false}); entries.foreach(function(entry) { if(!entry.directory && entry.filename.indexof(".jpg") != -1) { //rewrite w/o a path var cleanname = entry.filename; if(cleanname.indexof("/") >= 0) cleanname = cleanname.split("/").pop(); dir.getfile(cleanname, {create:true}, function(file) { console.log("yes, i opened "+file.fullpath); images.push({path:file.tourl(), loaded:false}) entry.getdata(new zip.filewriter(file), function(e) { done++; //$("#statusmodaltext").html("did "+done+" images out of "+images.length); var perc = math.floor(done/images.length*100); var pstring = 'processing images.'; pstring += '<div class="progress progress-striped active">'; pstring += '<div class="bar" style="width: '+perc+'%;"></div>'; pstring += '</div>'; $("#statusmodaltext").html(pstring); for(var i=0; i<images.length; i++) { if(images[i].path == file.tourl()) { images[i].loaded = true; break; } } if(done == images.length) { $("#statusmodal").modal("hide"); //enable buttons $("#buttonarea").show(); $("#prevbtn").on("click",prevpanel); $("#nextbtn").on("click",nextpanel); drawpanel(0); } }); },errorhandler); } }); }); }, function(err) { doerror("sorry, but unable to read this as a cbr file."); console.dir(err); }); }
once done - that leaves us with the simple job of providing basic interaction with the images. this is done via buttons that allow for navigation.
function drawpanel(num) { curpanel = num; $("#comicimg").attr("src",images[num].path); $("#panelcount").html("panel "+(curpanel+1)+" out of "+images.length); } function prevpanel() { if(curpanel > 0) drawpanel(curpanel-1); } function nextpanel() { if(curpanel+1 < images.length) drawpanel(curpanel+1); }
and that's it! before i link to the demo, i'll warn you that this is not very tolerant of browsers that don't support everything required. here are a few screen shots though to give you an idea of how it works.
first up - the application as it looks on loading.
next - i drag a cbz file over it...
and then it gets to work. now - this part can be a bit slow. to be fair, i dragged a 35 megabyte file into the browser and it took about 40 seconds to parse. i think that's fairly decent for javascript.
i also provide ui feedback as the images are saved.
and then finally - the comic is readable. (whether or not the story is any good is another question.)
want to try it out? hit the demo link below. note that you may want to try with the latest chrome and with a small comic. i've created a simple "comic" out of a zip of pictures that can be downloaded here .
Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Playwright JavaScript Tutorial: A Complete Guide
-
SRE vs. DevOps
-
Managing Data Residency, the Demo
-
What Is mTLS? How To Implement It With Istio
Comments