56

I want to set an image as a background, however the image name might be either bg.png or bg.jpg.

Is there any non-javascript way to create a fallback to an alternative image if the default background doesn't exist?

body{
    background: url(bg.png);
    background-size: 100% 100%;
    width: 100vw;
    height: 100vh;
    margin: 0;
}
Chris
  • 57,622
  • 19
  • 111
  • 137
edisonmecaj
  • 1,062
  • 1
  • 9
  • 20
  • 1
    What's the desired behavior if the image **doesn't** exist? – Chris Jun 02 '16 at 09:37
  • i will add a file uploader and the uploader will accept png or jpg. based on the image that will be uploaded it will set the file as bg.png or bg.jpg – edisonmecaj Jun 02 '16 at 09:40
  • you can add `data-fallback` attribute to body tag and then check `var body = $(document.body);` `if(!(body.css('background'))) {body.css('background', body.data('fallback ')) }` – devellopah Jun 02 '16 at 13:21

5 Answers5

101

You can use multiple backgrounds if there is no transparency involved and they occupy all available space or have the same size:

div{   
     background-image: url('http://placehold.it/1000x1000'), url('http://placehold.it/500x500');
     background-repeat:no-repeat;
     background-size: 100%;
     height:200px;
     width:200px;
}
<div></div>

If the first doesn't exit, the second will be displayed.

div{   
     background-image: url('http://placehol/1000x1000'), url('http://placehold.it/500x500');
     background-repeat:no-repeat;
     background-size: 100%;
     height:200px;
     width:200px;
}
<div></div>
Watchoutman
  • 37
  • 1
  • 10
  • I accepted his answer at the moment he answered. After that I noticed that you answered with the same solution. Thanks again – edisonmecaj Jun 02 '16 at 10:12
  • When I write my answer the other answer was diferent but if you want change your vote not problem for me, I try to help, don't worry the reputation. –  Jun 02 '16 at 10:15
  • Nah, it's okay. My edit and your answer were only 4 minutes apart :) – Chris Jun 02 '16 at 10:18
  • what if there is transparency tho? – Salix May 11 '17 at 18:14
  • 23
    The images are stacked, if the top one has transparency will see the bottom image through –  May 11 '17 at 18:54
  • 2
    What is the browser compatibility? – Crystal Miller Jul 13 '17 at 17:23
  • 2
    @CrystalMiller You can see here: https://caniuse.com/#feat=multibackgrounds –  Jul 13 '17 at 17:31
  • 22
    Unfortunately, on chrome - network tab - I can see it loads both images from the server. Even when the first is successful. – Martin Capodici Nov 16 '17 at 22:18
  • 1
    @MartinCapodici Yes, that is the expected behavior for multiple backgrounds, is not a replacement of images. –  Nov 17 '17 at 06:09
  • 8
    Browser loads both images. Would be nice if the second gets only loaded if the first one failed. Afaik, not possible with this solution. – robsch Oct 17 '18 at 07:24
17

To specify multiple possible backgrounds, you could do:

  background-color: green;
  background-image: url('bg.png'), url('bg.jpg');

This will set the background to bg.png if it exists. If it doesn't exist, it will be set to bg.jpg. If none of those images exist, the background will be set to the static green color.

Note that it will prioritize whatever image is specified first. So if both images exist, it will display the bg.png on top of the bg.jpg. If the png has any transparency, both images will be partially visible.

Check out the demo here. Test it by breaking any of the image urls'.

