Load a low-res background image first, then a high-res one

烈酒焚心 提交于 2019-12-28 05:14:09

问题


I am trying to optimize the size of my site when it is being outputted to the client. I am down to 1.9MB and 29KB when caching. The issue is that the first load contains an image which is very unoptimized for mobile devices; it has a 1080p resolution.

So I am looking for a method that allows me to first load a low-res version (min.bg.jpg) and once the site has loaded, use a high-res version - or even one with a resolution close to the device being used (NNNxNNN.bg.jpg or just bg.jpg).

The background is set using CSS just like everyone would expect. Its applied to the body and the entire statement looks like this:

body {
    background: url("/cdn/theme/images/bg.jpg");
    color: white;
    height: 100%;
    width: 100%;
    background-repeat: no-repeat;
    background-position: 50% 50%;
    background-attachment: fixed;
}

Now, I want to change that to use min.bg.jpg instead for the first load, and then something like this:

jQuery(function(){
    jQuery("body").[...]
});

Which way do I go on asynchronously downloading the new background, and then inserting it as the new CSS background image?

To show some differences, here is an example of the main and mini version I am using for testing:

Ingwie@Ingwies-Macbook-Pro.local ~/Work/BIRD3/cdn/theme/images $ file *.jpg
bg.jpg:     JPEG image data, EXIF standard
min.bg.jpg: JPEG image data, JFIF standard 1.01
Ingwie@Ingwies-Macbook-Pro.local ~/Work/BIRD3/cdn/theme/images $ du -h *.jpg
1,0M    bg.jpg
620K    min.bg.jpg

回答1:


Here's the method I use...

CSS:

#div_whatever {
   position: whatever;
   background-repeat: no-repeat;
   background-position: whatever whatever; 
   background-image: url(dir/image.jpg);
   /* image.jpg is a low-resolution at 30% quality. */
}

#img_highQuality {
    display: none;
}

HTML:

<img id="img_highQuality" src="dir/image.png">
<!-- img.png is a full-resolution image. -->

<div id="div_whatever"></div>

JQUERY:

$("#img_highQuality").off().on("load", function() {
    $("#div_whatever").css({
        "background-image" : "url(dir/image.png)"
    });
});
// Side note: I usually define CSS arrays because
// I inevitably want to go back and add another 
// property at some point.

What happens:

  1. A low-res version of the background quickly loads.
  2. Meanwhile, the higher resolution version is loading as a hidden image.
  3. When the high-res image is loaded, jQuery swaps the div's low-res image with the high-res version.

PURE JS VERSION

This example would be efficient for changing one to many elements.

CSS:

.hidden {
   display: none;
}

#div_whatever {
   position: whatever;
   background-repeat: no-repeat;
   background-position: whatever whatever; 
   background-image: url(dir/image.jpg);
   /* image.jpg is a low-resolution at 30% quality. */
}

HTML:

<div id="div_whatever"></div>
<img id="img_whatever" class="hidden" src="dir/image.png" onload="upgradeImage(this);">

JAVASCRIPT:

function upgradeImage(object) {
    var id = object.id;
    var target = "div_" + id.substring(4);

    document.getElementById(target).style.backgroundImage = "url(" + object.src + ")";
}

UPDATE / ENHANCEMENT (1/31/2017)

This enhancement is inspired by gdbj's excellent point that my solution results in the image path being specified in three locations. Although I didn't use gdbj's addClass() technique, the following jQuery code is modified to extract the image path (rather than it being hardwired into the jQuery code). More importantly, this version allows for multiple low-res to high-res image substitutions.

CSS

.img_highres {
  display: none;
}

#div_whatever1 {
  width: 100px;
  height: 100px;
  background-repeat: no-repeat;
  background-position: center center;
  background-image: url(PATH_TO_LOW_RES_PHOTO_1);
}

#div_whatever2 {
  width: 200px;
  height: 200px;
  background-repeat: no-repeat;
  background-position: center center;
  background-image: url(PATH_TO_LOW_RES_PHOTO_2);
}

HTML

<div id="div_whatever1"></div>
<img id="img_whatever1" class="img_highres" src="PATH_TO_HIGH_RES_PHOTO_1">

<div id="div_whatever2"></div>
<img id="img_whatever2" class="img_highres" src="PATH_TO_HIGH_RES_PHOTO_2">

JQUERY

  $(function() {
      $(".img_highres").off().on("load", function() {
         var id = $(this).attr("id");
         var highres = $(this).attr("src").toString();
         var target = "#div_" + id.substring(4);
         $(target).css("background-image", "url(" + highres + ")");
      });
   });

