95

So the alert gives undefined values for the width and height. I think the w and h values of the image from the img.onload calculation is not being passed to the values to return, or it may be returning w and h before the onload calculates them:

function getMeta(url){
 var w; var h;
 var img=new Image;
 img.src=url;
 img.onload=function(){w=this.width; h=this.height;};
 return {w:w,h:h}    
}

// "http://snook.ca/files/mootools_83_snookca.png" //1024x678
// "http://shijitht.files.wordpress.com/2010/08/github.png" //128x128

var end = getMeta("http://shijitht.files.wordpress.com/2010/08/github.png");
var w = end.w;
var h = end.h;
alert(w+'width'+h+'height');

How can I have the alert show the correct width and height?

http://jsfiddle.net/YtqXk/

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
Wonka
  • 8,244
  • 21
  • 73
  • 121
  • 3
    The reason it's not working is because at the point when you `return` your width and height values they are not yet known. You need to deal with the result _inside_ the `onload` callback. You should probably make the callback function a second argument to your `getMeta` function. – powerbuoy Jul 11 '12 at 23:10

6 Answers6

165

Get image size with JavaScript

In order to read the data from an image you'll need to make sure it's first loaded. Here's a callback-based approach and two promise-based solutions:

Callback

const getMeta = (url, cb) => {
  const img = new Image();
  img.onload = () => cb(null, img);
  img.onerror = (err) => cb(err);
  img.src = url;
};

// Use like:
getMeta("https://i.stack.imgur.com/qCWYU.jpg", (err, img) => {
  console.log(img.naturalWidth, img.naturalHeight);
});

Using the load Event listener (Promise):

const getMeta = (url) =>
  new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = (err) => reject(err);
    img.src = url;
  });

// Usage example: 
;(async() => {
  const img = await getMeta('https://i.stack.imgur.com/qCWYU.jpg');
  console.dir(img.naturalHeight + ' ' + img.naturalWidth);
})();

Using HTMLImageElement.decode() (Promise)

const getMeta = async (url) => {
  const img = new Image();
  img.src = url;
  await img.decode();  
  return img
};