Chris
  • 57,622
  • 19
  • 111
  • 137
  • 2
    its not the solution that I need but I just up-voted for the alternative solution. – edisonmecaj Jun 02 '16 at 09:41
  • 1
    @djsony90 Oh, so the image will always exist, but it will either be a `jpg` OR `png`? – Chris Jun 02 '16 at 09:43
  • sorry, I didn't specified. The uploader will delete the existing bg.png or bg.jpg then will update a new one bg.png or bg.jpg – edisonmecaj Jun 02 '16 at 09:45
  • 5
    As @blonfu mentioned in a different answer, both images are loaded and stacked. It does not just load the first successful file. – J.Hendrix Dec 06 '19 at 18:39
  • 1
    @J.Hendrix right, but there is no other way to solve that without JavaScript. OP asked for a "non-javascript way" and this is probably as good as it gets with css-only. Unless I'm missing something (if so please specify what), I think the down-vote isn't quite justified since there is no other way, really, to solve this. – Chris Dec 06 '19 at 20:44
  • To be clear, the statement "If [the first image] doesn't exist, it will be set to [second image]" is false; the images and colors are all loaded and stacked. In this example, bg.png will display on top, then bg.jpg, and finally a green background. Since all images take the full background, we don't see the other layers. – Connor Low Dec 12 '22 at 16:45
  • Because background images aren't guaranteed to be the same size, or you might be using an image with transparency (e.g. in an SVG); in such a case, you would see underlying the images/background-color. – Connor Low Dec 13 '22 at 20:10
  • I understand the OP use case; I still find this answer misleading. – Connor Low Dec 13 '22 at 21:15
  • Why? The answer was updated to be clearer and I explained that this isn't a canonical answer, but an answer specifically to OP's problem. OP also specifically asked for a CSS-only solution which most other answers here disregarded. If you think the answer is misleading still, you ask specifically what needs to be made more clear and I will update it once more. – Chris Dec 13 '22 at 21:25
  • @Chris, I now see your recent update and agree that it improves clarity. I still think the statement "This will set the background to `bg.png` if it exists. If it doesn't exist, it will be set to `bg.jpg`." is misleading - it sounds like it only displays the second image if the first wasn't available (which is not what you say in the following paragraph now). – Connor Low Dec 14 '22 at 02:24
  • 1
    This actually really worked well for me and turned out to be exactly what I was looking for. Thank you so much! – Joe May 07 '23 at 03:10
4

Although this solution does involve JS, it will download the first image using CSS, and will only use JS to download the fallback image if the main image failed to download.

First, set the main image with CSS as usual, for example:

.myImage {
    background-image = "main-image.png";
}

And add the following JS that will only act if loading the main image fails (otherwise, fallback image will never be downloaded).

var img = document.createElement("img");
img.onerror = function() {
    var style = document.createElement('style');
    style.innerHTML = '.myImage { background-image = url("http://fallback_image.png"); }';
    document.head.appendChild(style);
}
img.src = "main_image.png";

Note that you can add multiple rules to the CSS block added with javascript, for example if you need to do this with multiple elements.

Raine Revere
  • 30,985
  • 5
  • 40
  • 52
DanyAlejandro
  • 1,440
  • 13
  • 24
1

You can have background image with css and use the same image URL in a hidden image tag and onError, change the image URL and make it show it

<div
  style={{ backgroundImage: `url(${imageSrc})` }}
>
  <img
    style={{ display: "none" }}
    src={imageSrc}
    onError={(e) => {
      e.currentTarget.src = brokenImageFallbackUrl;
      e.currentTarget.style.display = "block";
      // or keep this image hidden and set the backgroundImage of the parent div
    }}
  />
</div>
Aamir Afridi
  • 6,364
  • 3
  • 42
  • 42
0

I wanted to have a solution that doesn't load the images twice. For example CDN with a fallback is not very good if it always loads the original images also. So I ended up writing a Javascript to manipulate the loaded CSS DOM.

var cdn = "https://mycdn.net/"; // Original location or thing to find
var localImageToCssPath = "../"; // Replacement, Css are in /css/ folder.

function replaceStyleRule(allRules){
  var rulesCount = allRules.length;

  for (var i=0; i < rulesCount; i++) 
  {
    if(allRules[i].style !== undefined && allRules[i].style !== null &&
        allRules[i].style.backgroundImage !== undefined &&
        allRules[i].style.backgroundImage !== null &&
        allRules[i].style.backgroundImage !== "" &&
        allRules[i].style.backgroundImage !== "none" &&
        allRules[i].style.backgroundImage.indexOf(cdn) > -1
            )
      {
        var tmp = allRules[i].style.backgroundImage.replace(cdn, localImageToCssPath);
        //console.log(tmp);
        allRules[i].style.backgroundImage = tmp;
      }
      if(allRules[i].cssRules !== undefined && allRules[i].cssRules !== null){
        replaceStyleRule(allRules[i].cssRules);
      }

  }
}
function fixBgImages(){
  var allSheets = document.styleSheets;
  if(allSheets===undefined || allSheets===null){
    return;
  }
  var sheetsCount = allSheets.length;
  for (var j=0; j < sheetsCount; j++) 
  {
    var allRules = null;
    try{
        allRules = allSheets[j].cssRules;
    } catch(e){
        // Access can be denied
    }
    if(allRules!==undefined && allRules!==null){
        replaceStyleRule(allRules);
    }
  }
}

// use fixBgImages() in e.g. onError
Tuomas Hietanen
  • 4,650
  • 2
  • 35
  • 43