Статьи

Как я построил читатель комиксов HTML5

В продолжение моего воскресного поста о комиксах я подумал, что будет интересно поделиться небольшим экспериментом, который я построил в эти выходные. Комиксы доступны в сжатом формате, обычно называемом CBR или CBZ. Это не специальный формат, это просто сжатые архивы. CBR — это файлы RAR, а CBZ — это почтовые индексы. Несмотря на то, что поддержка файлов RAR, похоже, не поддерживается (я нашел только библиотеку Java для перечисления содержимого), формат Zip гораздо более широко используется и с ним легко работать. На самом деле, вы можете найти отличную реализацию JavaScript: zip.js Я подумал, что было бы забавно попробовать использовать это для создания моего собственного веб-ридера CBZ. Вот как я это сделал.

Сначала я добавил поддержку перетаскивания в свое приложение, чтобы пользователи могли просто перетаскивать свои локальные файлы CBZ. Вместо div я сделал целую страницу мишенью для событий перетаскивания:

$(document).on("drop", dropHandler);

DropHandler нужно сделать несколько вещей. Первое — нужно выяснить тип сбрасывания. Не забывайте, что люди могут перетаскивать блоки текста из других приложений. Я хочу слушать файлы. Еще лучше — мне нужно убедиться, что один файл удален, а не несколько. Вот фрагмент этой логики.

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]);
 }

 Хорошо, теперь самое интересное. Мое приложение должно попытаться распаковать zip-файл в файловую систему. Для этого я использую HTML5 File API. Ранее я сделал быстрый запрос на временное файловое хранилище, что довольно просто:

window.webkitStorageInfo.requestQuota(window.TEMPORARY, 20*1024*1024, function(grantedBytes) {
window.webkitRequestFileSystem(window.TEMPORARY, grantedBytes, onInitFs, errorHandler);
}, errorHandler);

Наличие файловой системы означает, что я могу извлечь изображения из архива в каталог и обратиться к ним позже. У нас есть доступ к файлу из события drop, так что это просто вопрос:

  • Передайте данные файла в zip.js
  • Распакуйте файлы
  • Сохраните файлы (опять же, это временная файловая система)
  • Сохраните ссылку на них, чтобы я мог отобразить их пользователю

Вот функция, которая обрабатывает все это.

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);
	});

}

После того, как это сделано, у нас остается простая задача — обеспечить базовое взаимодействие с изображениями. Это делается с помощью кнопок, которые позволяют для навигации.

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);
}

Вот и все! Прежде чем я сделаю ссылку на демо, я предупреждаю вас, что это не очень терпимо к браузерам, которые не поддерживают все необходимое. Вот несколько снимков экрана, чтобы дать вам представление о том, как это работает.

Сначала — приложение, как оно выглядит при загрузке.

Далее — я перетаскиваю файл CBZ поверх него …

И тогда он начинает работать. Теперь — эта часть может быть немного медленной. Честно говоря, я перетащил 35-мегабайтный файл в браузер, и на его анализ ушло около 40 секунд. Я думаю, что это довольно прилично для JavaScript.

Я также предоставляю обратную связь UI, поскольку изображения сохраняются.

И, наконец, комикс читабелен. (Является ли история хорошей, другой вопрос.)

Хотите попробовать это? Нажмите на демонстрационную ссылку ниже. Обратите внимание, что вы можете попробовать последнюю версию Chrome и небольшой комикс. Я создал простой «комикс» из архива картинок, который можно скачать здесь .