3

I'm building a photo application using electron that loads user photos from the file system. These photos can easily be 7MB or more in size. The application allows the user to switch between photo's using the keyboard arrows, at which point I want the new photo to display extremely fast.

For a 7MB image, just changing the src of an existing image tag in the DOM can take ~200-300ms, webkit must load the file, decode the file, and render the file on the page. The loading and decoding take 100-150ms each. (actually the profiler just says 2 x decoding, but the next step removes one of those decodes, so I presume it's related to the file read).

Preloading an img tag...

var img = new Image();
img.src = "/path/to/photo.jpg"

...means that webkit preloads the file, and this strips the file load time, but there is still a 100-150ms delay in appending like this...

domElement.appendChild(img);

devtools timeline

...because the read data must still be decoded for the item to be appended to the DOM.

Is there a way to pre-decode the image, so that appending to the DOM does not have a 100-150ms delay, and only the fast rendering is required?

Dan
  • 2,830
  • 19
  • 37

2 Answers2

2

No you cannot "pre-decode". However, you can pre-append the img in an effectively invisible way by applying the style width: 1px; height: 1px; opacity: 0.01, and webkit won't redo the work if you append again.

You can even remove the image in the mean time, provided it has had time to fully decode, and webkit will hold on to the decoded data (although I'm not sure for how long).

If you want to be absolutely certain it will load fast, you could do one of two things. Reveal the img tag by removing the styles above, or by loading the same img tag in a different part of the DOM, while leaving the 'pre-appended' one in place. This will take between 3ms and 20ms in my experience.

BE CAREFUL regarding cleanup if you are using a lot of user defined photo contents.

In my experience, simply removing numerous img elements from the DOM will cause memory leaks (and electron windows to crash). I would advise that you either set the img.src to null after removing the image from the DOM, or set the entire img to null if you no longer need the Image instance.

You could play with the following code (use images of your own) using the chrome devtools timeline to measure the render speeds of photos in different scenarios.

<style>
/*#preload img {
  width: 1px;
  height: 1px;
  opacity: 0.01;
}*/
</style>

<button>Toggle Image</button>

<div id="container"></div>

<div id="preload"></div>

<script>
  "use strict"

  const button = document.getElementsByTagName('button')[0]
      , container = document.getElementById('container')
      , preload = document.getElementById('preload')
      , img1 = new Image()
      , img2 = new Image()
  var current = img2

  img1.src = './img1.JPG'
  img2.src = './img2.JPG'

  preload.appendChild(img2)
  setTimeout(function(){
    preload.removeChild(preload.childNodes[0])
  }, 200)

  button.addEventListener('click', function(e){
     toggleImg()
  })

  function toggleImg(){
    if (current === img1) {
      setImg(img2);
    } else {
      setImg(img1)
    }
  }

  function setImg(img){
    if (container.hasChildNodes()) container.removeChild(container.childNodes[0])
    container.appendChild(img)
    current = img
  }
</script>
Community
  • 1
  • 1
Dan
  • 2,830
  • 19
  • 37
1

I'd suggest experimenting with using the HTML5 Canvas to render your images, that way you can load the next image ahead of time and have more direct control over the caching strategy.

Vadim Macagon
  • 14,463
  • 2
  • 52
  • 45
  • That is certainly something to look into. I believe I had previously had a quick play, but I can't remember the results. How would canvas provide "more direct control"? – Dan May 19 '16 at 03:24
  • @Dan You can decide what to preload and when without having to juggle a bunch of `img` elements in the DOM. Technically you'd still have to manage a bunch of `img` elements, but instead of appending and removing them to/from the DOM you'd draw them on the canvas. – Vadim Macagon May 19 '16 at 05:02