11

I've been reading all night and can't seem to come to any sort of concrete answer on what the best way to do this is. The two things that I know do work are these—

For fading in an image when it loads:

Use an image wrapper and an <img> tag like this:

<div class="imageWrapper">
    <img src="img.jpg" alt="" onload="imageLoaded(this)">
</div>

and the css looks like

.imageWrapper {
  opacity: 0
}

.loaded {
  opacity: 1
}

and then have in your js file something like

var imageLoaded = (img) => {
    var imgWrapper = img.parentNode;
    imgWrapper.className += ' loaded';
}

For loading a different image based on screen size

@media screen only and (min-device-width: 0) and (max-device-width: 450px) {
  .backgroundImage {
    background: url('small_background_image.jpg');
  }
}

@media screen only and (min-device-width: 451px) and (max-device-width: 1024px) {
  .backgroundImage {
    background: url('medium_background_image.jpg');
  }
}

@media screen only and (min-device-width: 1025px) {
  .backgroundImage {
    background: url('large_background_image.jpg');
  }
}

My Problem

I can do both of these things separately (fade in an image when it is loaded, change a background-image when the browser detects a smaller screen size), but I can't seem to find a solution that incorporates both. I need to be able to specify a different image for different screen sizes, and also be able to detect when that image is loaded and fade it in.

Is there a good way to do this?

My solution:

I ended up using a variation of guest271314's answer in order to load the correct images. This works on all latest version of each browser and is very easily implemented.

First, I have an inline script placed right underneath my opening <body> tag so that if my browser is slow with loading my js file it can load the images right away anyways.

<body>
    <script>
        function imageLoaded(img) {
            if ((" " + img.className + " ").indexOf(" "+'loaded'+" ") > -1 ) {
            return true;
            } else {
                img.className += ' loaded';
            }
        }
    </script>

and then I have my <picture> tag like so:

<picture class="backgroundContainer">
    <source srcset="img-1024.jpg" media="(min-width: 0px) and (max-width:1024px)">
    <source srcset="img-1920.jpg" media="(min-width: 1025px) and (max-width: 1920px)">
    <source srcset="img-2560.jpg" media="(min-width: 1921px)">
    <img class="backgroundImage" onload="imageLoaded(this)" src="img-1920.jpg" alt="">
</picture>

and my sass looks like this:

@keyframes fadeIn
  0%
    opacity: 0
  100%
    opacity: 1

.backgroundContainer
  width: 100%
  height: 100%

.backgroundImage
  opacity: 0

.loaded
  opacity: 1
  animation: fadeIn 3s

And this allows the browser (regardless of speed) to wait until the correctly-sized image is done loading, and then fades it in over 3s, with a safe fallback using the <img> tag for older browsers.

Community
  • 1
  • 1
shan
  • 3,035
  • 5
  • 34
  • 50

4 Answers4

4

I basically need to be able to fade in a background-image when it loads, or be able to change an <img> tag's src before it loads depending on screen size, or do something that incorporates both of these things using a method I'm unaware of.

You can use css content property with value set to url() function having URL of image to display, or change displayed image source; css animation, @keyframes to fade in background-image when the source image loads.

html

<picture class="backgroundImage"></picture>

css

