Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a `loadedmetadata` for html5 img?

I'm rendering large images which are streamed natively by the browser.

What I need is a Javascript event that indicates that the image's dimensions were retrieved from its metadata. The only event that seems to be firing is the onload event, but this is not useful as the dimensions were known long before that. I've tried loadstart but it does not fire for img elements.

Is there a loadedmetadata event for the img element in html5?

like image 596
Tom Avatar asked Jan 17 '20 21:01

Tom


1 Answers

There is not an equivalent of loadedmetadata for img elements.

The most updated specs at the time of writting are the w3 Recommendation (5.2) (or the w3 WD (5.3)) and the WHATWG Living Standard. Although I find easier to browse through all the events in MDN; their docs are more user friendly.

You can check that loadedmetadata is the only event related to metadata and that it applies just to HTMLMediaElements.

You could take advantage of the Streams API to access the streams of data, process them and extract the metadata yourself. It has two caveats, though: it is an experimental technology with limited support and you will need to look for a way to read the image dimensions from the data stream depending on the image format.

I put together an example for PNG images based on MDN docs.

Following the PNG spec, the dimensions of a PNG image are just after the signature, at the beginning of the IHDR chunk (i.e., width at bytes 16-19, height at 20-23). Although it is not guaranteed, you can bet that the metadata of every image format is available in the first chunk that you receive.

const image = document.getElementById('img');

// Fetch the original image
fetch('https://upload.wikimedia.org/wikipedia/commons/d/de/Wikipedia_Logo_1.0.png')
  // Retrieve its body as ReadableStream
  .then(response => {
    const reader = response.body.getReader();
    return stream = new ReadableStream({
      start(controller) {
        let firstChunkReceived = false;
        return pump();

        function pump() {
          return reader.read().then(({
            done,
            value
          }) => {
            // When no more data needs to be consumed, close the stream
            if (done) {
              controller.close();
              return;
            }
            // Log the chunk of data in console
            console.log('data chunk: [' + value + ']');
            // Retrieve the metadata from the first chunk
            if (!firstChunkReceived) {
              firstChunkReceived = true;
              let width = (new DataView(value.buffer, 16, 20)).getInt32();
              let height = (new DataView(value.buffer, 20, 24)).getInt32();
              console.log('width: ' + width + '; height: ' + height);
            }
            // Enqueue the next data chunk into our target stream
            controller.enqueue(value);
            return pump();
          });
        }
      }
    })
  }).then(stream => new Response(stream))
  .then(response => response.blob())
  .then(blob => URL.createObjectURL(blob))
  .then(url => console.log(image.src = url))
  .catch(err => console.error(err));
<img id="img" src="" alt="Image preview...">

Disclaimer: when I read this question I knew that the Streams API could be used but I've never been in a need to extract metadata so I've never made ANY research about it. It could be that there are other APIs or libraries that do a better job, more straightforward and with wider browser support.

like image 172
David Avatar answered Nov 17 '22 17:11

David