15

I'm faced with a simple problem. Let says my user load around 150 images through a simple <img data-image='1' src="myimg1.jpg"> <img data-image=2' src="myimg2.jpg"> .. etc

When the user hovers over one of he images.I wish to display this myimg-thisimage.jpg in a small menu at the bottom of the screen. As of now, I'm changing the src attribute in my menu as:

$('#info-poster').attr("src","myimage-thisimage.jpg");

Note: myimage-thisimage.jpg is the current hovered over image.

But, when I do this. The browser is reloading the image (because, there is a small delay). Is there any way to bypass this loading since the user has already loaded the image using a clever way of cloning a DOM element maybe?

PS: The browser image cache is enabled. Therefore, the cache isnt the problem.

Edit: I know one way is to create 300 image elements and hide the other 150 of them. But in a scenario (definitely possible) where there are close to 500 images I would have to create around 1000 DOM elements which would be a big performance issue.

  • If it is the same image, hide and show $('#info-poster') – mplungjan Aug 20 '16 at 06:24
  • But that would create an additonal 150 img elements to be loaded on my DOM. A total of 150 * 2 . That wouldnt be a good way I suppose. –  Aug 20 '16 at 06:25
  • What is `myimg-thisimage.jpg` here? Is that the image that is being hovered? – Preetesh Aug 20 '16 at 06:38
  • Then you were not being clear – mplungjan Aug 20 '16 at 06:48
  • Can you check whether the delay is because of painting the image or fetching it from the server ? If the caching is enabled, then generally browsers do not make another request. You can check the network tab of your browser to see if a fresh request is being made when you are doing `$('#info-poster').attr("src","myimage-thisimage.jpg");` – vdua Aug 20 '16 at 07:53
  • @vdua I'll look into this. But I doubt it will work. Yes, the delay is because its fetching from the server. Which is precisely my point. In which case, it will be a round trip no matter if catch is enabled or not. I was looking for a solution which doesn't have to go upto the server to request/ check (maybe , i'm not very sure if this is possible.) –  Aug 20 '16 at 08:08
  • I did a quick test locally, it seems like it shouldn't reload the image. Fiddle: https://jsfiddle.net/sprc4gj3/ Can you try testing your code with the networks tab on dev tools, and see if a new request is made? – dee Aug 22 '16 at 09:27
  • So is "myimage-thisimage.jpg" a variable thing? Or is it always the same image? It sounds like what you are saying is that it is different for each one. You might want to search the web for how to preload images; there are many many articles on that. Then the images would be in cache and wouldn't cause the delay. – Heretic Monkey Aug 22 '16 at 17:09
  • @dee It is making a new request everytime I hover. I've checked in the network tab. –  Aug 25 '16 at 12:50

7 Answers7

6

You can use a canvas element to show the thumbnail, this way the image is copied and scaled locally. In the following snippet I added two canvas, in the first one the image is scaled while keeping the aspect ratio (I use the Letterboxing and Pillarboxing techniques when required); in the second one the image is stretched. I also added another image at the bottom which is ignored, as it doesn't have the data-image attribute.

Is important not to use the scaling algorithm of drawImage as it produces unsmooth results when you reduce the image a lot. To achieve this, set the logical size of the canvas to match the natural size of the image. Then copy the image to the canvas by calling the drawImage method. Finally set the display size of the canvas to the desired one. This way the browser uses a better algorithm to scale the image.

Here are some outstanding quotes from the specification of the drawImage() method:

  • You can be sure the image will not be reloaded, and that you have to use the natural size of the image to avoid scaling with drawImage:

If the original image data is a bitmap image, the value painted at a point in the destination rectangle is computed by filtering the original image data.

  • The browser decides which scaling algorithm to use. At the moment of writing this: Edge, Chrome and Firefox don't use nothing better than the bilinear or nearest-neighbor algorithms. This may change in the future:

The user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor).

