Comic-book viewer, Java Spring project

This is maybe more Javascript than Java at this point? I’ve been working on a comic book viewer. There’s a lot to do still, but the main design goals I had seem to be working. Curious if anyone is willing to give it a go, give feedback.

My main design thought was to try to maintain a sense of the page. I saw one viewer that would expand each frame up to the entire screen, and even though the image was nice and large, not knowing what was around it kind of didn’t feel right for a comic-book reading experience, to me. Maybe others disagree?

The second design thought was to make the viewing as mindless as possible, so that one isn’t distracted from the story while trying to navigate the panels. So, I set things up where the viewer “advances” to the next view automatically when you click “Next” or hit or . Getting the viewer to make good choices was a bit of a geometric-puzzle challenge.

I’ve only implemented 4 zoom levels ("+" and “-” buttons on top, or ), and 6 viewer-portal sizes (code automatically attempts to ‘best fit’ the current browser dims, but at this point they are all landscape oriented). I want to also get some touchscreen controls working (drag for repositioning, swipes, pinches for zooming). Most of all this has been written with Javascript. Gritting my teeth and getting some experience working with it. (Many functions, and tests for those functions, has been very helpful.)

The Java part is the interaction with the database, where we have images and panels and their dimensions organized, and using the controller code for maintaining state when changing pages. I also made an auxiliary Java app for encoding the panel locations and ordering.

Eventually, the comic book author will have his own site with links to the viewer (it’s very bare bones at this point), so only those links will require a port# in the address.

Also, I’ve put very little creative effort into the GUI for the viewer. That is not something I feel particularly good about designing, in terms of the look.

1 Like

Since this is still in early development you might already be aware of these issues, but just in case you are not:

Is the viewer not centered on the page on purpose? I’m on a 27’ 1440p monitor, so the viewer is in the very top left of the page. Doesn’t make for the best viewing experience. But maybe big screens are not your target audience?

Also, if you click on the zoom buttons ( + / - ) too quickly, without waiting for the zoom animation to complete, the buttons stop working entirely.

1 Like

Thanks! Yes, both these matters need to be addressed. I was aware but hadn’t dealt with them yet. Need to make a formal list for project-management purposes. It’s helpful to have it confirmed/reminded.

I’m in the middle of adding two portrait-oriented viewers. Adding them is not so difficult, but I think some down-stream logic will require re-entry of the “panel” data for the page. The program stalls (“prev” “next”) if it can’t zoom to a level that allows a full “panel” to show.

I think maybe something that attempts to maintain a zoom level might be good, too. So, if the “next” requires a zoom out, the following “next” attempts to zoom back to the initial level if the panel size allows it.

Some fixes and progress. I think I got the two suggestions from @Gjaller covered now. Still want to get to implementing touch screen gestures/mouse dragging, etc. But the basic mechanics of the on-screen buttons work pretty smoothly, it seems to me.

A couple problems with the tiny reader sizes remain, as the panel data needs to be re-entered to accommodate the minimum reader size.

Meanwhile, I’ve tasked the comic book artist with looking for other comic book readers for comparisons. I’ve found a couple (on the Libby library app, Kanopy, Hoopla).

I did some refactoring to try to clarify what is used for positioning calculations and what is used for actually changing the GUI. I can’t say it was a total success, but I think it is clearer than before.

The usual warning is “will you be able to read your code six months from now?” I think that is a “young brains” trope. If instead “two weeks,” the warning would still be perfectly valid.

There have been some improvements to the comic book reader. The latest in-progress version is viewable from Comments and suggestions always much appreciated!

Current question: I’m pondering about where to put the comic book page images, going forward. If I continue to store them as resources within the Spring Boot project, the jar file will soon become humongous. I can buy a few months to solve this problem by continuing to put jpgs in the resource directory, for now, but I expect this to soon become a real problem.

I could store and address the images at a static locations on my adonax site, for example, …/issue1/pg2, …/issue2/pg1, etc. But is there a way to make those images only visible via some sort of authorization? I’d prefer to not allow hot-patching, if possible.

Another possibility, I guess, would be to store the images in a database–I’m using MySQL. I’m assuming some sort of Blob format would work for jpgs, (or maybe I will first convert the jpgs to avif or webp). Am wondering about the performance costs of having blobs fetched from the database, and the coding involved.

Are there other options to consider?

It just occurs to me, maybe there is a way to address private locations on the linode server from the Spring Boot application?

Can you tell I’m new to this?

IMHO, it would be better to download the whole comic to the browser at the start and then navigate offline without fetching data from the server. Full page requests when changing pages cause annoying pauses. In the era of single-page applications, it’s a little archaic. If you don’t want to fetch all at once - you can fetch in background one page forward or something.

Storage question - it depends. Are these comics a part of deployment? If so, you can keep them in your jar. You can also keep them nearby your application on the server filesystem. You can serve it from your Spring app backend and disable direct access from the browser.

If you want to be more flexible - go with SQL if you are using it. It will be ok for storing such blobs. If these blobs would be big (many comics, each of size like - hundred of MBs) for some reason (I don’t think that these will) I would go for some cloud file storages (AWS S3 for example) as it is scaling better and it will be cheaper (but probably slower).

What do you mean?

1 Like

One of the comic-book readers I’m using as a comparison is by InterpopComics. I see that they download the entire comic at the beginning of the session. You are probably right about doing so, or about, at the least, downloading the likely “next page” in the background. I will give those alternatives some serious thought.

