1

I've got a fun little widget that invites users to choose from a list of 114 animals. The UI includes small thumbnails of each species, all the same size (100x70). The sum total of the thumbnails is 2.1M.

I'm not eager to make 114 server requests, so I used ImageMagick to make one VERY long image (11400x70), which comes down to 960K. So the challenge is to chop up that long image into 114 small images on the client. (EDIT: I need each animal as its own image because the UI filters the list down, e.g. "show me only herbivores").

I originally had a convoluted approach where I painted that large image to a canvas after loading a base64 string of the combined image, pulled the ImageData for each animal, painting THAT to a second canvas, and then pulled the dataURL:

var combined_canvas = document.createElement('canvas');
var combined_ctx = combined_canvas.getContext('2d');

var indv_canvas = document.createElement('canvas');
var indv_ctx = indv_canvas.getContext('2d');    

combined_canvas.width = 11400; combined_canvas.height = 70;
indv_canvas.width = 100; indv_canvas.height = 70;

var image = new Image();
image.onload = function() {
    combined_ctx.drawImage(image, 0, 0); // paint combined image

    // loop through the list of 114 animals
    Object.keys(master_list).forEach((d, i) => {
        // get the image data just for this animal in sequence
        var imageData = combined_ctx.getImageData(i * 100, 0, 100, 70);
        // paint that image data to the second canvas
        indv_ctx.putImageData(imageData, 0, 0); 
        var img = document.createElement('img'); 
        img.src = indv_canvas.toDataURL();
        document.getElementById("animal_thumbnails").appendChild(img);
    });
};
image.src = combined_images; // the base64 of the combined image

Naturally, it would be MUCH easier to make 114 CSS Sprites, which would also prevent a small loss in quality from the way Canvas paints images. But what I don't understand well is how browsers store the same image placed many times with different offsets.

In the event that the client duplicates the combined image in memory, I'm looking at about 100Mb for all these thumbnails. I would HOPE even the worst browser is smarter than that, but that's a degree too far under the hood for me to know.

The reason I'm loading the image as base64 is that one other idea I had was to decode the base64 with atob() and chop it up with jpg-js, per this Stackoverflow suggestion. But jpg-js adds about 300K when minified and I'm not confident it was ever really intended for the client.

Chris Wilson
  • 6,599
  • 8
  • 35
  • 71
  • 1
    You wouldn't make 114 CSS sprites. A CSS sprite is exactly what you did, many images combined in one. Now all you have to do is set this one image as a div's background, with the div being 70x70px, and move the background to display the image you want. The div acts like a window. – Jeremy Thille Jan 09 '19 at 14:29
  • Right, but I need each animal as its own node for filtering (e.g., show me only carnivores), so I would need 114 divs, right? – Chris Wilson Jan 09 '19 at 14:33
  • Your question seems browser dependent. Are you asking about a particular set of browsers, or about common browser implementations? – Nino Filiu Jan 09 '19 at 14:38
  • The latter -- mainly what the convention is for placing the same image on a page many many times. – Chris Wilson Jan 09 '19 at 14:41

1 Answers1

0

Regarding the memory use, please see this post and this post

So the challenge is to chop up that long image into 114 small images on the client.

What is the need to chop the long image. I would keep it as a single image and use as CSS sprite and show all images.

If your images has fixed dimension a JavaScript loop could render all images in the page. Or you could use a tool like this to get animal position and have it in a css file. But I would prefer the JS loop method.

The code would be something like this

var imageItem = "";
var x = 0;
for (var i = 0; i < 144, i++) {

  imageItem += "<li style='background-position: "+ x + " 0'></li>";
  x = x + 100; // this is the width of the each image
}

someElement.innerHtml = "<ul class='img-container'>" + imageItem + "</ul>";

In CSS

.img-container li {
  width: 100px;
  height: 70px;
  background-image : url('../your-long-image.jpg');
  background-repeat: no-repeat;
}

See this example, I am using only a single image, I am changing the offset position to show different locations from the same image.

var imageItem = "";
var x = 0;
for (var i = 0; i < 3; i++) {
      
      imageItem += "<li style='background-position: "+ x + "px  0'>ss</li>";
      x = x - 125; // this is the width of the each image
}

    document.getElementById("container").innerHTML = "<ul class='img-container'>" + imageItem + "</ul>";
.img-container li {
      width: 125px;
      height: 200px;
      border: 1px solid green;
      background-image : url('https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/intermediary/f/c9702304-c06d-4500-90b7-b6b6168093f8/db9n9km-ee485eb1-1ecc-4cd8-b845-f16900bde9e0.png');
      background-repeat: no-repeat;
      float:left;
      list-style: none;
    }
<div id="container"></div>
kiranvj
  • 32,342
  • 7
  • 71
  • 76
  • Just to be clear, the loop would create a new Image element on each iteration, set it to the combined image, and set the style to offset correctly? – Chris Wilson Jan 09 '19 at 14:37
  • There is no image element, we use background-image to show the image, we change background-position to show diffent image. – kiranvj Jan 09 '19 at 14:45
  • Right, of course. But question remains: Does using the same image 114 times with different positioning treat it as a new image in memory? – Chris Wilson Jan 09 '19 at 14:50
  • @ChrisWilson since it is new element for each animal, it will have some memory utilization in browser, but the image info will be stored only once. So it will be considered as 1 image NOT 144 images. I have added an example – kiranvj Jan 09 '19 at 14:57