function initCanvas(id,image,naturalWidth,naturalHeight){
    var canvas = document.getElementById(id);
    var ctx = canvas.getContext("2d");
    // Set the logical size of the canvas to match the 
    // natural size of the image, this way we don't use
    // the scaling algorithm of drawImage (It isn't good
    // for reducing big images as it produces unsmooth results).
    $(canvas).attr("width",naturalWidth) ;
    $(canvas).attr("height",naturalHeight) ;
    // Copy the image:
    ctx.drawImage(image,0,0,naturalWidth,naturalHeight);
    return canvas ;
}
function clearCanvas(id){
    var canvas = document.getElementById(id);
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0,0,canvas.width,canvas.height);
}
$(window).on("load", function( ){
    var images = $("img").filter(function(){
        var dataImage = $(this).data("image") ;
        if( typeof dataImage != "number" ) return false ;
        var number = parseInt(dataImage,10) ;
        return number > 0 && dataImage === number ;
    }) ;
    images.on("mouseenter", function( ){
        var naturalWidth = $(this).prop("naturalWidth") ;
        var naturalHeight = $(this).prop("naturalHeight") ;

        // Scaled thumbnail:
        // Copy the image to canvas-scaled and get a reference to it:
        var scaledCanvas = initCanvas("canvas-scaled",this,naturalWidth,naturalHeight);
        // Calculate the display size of the canvas:
        var hwfactor = naturalHeight/naturalWidth ;
        var whfactor = naturalWidth/naturalHeight ;
        var scaledWidth, scaledHeight ;
        if( hwfactor >= 1 ){ // Pillarboxing
            scaledHeight = "100px" ;
            scaledWidth = (100*whfactor)+"px" ;
        }
        else{ // Letterboxing
            scaledWidth = "100px" ;
            scaledHeight = (100*hwfactor)+"px" ;
        }
        // Now we change the display size of the canvas.
        // A better scaling algorithm will be used.
        $(scaledCanvas).css("width",scaledWidth);
        $(scaledCanvas).css("height",scaledHeight);

        // Stretched thumbnail:
        // Copy the image to canvas-stretched. The display size
        // of canvas-stretched is already set in the style section.
        initCanvas("canvas-stretched",this,naturalWidth,naturalHeight);
    });
    images.on("mouseleave", function( ){
        clearCanvas("canvas-scaled");
        clearCanvas("canvas-stretched");
    });
});
body{
    background: #000;
}
.wrapper img{
    width: 100px ;
    height: auto ;
}
#banner{
    display: block ;
    width: 100% ;
    height: 40px ;
    padding-top: 1pt ;
}
#canvas-stretched{
    width: 100px ;
    height: 100px ;
}
.canvas-wrapper{
    display: -webkit-inline-flex ;
    display: inline-flex ;
    -webkit-justify-content: space-around ;
    justify-content: space-around ;
    -webkit-align-items: center ;
    align-items: center ;
    vertical-align: bottom ;
    border: 1px solid #888 ;
    width: 100px ;
    height: 100px ;
    overflow: hidden ;
}
.viewer{
    display: inline-block ;
}
.viewer span{
    color: #ddd ;
    font-family: sans-serif ;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>

<span class="wrapper">
    <img data-image="1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/550px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg"/>
    <img data-image="2" src="https://upload.wikimedia.org/wikipedia/en/8/81/Megadrive_another_world.png"/>
    <img data-image="3" src="https://upload.wikimedia.org/wikipedia/en/e/ee/TheKlingonHamlet.jpg"/>
</span>
<span class="viewer">
    <span>scaled</span><br>
    <div class="canvas-wrapper">
        <canvas id="canvas-scaled"></canvas>
    </div>
</span>
<span class="viewer">
    <span>stretched</span><br>
    <div class="canvas-wrapper">
        <canvas id="canvas-stretched"></canvas>
    </div>
</span>
<img id="banner" src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/The_southern_plane_of_the_Milky_Way_from_the_ATLASGAL_survey.jpg/320px-The_southern_plane_of_the_Milky_Way_from_the_ATLASGAL_survey.jpg"/>
  • Atleast this method does not reload the image as per the "Network" tab. Will have a look at this. –  Aug 25 '16 at 12:54
  • @SaiKrishnaDeep The image will not be reloaded according to the [specification](https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage) of the drawImage() method. I've added a few important quotes from the specification in the answer. –  Aug 25 '16 at 20:35
  • @A.Wolff The OP only wants a thumbnail. He will not use any method that checks the origin-clean flag. The snippet takes the images from another origin and works fine. I don't see the point of adding it to the answer. See [Security with canvas elements](https://html.spec.whatwg.org/#security-with-canvas-elements). –  Aug 29 '16 at 09:16
2

This line is the problem:

$('#info-poster').attr("src","myimage-thisimage.jpg");

The browser is reloading the image because you reasign(bad practice) the "src" attribute.
Instead, you can use CSS options to display/hide "myimage-thisimage.jpg".

Since you use jQuery, we can make use of the methods: hide/show.

You mentioned "clone", I don't think you mean HTML elements clonning.

Example: (live on JS Bin)

<img id="dummy" width="200" height="150" data-image='1' src="http://europunkt.ro/wp-content/uploads/2016/06/romania.jpg">

<!-- Hidden by default -->
<img style="display:none" id="info-poster"  width="200" height="150">



<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<script>
  var $dummy      = $("#dummy");
  var $infoPoster = $("#info-poster");

  var infoPosterHasLoaded = false;

  $dummy.on("mouseenter", function() {
    // add the src attribute ONLY once
    if(infoPosterHasLoaded === false){
      $infoPoster.attr("src", "http://www.ilovemaramures.com/wp-content/uploads/2012/05/Pasul-Prislop.jpg")
      infoPosterHasLoaded = true;
    }
    $infoPoster.show();
  });

  $dummy.on("mouseleave", function() {
    $infoPoster.hide();
  });
</script>

For a more fancy "hide/show" you can check jQuery Effects.


Edit - after I read your comment

In case you want to use the "data-image" attribute from the hovered element, check these objects: event.target, event.currentTarget, this

New JS Bin version.

Community
  • 1
  • 1
Marian07
  • 2,303
  • 4
  • 27
  • 48
  • Please read the comments on the other answers. That isn't my problem. Creating another seprate DOM for an existing image is the problem. –  Aug 20 '16 at 08:07
  • @Marian07 It is not loading the image even if you set the `src` attribute. I updated your sample a little bit and no network calls are being made. https://jsbin.com/dudiwedubo/1/edit?html,js,output – vdua Aug 20 '16 at 09:02
1

I believe that what you are wanting is possible with the jQuery .clone() and .append() function. See the example below.

jQuery.clone()

jQuery.append()

$(function() {
  $('img#ToClone').click(function(){
    var imageClone = $('#ToClone').clone();
    var cloneDestination = $('#CloneTo');
    cloneDestination.append(imageClone);
  });
});
                     
div
{
  padding:2px;
  background:blue;
}
div#CloneTo
{
  background:green;
}
img{
  height:50px;
  width:50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <p>Click on the red square to clone it below.</p>
  <img id="ToClone" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGlSURBVHhe7dCxTQQAEMRACiGk/86o4UmMxEpY/KW8R5ddtH575GnFOijWQbEOinVQrINiHRTroFgHxTr4Pdbbq2K/KNZgvyjWYL8o1mC/KNZgv3gq1uf7x389Fn5jvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgvyjWYL8o1mC/KNZgv3gq1utgvyjWYL8o1mC/KNZgvyjWYL/4452finVQrINiHRTroFgHxToo1kGxDor1tMfjC8KHtIEDA9XCAAAAAElFTkSuQmCC" />
</div>
<div id="CloneTo">
  <p>Clone should appear here.</p>
  <!-- The cloned image should appear here. -->
</div>
1

You may convert the image requests at server to respond with base64 string which you can store in your own cache.

Sample code below:

HTML

  <img id="image1Id" src="" />
  <input type="button" onclick='javascript:loadSomeThing("image1", "", "image1Id");' value="Load Image1" />

Script

var imageArray = [];

function loadSomeThing(key, someUrl, elementId) {
  var imageData = imageArray[key];
  if (!imageData) {
    imageData = ajaxGetImageData(someUrl);
    imageArray[key] = imageData;
  }
  document.getElementById(elementId).src = imageData;
}

function ajaxGetImageData(url) {
  //Code to get base64 image string
  return "data:image/jpeg;base64,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";
}

Demo

jsFiddle

James Jithin
  • 10,183
  • 5
  • 36
  • 51
0

You should let the browser to do the cache handling. I suggest you could have a <img id="info-poster" src="myimage-thisimage.jpg" class="hide-on-load"/>, so then if your browser want to load a new copy of the image, it would do it before the user mouse over your other images. (if it is a small/acceptable image that user may have to download it every page load)

Then you could simply bind $("img.bind-event").on("mouseenter", function() { $("#info-poster").show(); }); and $("img.bind-event").on("mouseleave", function() { $("#info-poster").hide(); });

Zay Lau
  • 1,856
  • 1
  • 10
  • 17
  • This was already suggested y @mplugjan in the comments. But, the problem is that I'm creating x2 DOM elements. It –  Aug 20 '16 at 06:49
0

IDEA

initial markup

<img data-src='myimg1.jpg' data-image='1' src='placeholder.jpg'>

after myimg1.jpg has loaded dynamically (*)

<img data-image='1' src='blob:asdfasdfasdfasdfadfa'>

Then on 'mouseenter'

infoPosterEl.src = thisImageEl.src 
// update image src to an object url(e.g. "blob:") will not bother http comm.

(*)

// Fetch acutal image as blob 
// Create object url for the blob
// Update this <img> src to the object url
Community
  • 1
  • 1
user943702
  • 956
  • 6
  • 12
0

You can store the path to each image in an array, iterate array using Array.prototype.forEach(), set the background of each <img> element using url("/path/to/image"); at mouseover of each <img> set background-size of menu element to 100% 100% at index of hovered <img> element within collection using Array.prototype.slice(), Array.prototype.splice(). The approach should request each image from server at most once, toggling the image displayed at menu element to correspond to hovered image.

var urls = ["http://placehold.it/100x100?text=1"
            , "http://placehold.it/100x100?text=2"
            , "http://placehold.it/100x100?text=3"
            , "http://placehold.it/100x100?text=4"
            , "http://placehold.it/100x100?text=5"]

, sources = []

, sizes = []

, imgs = document.querySelectorAll(".img")

, menu = document.querySelector(".menu");

function toggleImage(index) {
  this.onmouseover = function() {
    
    var curr = sizes.slice(0);
    curr.splice(index, 1, "100% 100%");
    menu.style.backgroundSize = curr.join(",");
  }
}

urls.forEach(function(path, index) {
  sources.push("url(" + path + ")");
  sizes.push("0% 0%");
  imgs[index].style.background = sources[index];
  toggleImage.call(imgs[index], index);
});

menu.style.background = sources.join(",");
menu.style.backgroundSize = sizes.join(",");
.menu {
  left: calc(100vw / 2 - 50px);
  position: relative;
  width: 75px;
  height: 75px;
  display: block;
}
<img class="img" width="100" height="100" src="" /><img class="img" width="100" height="100" /><img class="img" width="100" height="100" /><img class="img" width="100" height="100" /><img class="img" width="100" height="100" />
<div class="menu">
</div>
guest271314
  • 1
  • 15
  • 104
  • 177