My concern about keeping the comics as part of the deployment is that as there are more of them, the deployment jar could get kind of huge. Some of this could be alleviated by using a compressed image format like WebP, I am now reading. But even there, this wouldn’t save anything in terms of deployment jar size if the JPG also has to be uploaded for backwards compatibility on older browsers. I’m curious, what are considered acceptable file sizes for deployment jars? I’m currently at 80MB which is no problem. A jar file of 800MB, would that also be fine? 8GB? (I don’t see hitting that size any time soon!)

You wrote “you can also keep them nearby your application on the server filesystem.” This is what I was trying to articulate when I wrote “maybe there is a way to address private location on the linode server…” I’m using linode for my server host (same as uses, AFAIK!). The deployment app lives at /var/lib/moby. So something like placing the graphics in a subfile system is what I’m wondering about: /var/lib/moby/graphics/issueX/pageX. I know I can set permissions on the directories. I just am not clear on how from within Spring I address the deployment server locations. I will see what I can find in search, since you do say this is a possibility. Come to think of it, the tutorial I’ve been working through (a udemy course of 300 videos!) has us upload graphics to a file location by the deployment file location. I probably just need to reread that section!

Until there is actual stress on my linode account by popular demand (which may never occur), I’m going to take a pass on AWS or other cloud file storages.

Thanks for the input!

JAR is just a ZIP with Java classes and resources. The problem is rather different - do you want to reupload 8GB of Comics because you’ve changed one line of code? I am sure that you don’t want to do this :smiley:

Spring(boot) applications are just like any other java application (any other app at all). What you want to do is to:

  1. put a jar in the server
  2. make a directory with comics somewhere on the server
  3. add sufficient permissions to let your app access it
  4. block http access to these directories (if they are not blocked)
  5. make some endpoints in your spring app and make it read the file returning it back in the http response. If you make your app as SPA (Single Page Application) with some Angular/Reactjs (or even simpler AJAX requests) + REST API on your backend side - it will be much easier for you to fight with slow page loading problems.

Some tutorial to start with rest API and async client requests:

If you want to learn frontend for some reason - Angular or React.js will be better (but more complicated). They are using AJAX under the hood. I assume that you don’t want to fall into next complex tech so AJAX will be good at start :slight_smile:

1 Like


Thanks for the tutorial link. I’ve been working with the author Nam Ha Minh (of who I have found to be one of the more reliable tutorial and udemy course authors, but am pleased to have this as a backup reference.

I haven’t made a final decision about diving into React or Angular. Much of what I’m doing is in part an effort to make myself more employable, so there’s that consideration. Picking one or the other could be valuable. To date, I’ve been using Thymeleaf in combo with straight JS or JS enhanced with jQuery or Bootstrap. Nam Ha Minh’s Udemy course makes use of Bootstrap, so I’ve had to get some facility with it. But I think at this stage I learn more when I write my own CSS/JS solutions. So the ComicBook reader project, which doesn’t follow any specific tutorial or course, doesn’t even use jQuery.

I’m going to get the adjacent file folders for images working first (hopefully the next few days). I think I can use a relative form of addressing and thus have both dev copies and production copies of the graphics. I will likely be back here asking questions when dealing with figuring out how to integrate background loading with the current page-based controller setup. It will probably entail rewriting/reconceptualization what I have.

1 Like

After getting the adjacent file folder for images working, I decided to code fullscreen mode. It turns out it is really easy to implement. BUT…I just learned why single screen web apps are so popular: when you load a new page, even if you were in full screen on the previous page, continuing in full screen mode is not allowed! Nor does sending down a boolean via Thymeleaf that directs the javascript to go into fullscreen mode work.

API can only be initiated by a user gesture.

Forced to rethink page navigation now.

I don’t think I want to make the user sit there waiting for all the images to load before the app opens. Some comic books / graphic novels have quite a few pages. Makes more sense to send down an array of img urls, yes? We can still cache as we go with this plan. Ah…but I will have to either load all the panel local data, or set up some sort of Spring Service or AJAX function to get the panel data. Still coming to grips with the implications. It seems to me that having full screen mode is a necessity.

If these comic-books are a set of images that you can send one by one - you can make it like this approach with AJAX for example (as you’ve mentioned) or you can go with something like SSE.

I had a similar problem once (but with GBs of data) - that the user would have to wait for the whole payload to be retrieved. I could split my data into chunks and keep sending them as SSE so frontend could read and display these chunks as soon as they came. It’s usually used to notify clients about server state change (and I used it like that too).

It may be something for you. It’s an HTTP request under the hood with a different content type and ready-to-use APIs on both sides.

1 Like

Thanks again for the links. Interesting to read about these techniques. For starters, though, I’m trying a pretty simple method for preloading images. Whenever we turn a page forward, I run a preloader after a slight delay (giving the page-turn code time to finish).

setTimeout(preloadImage, 250, preloadURL);	


function preloadImage(preloadURL) {
    var img = new Image();
    img.src = preloadURL;

I think it’s helping, but It’s kind of hard to verify what exactly is happening. There’s also issues remaining with the way zooms and fades and translations are not working together consistently. But at this point, these are Javascript problems, not Java. These issues don’t involve the back end. So I’ll take my questions on this stuff to StackOverflow or SitePoint.