skip to Main Content

I am trying to create a pure javascript (no jQuery) magnifier for when I hover over an area on a visible img, allowing me to zoom in to a larger segment of the original (which may be scaled to fit the screen).

[I think] I can make it work for a small example image (one sized 220 x 346px) but when I try a larger (1239 x 1754px) one then it seems to only work when looking at the top left corner of the visible img.

Can anyone assist in correcting the code to make this work?

let image = document.getElementsByClassName('visible')[0];
let mag = document.getElementsByClassName('zoomed')[0];

image.addEventListener('mousemove', showMag);

useImage(1);


function useImage(n) {
  if (n == 1) image.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Letraset_Lorem_Ipsum.jpg/220px-Letraset_Lorem_Ipsum.jpg';
  if (n == 2) image.src = 'https://data.webarchive.org.uk/crawl-test-site/resources/documents/lorem-ipsum.PDFA1-a.png';
  document.getElementById('nm').innerHTML = 'Image '+n;
  document.getElementById('notes').innerHTML = `${image.naturalWidth}/${image.naturalHeight}`;
}



function showMag() {
  const imageRect = image.getBoundingClientRect();

  let url = image.src;
  let bgi = 'url(' + url + ')';
  if (mag.style.backgroundImage !== bgi) mag.style.backgroundImage = bgi;

  const scaleX = image.naturalWidth / image.width;
  const scaleY = image.naturalHeight / image.height;

  // Position over the visible image....
  let hoverX = parseInt(event.clientX - imageRect.left);
  let hoverY = parseInt(event.clientY - imageRect.top);

  // Position over the zoomed view...
  const zoomedX = parseInt(hoverX * scaleX);
  const zoomedY = parseInt(hoverY * scaleY);
  mag.style.backgroundPosition = -zoomedX + 'px ' + -zoomedY + 'px';
  
  
  document.getElementById('notes').innerHTML=`<span>w/h=${image.naturalWidth}/${image.naturalHeight}</span> <span>hover x/y=${hoverX}/${hoverY}</span> <span>positioning = ${mag.style.backgroundPosition}</span> <span>scale=${scaleX}</span>`;
  
}
.container {
    white-space:nowrap;
}
img {
    vertical-align:top;
}
.visible {
    width:30%;
}
.zoomed {
    width:400px;
    height:400px;
    border:solid 1px red;
    background-repeat: no-repeat;
    background-size:100%;
    background-color:grey;
}
span {
    background-color:#e8e8e8;
    border-radius:9px;
    padding:4px;
    margin-right:10px;
    line-height:30px;
}
<button onClick='useImage(1)'>Image 1</button>
<button onClick='useImage(2)'>Image 2</button>
<hr>

<div id='nm'></div>
<div class='container'>
  <img class='visible'>
  <img class='zoomed' src=''>
</div>
<div id='notes'></div>

2

