43

Is it possible to use JavaScript to get the actual size (width and height in pixels) of a CSS referenced background image?

Kirk Ouimet
  • 27,280
  • 43
  • 127
  • 177

7 Answers7

62

Yes, and I'd do it like this...

window.onload = function () {
  var imageSrc = document
    .getElementById('hello')
    .style.backgroundImage.replace(/url\((['"])?(.*?)\1\)/gi, '$2')
    .split(',')[0];

  // I just broke it up on newlines for readability

  var image = new Image();
  image.src = imageSrc;

  image.onload = function () {
    var width = image.width,
      height = image.height;
    alert('width =' + width + ', height = ' + height);
  };
};

Some notes...

  • We need to remove the url() part that JavaScript returns to get the proper image source. We need to split on , in case the element has multiple background images.
  • We make a new Image object and set its src to the new image.
  • We can then read the width & height.

jQuery would probably a lot less of a headache to get going.

Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
alex
  • 479,566
  • 201
  • 878
  • 984
  • Code posted here and code on JSbin are different. Your regexp in replace has a bug. Here more correct version `.replace(/url\(['"]?(.*)['"]?\)/gi, '$1')` – vogdb Feb 06 '13 at 14:50
  • this does not handle data urls because of the `split`. I removed the split as I only use a single background, but otherwise an additional `if` statement needs to be used to check the presence of `data:` – Timmerz Oct 05 '14 at 13:28
  • @Timmerz Wouldn't a data URI imply that the image would already be loaded? – alex Oct 06 '14 at 22:52
  • @alex I'm not sure what you mean? your function gets the size of a `background-image` by copying it to an `Image`. I'm saying this won't work if the `background-image` references a data url instead of a hyperlink. – Timmerz Oct 06 '14 at 23:23
  • @Timmerz Sorry, I had a few tabs open and wasn't in the correct context when I replied. – alex Oct 06 '14 at 23:38
  • alex in order to get the width and height you need to wait until the image is loaded. no? Thanks to @Killah answer I added it to my code before the src line and now everything works. – Randall Flagg Oct 14 '15 at 15:40
  • @RandallFlagg This is under `window.onload`, so all images should be loaded. – alex Oct 14 '15 at 23:33
  • @alex but you created a new Image and gave it a new source. If you change the source, even after everything was loaded, that means the image is reloading and thus meaning you need to wait before accessing the properties of the image. – Randall Flagg Oct 19 '15 at 09:17
  • @RandallFlagg The source is from an existing image on the page. Unless you have disabled cache, it should be available. I do see what you're getting at though. I guess it would be safer to use a `load` listener again before checking the dimensions. – alex Oct 20 '15 at 02:31
  • After researching this question for a couple of hours, this is the easiest and fastest way to do it. – ntgCleaner Sep 07 '16 at 16:36
  • @RazvanZamfir then you'd [ask that question](http://stackoverflow.com/questions/ask) – alex Sep 30 '16 at 10:28
  • Your answer answers my question almost entirely. Look at this [page](http://ascetic.rol.ro/stillness-body-soul-hesychia/) – Razvan Zamfir Sep 30 '16 at 10:31
  • Alex, I used your script om [this page](http://ascetic.rol.ro/slander/) to calculate the w/h ratio and add a css rule if ratio < 1. But unless i reload the page the script returns NaN. – Razvan Zamfir Oct 06 '16 at 13:19
  • 1
    `document.getElementById('hello').attributes.src.value` works for me – Zachary Ryan Smith Nov 01 '17 at 21:35
  • 1
    @ZacharyRyanSmith I just tried it with a background image and it appeared the `src` property wasn't defined on `attributes`. Are you sure it's a background image, not a normal `img` element? – alex Nov 02 '17 at 10:22
  • @alex you're correct, it was a normal `img` element. Sorry, I was not thinking about `img` vs background-image – Zachary Ryan Smith Nov 02 '17 at 18:21
  • 1
    You should set the width and height in an `image.onload` handler; if you don't do this the width/height can be 0 if your internet connection is slow or the image is big. – thdoan Jun 13 '19 at 22:59
  • @thdoan Doesn't `window.onload` guarantee that the page has finished loading, images and all? – alex Jun 14 '19 at 10:28
13

Can't comment under answers, so here is jQuery version including background-size (posted because this question is first one in google search and may be useful to someone else than me):

function getBackgroundSize(selector, callback) {
  var img = new Image(),
      // here we will place image's width and height
      width, height,
      // here we get the size of the background and split it to array
      backgroundSize = $(selector).css('background-size').split(' ');

  // checking if width was set to pixel value
  if (/px/.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
  // checking if width was set to percent value
  if (/%/.test(backgroundSize[0])) width = $(selector).parent().width() * (parseInt(backgroundSize[0]) / 100);
  // checking if height was set to pixel value
  if (/px/.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
  // checking if height was set to percent value
  if (/%/.test(backgroundSize[1])) height = $(selector).parent().height() * (parseInt(backgroundSize[0]) / 100);

  img.onload = function () {
    // check if width was set earlier, if not then set it now
    if (typeof width == 'undefined') width = this.width;
    // do the same with height
    if (typeof height == 'undefined') height = this.height;
    // call the callback
    callback({ width: width, height: height });
  }
  // extract image source from css using one, simple regex
  // src should be set AFTER onload handler
  img.src = $(selector).css('background-image').replace(/url\(['"]*(.*?)['"]*\)/g, '$1');
}

or as jQuery plugin:

(function ($) {
// for better performance, define regexes once, before the code
var pxRegex = /px/, percentRegex = /%/, urlRegex = /url\(['"]*(.*?)['"]*\)/g;
$.fn.getBackgroundSize = function (callback) {
  var img = new Image(), width, height, backgroundSize = this.css('background-size').split(' ');

  if (pxRegex.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
  if (percentRegex.test(backgroundSize[0])) width = this.parent().width() * (parseInt(backgroundSize[0]) / 100);
  if (pxRegex.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
  if (percentRegex.test(backgroundSize[1])) height = this.parent().height() * (parseInt(backgroundSize[0]) / 100);
  // additional performance boost, if width and height was set just call the callback and return
  if ((typeof width != 'undefined') && (typeof height != 'undefined')) {
    callback({ width: width, height: height });
    return this;
  }
  img.onload = function () {
    if (typeof width == 'undefined') width = this.width;
    if (typeof height == 'undefined') height = this.height;
    callback({ width: width, height: height });
  }
  img.src = this.css('background-image').replace(urlRegex, '$1');
  return this;
}
})(jQuery);
klh
  • 605
  • 7
  • 23
  • Lovely! The jQuery plugin worked perfectly for me. Everyone, this function gives the scaled image size. Thanks very much! – emrys57 Jul 09 '13 at 13:10
  • 1
    Whoops! Sorry, it gives the _unscaled_ image size. But I still like it! – emrys57 Jul 09 '13 at 16:16
  • 1
    There's a potential bug in that regexp. Here's the correct one: `urlRegex = /url\(['"]*(.*?)['"]*\)/g` – neochief May 04 '14 at 10:57
  • @neochief thanks, fixed that, totally forgot someone can use `"` in the `url()` – klh May 06 '14 at 13:59
  • Сouldn't you explain how to use it? Or give an example. Both the function and the plugin. Thanks. – jevgenij Dec 24 '15 at 18:06
  • @JonLennartAasenden dude, I added a jQuery version for reasons stated at the beginning of the answer. Go on your jQuery-hate-spree somewhere else, nobody needs zealots here. – klh Nov 15 '16 at 17:41
  • @JonLennartAasenden At the time of writing this answer this question popped up as first in Google search for "jQuery get background size" and similar. If a teacher has to look things up on SO then I don't think that person should be teaching anyone. I was at your stage - hating on jQuery. Then I grew up. Stop spamming - your last comment was removed already. – klh Dec 29 '16 at 09:28
  • `height = $(selector).parent().height() * (parseInt(backgroundSize[0]) / 100); ` --> should use `backgroundSize[1]` instead `backgroundSize[0] ` // `width = $(selector).parent().width() * ` `height = $(selector).parent().height() * ` --> Should use `$(selector).width` and `$(selector).height`, not parent's – Itagaki Fumihiko Aug 29 '21 at 02:17
  • @ItagakiFumihiko 9 years and nobody noticed a typo :D feel free to use the edit feature to fix it, so you get credit for spotting it. Sadly I don't remember anymore why `.parent()` was needed, it doesn't make sense. – klh Oct 29 '21 at 00:33
  • I did edit it. But the reviewers https://stackoverflow.com/users/5626245/dave and https://stackoverflow.com/users/15744300/jan-wilamowski said in a single word "This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer." Have they not read through my comment on Aug 29, 2021 at 2:17 and the author's comment to it? The author @klh said: "feel free to use the edit feature to fix it, so you get credit for spotting it." I had another bad experience. – Itagaki Fumihiko May 03 '22 at 02:21
  • I wrote the revised code as a new answer. – Itagaki Fumihiko May 05 '22 at 09:45
11
var actualImage = new Image();
actualImage.src = $('YOUR SELECTOR HERE').css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, "");

actualImage.onload = function() {
    width = this.width;
    height = this.height;
}
anhh
  • 111
  • 1
  • 2
5
var dimension, image;

image = new Image();
image.src = {url/data}
image.onload = function() {
    dimension = {
        width: image.naturalWidth,
        height: image.naturalHeight
    };
    console.log(dimension); // Actual image dimension
};
  • 1
    Despite how old this question and this answer is, this is what worked for me. I also edited the answer to fix a syntax issue and used Killah's helpful regex expression in setting the image.src variable. – AuRise Jun 05 '17 at 19:11
  • using 'naturalWidth' and 'naturalHeight' works like a charm – Mau Sep 29 '18 at 10:50
2

Here it is in jQuery:

var actualImage = new Image();
actualImage.src = $('YOUR SELECTOR HERE').css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, "");

actualImage.width // The actual image width
actualImage.height // The actual image height

Thanks for the sweet regex alex.

Kirk Ouimet
  • 27,280
  • 43
  • 127
  • 177
  • 1
    Except that this doesn't work in Chrome. FF seems to load the image but Chrome doesn't. So the width and height properties of actualImage are always 0. – magiconair Jun 08 '11 at 10:44
0

If you're using React you can create a custom hook:

import { useEffect, useState, useCallback, useRef } from 'react'

const urlRgx = /url\((['"])?(.+?)\1\)/
const getImagePromise = src =>
  new Promise(resolve => {
    const img = new Image()

    img.onload = () =>
      resolve({
        src,
        width: img.naturalWidth,
        height: img.naturalHeight
      })
    img.src = src
  })
const useBackgroundImageSize = (asCallbackFlagOrUrls = false) => {
  const ref = useRef()
  const [images, setImages] = useState(null)
  const callback = useCallback(async () => {
    if (Array.isArray(asCallbackFlagOrUrls)) {
      const imgPromises = asCallbackFlagOrUrls.map(getImagePromise)
      const imgs = await Promise.all(imgPromises)

      if (ref?.current) {
        setImages(imgs)
      }
    }

    if (typeof asCallbackFlagOrUrls === 'string') {
      const image = await getImagePromise(asCallbackFlagOrUrls)

      if (ref?.current) {
        setImages(image)
      }
    }

    if (typeof asCallbackFlagOrUrls === 'boolean') {
      if (ref.current) {
        const matches = window
          .getComputedStyle(ref.current)
          .backgroundImage.match(new RegExp(urlRgx, 'g'))

        if (Array.isArray(matches)) {
          const imgPromises = matches.map(match =>
            getImagePromise(match.replace(new RegExp(urlRgx), '$2'))
          )
          const imgs = await Promise.all(imgPromises)

          if (ref?.current) {
            setImages(imgs.length > 1 ? imgs : imgs[0])
          }
        }
      }
    }
  }, [ref, asCallbackFlagOrUrls])

  useEffect(() => {
    if (asCallbackFlagOrUrls !== true) {
      callback()
    }
  }, [asCallbackFlagOrUrls, callback])

  return asCallbackFlagOrUrls === true ? [ref, images, callback] : [ref, images]
}

export { useBackgroundImageSize }

Then use it like:

const App = () => {
  const [ref, image] = useBackgroundImageSize()

  console.log(image) // { width, height, src }

  return <div ref={ref} image={image} />
}

You can also install background-image-size-hook and use it as a dependency. See the README for more usage details.

morganney
  • 6,566
  • 1
  • 24
  • 35
0

Here is a fixed version of the code from klh's post. I pointed out some small mistakes on the comment section of his post and was told please edit it. And so I did. However, reviewers Jan Wilamowski and Dave rejected it.

"This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer."

Apparently they did not see the comments section.

I had no choice but to write the revised code as a new answer.

function getBackgroundSize(selector, callback) {
  var img = new Image(),
      // here we will place image's width and height
      width, height,
      // here we get the size of the background and split it to array
      backgroundSize = $(selector).css('background-size').split(' ');

  // checking if width was set to pixel value
  if (/px/.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
  // checking if width was set to percent value
  if (/%/.test(backgroundSize[0])) width = $(selector).width() * (parseInt(backgroundSize[0]) / 100);
  // checking if height was set to pixel value
  if (/px/.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
  // checking if height was set to percent value
  if (/%/.test(backgroundSize[1])) height = $(selector).height() * (parseInt(backgroundSize[1]) / 100);

  img.onload = function () {
    // check if width was set earlier, if not then set it now
    if (typeof width == 'undefined') width = this.width;
    // do the same with height
    if (typeof height == 'undefined') height = this.height;
    // call the callback
    callback({ width: width, height: height });
  }
  // extract image source from css using one, simple regex
  // src should be set AFTER onload handler
  img.src = $(selector).css('background-image').replace(/url\(['"]*(.*?)['"]*\)/g, '$1');
}

JQuery

(function ($) {
// for better performance, define regexes once, before the code
var pxRegex = /px/, percentRegex = /%/, urlRegex = /url\(['"]*(.*?)['"]*\)/g;
$.fn.getBackgroundSize = function (callback) {
  var img = new Image(), width, height, backgroundSize = this.css('background-size').split(' ');

  if (pxRegex.test(backgroundSize[0])) width = parseInt(backgroundSize[0]);
  if (percentRegex.test(backgroundSize[0])) width = this.width() * (parseInt(backgroundSize[0]) / 100);
  if (pxRegex.test(backgroundSize[1])) height = parseInt(backgroundSize[1]);
  if (percentRegex.test(backgroundSize[1])) height = this.height() * (parseInt(backgroundSize[1]) / 100);
  // additional performance boost, if width and height was set just call the callback and return
  if ((typeof width != 'undefined') && (typeof height != 'undefined')) {
    callback({ width: width, height: height });
    return this;
  }
  img.onload = function () {
    if (typeof width == 'undefined') width = this.width;
    if (typeof height == 'undefined') height = this.height;
    callback({ width: width, height: height });
  }
  img.src = this.css('background-image').replace(urlRegex, '$1');
  return this;
}
})(jQuery);