.backgroundImage {
  opacity: 0; /* set `opacity` to `0` */
  width: 50px;
  width: 50px;
  content: url(http://lorempixel.com/50/50/technics); /* set intial image */
  animation-name: fadein;
  animation-iteration-count: 1;
  animation-duration: 2500ms;
  animation-fill-mode: both;
}

@keyframes fadein {
  to {
    opacity: 1; /* fade in image */
  }
}
/* set media queries */
@media screen only and (min-device-width: 0) and (max-device-width: 450px) {
  .backgroundImage {
    opacity: 0; /* set `opacity` to `0` */
    content: url(http://lorempixel.com/50/50/cats); /* set image */
    animation-name: first;
  }
  @keyframes first {
    to {
      opacity: 1; /* fade in image */
    }
  }
}

@media screen only and (min-device-width: 451px) and (max-device-width: 1024px) {
  .backgroundImage {
    opacity: 0; /* set `opacity` to `0` */
    content: url(http://lorempixel.com/50/50/city); /* set image */
    animation-name: second;
  }
  @keyframes second {
    to {
      opacity: 1; /* fade in image */
    }
  }
}

@media screen only and (min-device-width: 1025px) {
  .backgroundImage {
    opacity: 0; /* set `opacity` to `0` */
    content: url(http://lorempixel.com/50/50/sports); /* set image */
    animation-name: third;
  }
  @keyframes third {
    to {
      opacity: 1; /* fade in image */
    }
  }
}

jsfiddle https://jsfiddle.net/yayd5Lma/3

guest271314
  • 1
  • 15
  • 104
  • 177
  • Hmm, could you add a brief example or something? Having a hard time determining how exactly to structure this.. – shan Sep 27 '16 at 05:27
  • 1
    @shanling Included comments at `css` describing approach. You can resize result window at jsfiddle to view effects – guest271314 Sep 27 '16 at 06:12
  • huh, interesting. is there a way to style the image that you set in the "content" property? and is it dynamically loading the images? i'm inspecting the network tab on chrome devtools and it appears to just be fetching the images when the screen changes to fit different media queries. is this a usual functionality of using css "content" property? – shan Sep 27 '16 at 06:15
  • 1
    Yes, can can style the `` element. Yes, the image would not be requested until the media query condition is true, then the image should be cached if cache is not disabled. See [`content`](https://developer.mozilla.org/en-US/docs/Web/CSS/content), [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture), [Is it possible to display an .html document , or .html fragment at CSS content?](http://stackoverflow.com/q/39689386/) – guest271314 Sep 27 '16 at 06:18
  • This worked like a charm. Fading in like it should be once it's loaded only loads the proper image. Never heard of the content property being used like this, thank you so much!! – shan Sep 27 '16 at 06:28
  • Hmm, I was testing this more today and after throttling my internet to slower speeds (2G on devtools) it does not load with a fade, just the same old chunk after chunk :-/ – shan Sep 28 '16 at 00:09
  • @shanling Can you describe the procedure that you performed? What were you trying to determine? – guest271314 Sep 28 '16 at 00:13
  • I went into Chrome DevTools and under 'Network Conditions' set 'Network Throttling' to '2G'. Then when I did a cache refresh (on both my site & the fiddle) the images would not wait until they were loaded to fade in, but appear in chunks as images typically do by default. I was trying to determine if it was just my network speed that made this appear to work (my network speed is usually fast enough to load the image before the fade animation begins) or if this truly was a universal approach to fade in the correct responsive image after it loads regardless of network speed. – shan Sep 28 '16 at 00:21
  • @shanling _"regardless of network speed"_ appears to be fairly broad? Not at chromium currently; will try the same when can. Do `javascript` approaches return same result? – guest271314 Sep 28 '16 at 00:24
  • That was my initial problem that I was facing— I was able to wait until the images loaded with a simple onload function in javascript and it would wait to display the image (with a fade-in) until it was fully loaded, regardless of how long it took to load the image, but using a javascript approach like this I could not use a css media query to only load certain images based on screen size as I would have to attach the 'onload' function to an img tag, which i could not dynamically replace the url of (as far as I know). maybe using this css content approach with a tiny bit of javascript.. – shan Sep 28 '16 at 00:29
  • added a new fiddle which shows the issue I think. however you cannot detect if the animation happens when the image is truly loaded or not unless you force a slow internet connection, as the images are very small in size and will load near-instantly on normal internet speeds. – shan Sep 28 '16 at 01:14
  • Note, the `` element can be set with a [`media`](https://html.spec.whatwg.org/multipage/embedded-content.html#attr-source-media) attribute, where it may be possible to further adjust current approach, or try a different combination of approaches to meet requirement. – guest271314 Sep 28 '16 at 01:52
1

edit #2: an updated fiddle of progress so far: https://jsfiddle.net/c5hy0g8r/11

The problem is still that on slow network conditions, using css 'content' property will still load in chunks as images usually do. The usual method of onload="imageLoaded(this)" will not work on an img or picture that has its content generated by css content: url('img.jpg') property.

You can use <picture> element having one or more <source> elements with srcset attribute set to image source which should be displayed at <img> when corresponding media attribute set to a valid media query list matches the environment.

At onload event of <img> element, set parent element opacity to 0; use requestAnimationFrame() to animate opacity of element from 0 to 1.

<!DOCTYPE html>
<html>

<head>
  <style>
    .imageWrapper {
      opacity: 0;
    }
  </style>
</head>

<body>

  <picture class="imageWrapper">
    <source srcset="http://lorempixel.com/50/50/technics" media="(min-width: 0px) and (max-width:150px)">
    <source srcset="http://lorempixel.com/50/50/city" media="(min-width: 151px) and (max-width: 300px)">
    <source srcset="http://lorempixel.com/50/50/nature" media="(min-width: 301px) and (max-width:450px)">
    <source srcset="http://lorempixel.com/50/50/sports" media="(min-width: 451px) and (max-width:600px)">
    <source srcset="http://lorempixel.com/50/50/animals" media="(min-width: 601px)">
    <img width="50px" height="50px" onload="imageLoaded(this)" src="null" alt="">
  </picture>
  <figcaption></figcaption>
    <script>
    const num = 1/60;
    var fx = null;
    var caption = document.querySelector("figcaption");
    var imageLoaded = (img) => {
      caption.innerHTML = "";
      cancelAnimationFrame(fx);
      var n = 0;
      var imgWrapper = img.parentNode;
      imgWrapper.style.opacity = 0;
      fx = requestAnimationFrame(function animate() {
        console.log(n);
        if (n < 1) {
          n += num;
          imgWrapper.style.opacity = n;
          fx = requestAnimationFrame(animate);
        } else {
          caption.innerHTML = img.currentSrc.slice(img.currentSrc.lastIndexOf("/") + 1);
          console.log("complete");
        }
      })
    }
  </script>
</body>
</html>

plnkr http://plnkr.co/edit/RWSc6J3kUUJPUZsrpagi?p=preview

guest271314
  • 1
  • 15
  • 104
  • 177
  • I ended up using the tag and everything is working as intended. I have a tiny bit simpler approach that I will edit my original post with. Thank you! – shan Sep 28 '16 at 21:07
0

You should use css3 animations for the purpose.

@-webkit-keyframes fadeIn {
            0% {opacity: 0;}
            100% {opacity: 1;}
         }

         @keyframes fadeIn {
            0% {opacity: 0;}
            100% {opacity: 1;}
         }

         .fadeIn {
            -webkit-animation-name: fadeIn;
            animation-name: fadeIn;
         }
  • yes, I can fade in anything with css3 animations but i need to fade in only *after* an image is fully loaded, hence my issue – shan Sep 27 '16 at 05:39
0

I would just load all of the images in my markup and assign classes for the media queries:

<div class="imageWrapper">
    <img class="small" src="img_small.jpg" onload="imageLoaded(this)">
    <img class="med" src="img_med.jpg" onload="imageLoaded(this)">
    <img class="large" src="img_large.jpg" onload="imageLoaded(this)">
</div>

Then use your media queries to hide them as needed:

@media screen only and (min-device-width: 0) and (max-device-width: 450px) {
  .imageWrapper .large, .imageWrapper .medium { display: none; }
}

@media screen only and (min-device-width: 451px) and (max-device-width: 1024px) {
  .imageWrapper .large, .imageWrapper .small { display: none; }
}

@media screen only and (min-device-width: 1025px) {
  .imageWrapper .small, .imageWrapper .medium { display: none; }
}
  • well, this would load all of the images, not only the one that was needed for the screen-size, and would defeat the need for the media query in the first place, if i'm not mistaken – shan Sep 27 '16 at 06:09
  • how would this defeat the need for media query? – But those new buttons though.. Sep 28 '16 at 14:29
  • my question involves loading only the correct image. this would load all three, and then hide all but the correct image, adding a large amount of loading time to my website. with a media query you can have the browser decide which image to load based on screen size and it will only load that particular image, bypassing the other two. this media query you have presented would not prevent the other two from loading. – shan Sep 28 '16 at 20:24
  • that's true but it would display the correct image depending on screen size and it would also the fade you wanted. i suppose it really depends on the image sizes you are working with but loading 2 extra images is probably not a big deal. in any event, this answers your question - maybe it's not what you wanted but it should do you what you asked. – But those new buttons though.. Sep 29 '16 at 03:23