Answers


  1. The issue with your code is cause of the calculation of the zoom x and zoom y pos cause when u hovering over the larger image since the natural dimensions of the image are much larger than the displayed dimensions you need to take into account the offset of the visible portion within the larger image to fix this you should multiply the hover x and hover y coordinates by the ratio of the displayed image size to the natural image size additionally when calculating the background position for the zoomed image you need to take into account the offset of the visible image relative to the larger image I jus rewrote it below it should work now bud.

    <!DOCTYPE html>
    <html>
    <head>
        <title>Magnifier</title>
        <style>
            .container {
                white-space: nowrap;
            }
            img {
                vertical-align: top;
            }
            .visible {
                width: 30%;
            }
            .zoomed {
                width: 400px;
                height: 400px;
                border: solid 1px red;
                background-repeat: no-repeat;
                background-size: 100%;
                background-color: grey;
            }
            span {
                background-color: #e8e8e8;
                border-radius: 9px;
                padding: 4px;
                margin-right: 10px;
                line-height: 30px;
            }
        </style>
    </head>
    <body>
        <button onClick='useImage(1)'>Image 1</button>
        <button onClick='useImage(2)'>Image 2</button>
        <hr>
    
        <div id='nm'></div>
        <div class='container'>
            <img class='visible'>
            <div class='zoomed'></div>
        </div>
        <div id='notes'></div>
    
        <script>
            let image = document.getElementsByClassName('visible')[0];
            let mag = document.getElementsByClassName('zoomed')[0];
    
            image.addEventListener('mousemove', showMag);
    
            useImage(1);
    
            function useImage(n) {
                if (n == 1)
                    image.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Letraset_Lorem_Ipsum.jpg/220px-Letraset_Lorem_Ipsum.jpg';
                if (n == 2)
                    image.src = 'https://data.webarchive.org.uk/crawl-test-site/resources/documents/lorem-ipsum.PDFA1-a.png';
                document.getElementById('nm').innerHTML = 'Image ' + n;
                document.getElementById('notes').innerHTML = `${image.naturalWidth}/${image.naturalHeight}`;
            }
    
            function showMag(event) {
                const imageRect = image.getBoundingClientRect();
    
                let url = image.src;
                let bgi = 'url(' + url + ')';
                if (mag.style.backgroundImage !== bgi) mag.style.backgroundImage = bgi;
    
                const scaleX = image.naturalWidth / image.width;
                const scaleY = image.naturalHeight / image.height;
    
                // Position over the visible image....
                let hoverX = parseInt(event.clientX - imageRect.left);
                let hoverY = parseInt(event.clientY - imageRect.top);
    
                // Position over the zoomed view...
                const zoomedX = parseInt((hoverX * scaleX) - mag.offsetWidth / 2);
                const zoomedY = parseInt((hoverY * scaleY) - mag.offsetHeight / 2);
                mag.style.backgroundPosition = -zoomedX + 'px ' + -zoomedY + 'px';
    
                document.getElementById('notes').innerHTML = `<span>w/h=${image.naturalWidth}/${image.naturalHeight}</span> <span>hover x/y=${hoverX}/${hoverY}</span> <span>positioning = ${mag.style.backgroundPosition}</span> <span>scale=${scaleX}</span>`;
            }
        </script>
    </body>
    </html>
    Login or Signup to reply.
  2. The simplest way to implement a magnifying glass is to:

    • convert the pointer position inside the thumbnail into a normalized delta float value from 0..1
    • use that float delta value as the background-position percentage: deltaX|Y * 100 %
    • There’s a caveat! Since the natural width, height of the original image can be smaller than the magnify area, you need to switch the background-size from "auto" to "cover" – and you need to do that inside an image’s "load" event listener. This way, by using cover the browser will magnify the image on its own inside the zoom area, and the user can at least either view it a bit zoomed in (if a perfect square) or explore it vertically or horizontally – without any extra effort (code) from your side

    Example:

    // DOM utils:
    
    const el = (sel, par) => (par || document).querySelector(sel);
    const els = (sel, par) => (par || document).querySelectorAll(sel);
    
    // Task: Magnify:
    
    const images = [
      "https://i.stack.imgur.com/VFhxw.jpg",
      "https://i.stack.imgur.com/3NsGq.png",
      "https://i.stack.imgur.com/1P0rL.jpg"
    ];
    
    const elImgSource = el("#imgSource");
    const elImgZoomed = el("#imgZoomed");
    
    const useImage = (n) => {
      elImgSource.src = images[n];
      elImgSource.addEventListener("load", () => {
        const noFit = elImgSource.naturalWidth < elImgZoomed.offsetWidth || elImgSource.naturalHeight < elImgZoomed.offsetHeight;
        elImgZoomed.style.backgroundSize = noFit ? "cover" : "auto";
      });
      elImgZoomed.style.backgroundImage = `url(${elImgSource.src})`;
    };
    
    const showMag = (evt) => {
      const imageRect = elImgSource.getBoundingClientRect();
      const pointerX = evt.pageX - imageRect.left;
      const pointerY = evt.pageY - imageRect.top;
      const deltaX = pointerX / elImgSource.width; // Normalize (0 to 1 float)
      const deltaY = pointerY / elImgSource.height; // Normalize (0 to 1 float)
      elImgZoomed.style.backgroundPosition = `${deltaX * 100}% ${deltaY * 100}%`;
    };
    
    // Events:
    elImgSource.addEventListener("pointermove", showMag);
    
    els("[data-useimage]").forEach(elBtn => {
      elBtn.addEventListener("click", () => useImage(Number(elBtn.dataset.useimage)));
    });
    
    // Init:
    useImage(0);
    * { margin: 0; box-sizing: border-box; }
    
    .container {
      margin: 0 auto;
      display: flex;
      max-width: 1000px;
    }
    
    #imgSource {
      display: inline-block;
      touch-action: none;
      align-self: start;
      width: 30%;
    }
    
    #imgZoomed {
      width: 400px;
      height: 400px;
      background: no-repeat center;
      border: 1px solid #888;
    }
    <div class="container">
      <button data-useimage="0">Letraset</button>
      <button data-useimage="1">Lorem ipsum</button>
      <button data-useimage="2">Sea</button>
    </div>
    <div class="container">
      <img id="imgSource" src="#!">
      <div id="imgZoomed"></div>
    </div>

    Extras:

    • as you can notice I used the "pointermove" event, to make it handheld, touch devices friendly
    • the magnified image is initially centered (which is nicer!) – and that’s due to the CSS rule I used: background: no-repeat center;
    • the images buttons use a data-* attribute where the image index is stored. The images URLs are set inside an Array. On click the index is used instead of a complex if/else statement like the one you used.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search