// Usage example:
getMeta('https://i.stack.imgur.com/qCWYU.jpg').then(img => {
  console.dir(img.naturalHeight +' '+ img.naturalWidth);
});
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 7
    Since the width and height are not being returned, how can I have them returned to use outside the function? – Wonka Jul 12 '12 at 00:08
  • 3
    @Wonka not without calling some function inside the `.load` function --- or a setInterval, cause while your code is readed and executed, the image is still loading: Interval will check till the image is loaded and clear it self like in this demo: http://jsfiddle.net/roXon/SN2t6/1/ – Roko C. Buljan Jul 12 '12 at 00:22
  • 2
    @Wonka look at here for **call a function as soon the image is loaded :** http://jsfiddle.net/roXon/SN2t6/4/ – Roko C. Buljan Jul 12 '12 at 00:34
  • Note that the width and height come back as `0` for SVG images in IE11 – Sam Mar 25 '20 at 02:53
  • @RokoC.Buljan, a code from the jQuery section throws the error: `Uncaught TypeError: url.indexOf is not a function`. I think a little mistake creep into it, namely `img` attribute and listener are landed in the same object. see [this answer](https://stackoverflow.com/a/61844018/5589126) – almaceleste May 16 '20 at 22:01
  • This comes back with a CORS error if not on the same origin - even with `img.crossOrigin=true` added. Is there a way to change the MIME type of this request or otherwise add headers to a pure JPG request? – lowcrawler Mar 31 '21 at 17:13
35

Just pass a callback as argument like this:

function getMeta(url, callback) {
    const img = new Image();
    img.src = url;
    img.onload = function() { callback(this.width, this.height); }
}
getMeta(
  "http://snook.ca/files/mootools_83_snookca.png",
  (width, height) => { alert(width + 'px ' + height + 'px') }
);
feychu
  • 1,284
  • 1
  • 14
  • 33
28

ES6

Using async/await you can do below getMeta function in sequence-like way and you can use it as follows (which is almost identical to code in your question (I add await keyword and change variable end to img, and change var to let keyword). You need to run getMeta by await only from async function (run).

function getMeta(url) {
    return new Promise((resolve, reject) => {
        let img = new Image();
        img.onload = () => resolve(img);
        img.onerror = () => reject();
        img.src = url;
    });
}

async function run() {

  let img = await getMeta("http://shijitht.files.wordpress.com/2010/08/github.png");

  let w = img.width;
  let h = img.height; 

  size.innerText = `width=${w}px, height=${h}px`;
  size.appendChild(img);
}

run();
<div id="size" />

Rxjs

const { race, fromEvent, map, mergeMap, of } = rxjs;

function getMeta(url) {
    return of(url).pipe(
        mergeMap((path) => {
            const img = new Image();
            let load = fromEvent(img, 'load').pipe(map(_=> img))
            let error = fromEvent(img, 'error').pipe(mergeMap((err) => throwError(() => err)));
            img.src = path;
            
            return race(load, error);
        })
    );
}


let url = "http://shijitht.files.wordpress.com/2010/08/github.png";

getMeta(url).subscribe(img=> { 
  let w = img.width;
  let h = img.height; 

  size.innerText = `width=${w}px, height=${h}px`;
  size.appendChild(img);
}, e=> console.log('Load error'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.5.2/rxjs.umd.min.js" integrity="sha512-wBEi/LQM8Pi08xK2jwHJNCiHchHnfcJao0XVQvkTGc91Q/yvC/6q0xPf+qQr54SlG8yRbRCA8QDYrA98+0H+hg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="size" />
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
13

The w and h variables in img.onload function are not in the same scope with those in the getMeta() function. One way to do it, is as follows:

Fiddle: http://jsfiddle.net/ppanagi/28UES/2/

function getMeta(varA, varB) {
    if (typeof varB !== 'undefined') {
       alert(varA + ' width ' + varB + ' height');
    } else {
       var img = new Image();
       img.src = varA;
       img.onload = getMeta(this.width, this.height);
    }
}


getMeta("http://snook.ca/files/mootools_83_snookca.png");
Panagiotis Panagi
  • 9,927
  • 7
  • 55
  • 103
  • Thanks, this works as intended, just takes a bit too long to get the info – Wonka Jul 11 '12 at 23:16
  • 1
    This solution will not work if the image has not already been stored in the browser's cache. You must accept a callback function in getMeta that will be called when the onload event fires (http://jsfiddle.net/28UES/) – Russ Ferri Jul 11 '12 at 23:22
  • @Zack hmmm.. Since the width and height are not being returned, how can I have them returned to use outside the function? – Wonka Jul 11 '12 at 23:45
  • @Wonka updated my answer. As soon as you define a callback function you can pass it the variables and manipulate them in the callback function. – Panagiotis Panagi Jul 11 '12 at 23:47
  • @Zack I see the update, which resolves the cache issue. But can you show how to access the width and height outside the function. I need the function to return values outside of itself. – Wonka Jul 11 '12 at 23:52
  • @Wonka It's not possible to know the width and height of an image until after it has been downloaded. This occurs asynchronously (http://stackoverflow.com/questions/2691301/async-or-sync-when-we-set-the-src-property-of-the-image-object), so the getMeta function cannot return the dimensions directly. The usual workaround is to pass a callback function to getMeta that does something useful with the dimensions after the image has been loaded. – Russ Ferri Jul 12 '12 at 00:18
  • Any code outside the getMeta() function will be called before the dimensions of the image are available. Therefore, as Ferri points out, you need to use a callback function for the onload event of the image. – Panagiotis Panagi Jul 12 '12 at 00:59
  • this is [**recursive function**](https://en.wikipedia.org/wiki/Recursion_(computer_science)) (function which call itself) – Kamil Kiełczewski Jun 27 '20 at 08:23
0

Get image size with jQuery
(depending on which formatting method is more suitable for your preferences):

function getMeta(url){
    $('<img/>',{
        src: url,
        on: {
            load: (e) => {
                console.log('image size:', $(e.target).width(), $(e.target).height());
            },
        }
    });
}

or

function getMeta(url){
    $('<img/>',{
        src: url,
    }).on({
        load: (e) => {
            console.log('image size:', $(e.target).width(), $(e.target).height());
        },
    });
}
almaceleste
  • 397
  • 4
  • 11
-2

You will can try package.

import getImageSize from 'image-size-from-url';

const {width, height} = await getImageSize('URL');