What's happens:

  1. Low res images are loaded for each of the divs based on their CSS background-image settings. (Note that the CSS also sets the div to the intended dimensions.)
  2. Meanwhile, the higher resolution photos are being loaded as hidden images (all sharing a class name of img_highres).
  3. A jQuery function is triggered each time an img_highres photo completes loading.
  4. The jQuery function reads the image src path, and changes the background image of the corresponding div. In the example above, the naming convention is "div_[name]" for the visible divs and "img_[same name]" for the high res images loaded in the background.



回答2:


Let's try a basic one :

<img border="0" 
     style="background:url(http://i.stack.imgur.com/zWfJ5.jpg) no-repeat; 
            width:1920px;
            height:1200px"
     src="http://i.stack.imgur.com/XOYra.jpg" width="1920" height="1200" />

zWfJ5.jpg is the low-resolution version, and XOYra.jpg is the high-resolution version.

If there is a way to arrange the loading so the background-image displays first, this could be the simplest i can think of.

where low resolution 44k:

and high resolution is 1.16M

result :

jsFiddled here ( this needs a bigger image for loading comparison. )




回答3:


A bit late, but you can use this extremely simple solution: You can put the two images in the css background:

  background-image: url("high-res.jpg"),url("low-res.jpg");

The browser will display the low-res image fist, then display the high-res over the low-res when it has been loaded.




回答4:


I would normally optimise the image using Grunt or an online tool such as Tiny PNG to reduce the file size.

Then you could choose to defer the loading of the images, I found the following article helpful when it came to defering images - https://www.feedthebot.com/pagespeed/defer-images.html

The article discusses using a base64 image for the initial loading and then defering the loading of the high quality image. The image mark up mentioned in the article is as follows...

<img src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-src="your-image-here">

The JavaScript mentioned in the article is as follows...

<script type="text/javascript" async>
function init() {
    var imgDefer = document.getElementsByTagName('img');
    for (var i=0; i<imgDefer.length; i++) {
        if(imgDefer[i].getAttribute('data-src')) {
            imgDefer[i].setAttribute('src',imgDefer[i].getAttribute('data-src'));
        }
    }
}
window.onload = init;
</script>

I hope this helps.




回答5:


On Ubuntu / Chrome 71, Milche's answer does not work consistently for me and the higher resolution image (via img src) often loads and resolves before the lower resolution image (via css background) even begins downloading.

My solution is to start with the lower res image as the src and use the Image class to create an unattached <img> instance with the high res image. Once it loads, then update the existing <img> source with the high res image source.

HTML:

<img id='my-image' src='low-res.png' alt='Title' width='1920px' height='1200px'>

JavaScript:

window.addEventListener('load', function() {
    loadHighResImage(document.getElementById('my-image'), 'high-res.png')
})

function loadHighResImage(elem, highResUrl) {
    let image = new Image()
    image.addEventListener('load', () => elem.src = highResUrl)
    image.src = highResUrl
}

Fiddle: https://jsfiddle.net/25aqmd67/

This approach works for lower res images that are simply scaled down as well.




回答6:


All answers above mostly work with a little adjustment, but here is the way I think short and simple to kick off.

Note:

  • Uncomment the code load the high-resolution image for usage, a sleep function is just for simulating a slow network.
  • Actually, this method does not load 2 resources (low and high) simultaneous, but it's acceptable because low resource won't take much time to load.
  • Just copy whole code and run for a quick check.

<!DOCTYPE html>
<html>
  <head>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <style type="text/css">
      
    </style>
  </head>

  <body>
    <!-- Load low res image first -->
    <img style="width: 400px; height: auto;" alt="" src="https://s3-ap-southeast-1.amazonaws.com/wheredat/banner-low-quality/banner_20180725_123048.jpg" onload="upgrade(this)">
  </body>
  
  <script type="text/javascript">

	function upgrade(image){

                // After load low res image, remove onload listener.

		// Remove onload listener.
		$(image).prop("onload", null);

                // Load high resolution image.
                // $(image).attr('src', 'https://s3-ap-southeast-1.amazonaws.com/wheredat/banner/banner_20180725_123048.jpeg');

		// Simulate slow network, after 1.5s, the high res image loads.
		sleep(1500).then(() => {
		    // Do something after the sleep!

			// Load a high resolution image.
			$(image).attr('src', 'https://s3-ap-southeast-1.amazonaws.com/wheredat/banner/banner_20180725_123048.jpeg');
		});	
	}

	// Sleep time expects milliseconds
	function sleep (time) {
	  return new Promise((resolve) => setTimeout(resolve, time));
	}

  </script>
  
</html>



回答7:


Have you tried using CSS sprites to help get what you want? http://css-tricks.com/css-sprites/



来源:https://stackoverflow.com/questions/27934548/load-a-low-res-background-image-first-then-a-high-res-one

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!