Create an Image Zoom Effect with bitty

October 2025

I'm doing Weird Web October. It's like Inktober. But, instead of doing a drawing every day based on a theme, you make a website. Day 11's theme was camera. I made this piece where you can move a frame around an image. When you click, it captures what's in the frame like taking a photo.

The page is the prototype I made to figure out how to make things work. It includes a zoom feature that's not in the final piece. I like having it as an option, but it felt more like using a camera without it on the weird web oct. site.

I used bitty to make things easier to wire up. It's not required for the effect. The technique could be replicated without it.

The Prototype

Here's the first run at the idea. All the code that makes it is below the image.

Output

The movie poster of the book cover for 
      A Scanner Darkly by Philip K. Dick

HTML

<bitty-1-3 data-connect="ImageZoomer" data-send="init">
  <img 
    data-receive="init"
    src="/neo-files/images/scanner-darkly-cover-1/scanner-darkly-cover-1.avif"
    alt="The movie poster of the book cover for 
      A Scanner Darkly by Philip K. Dick"
  />
</bitty-1-3>

CSS

:root {
  --zoomed-top: 20px; 
  --zoomed-left: 20px;
  --zoomed-bg-x: 0px;
  --zoomed-bg-y: 0px;
}

[data-connect=ImageZoomer] {
  position: relative;
  display: block;
  width: 200px; 
  margin: 50px auto; 
}

[data-receive=init] {
  display: block;
  width: 200px;
}


.zoomed-view {
  width: 30px; height: 30px;
  position: absolute;
  top: var(--zoomed-top); 
  left: var(--zoomed-left); 
  border: 1px solid red;
  background-image: url('/neo-files/images/scanner-darkly-cover-1/scanner-darkly-cover-1.avif');
  background-position: var(--zoomed-bg-x) var(--zoomed-bg-y);
  background-repeat: no-repeat;
}
JavaScript
function setPx(key, value) {
  document.documentElement.style.setProperty(key, `${value}px`);
}

function lerpIt(a, b, ratio) {
  return a + ratio * (b - a);
}

window.ImageZoomer = class {
  init(_event, el) {
    const boxWidth = 30;
    const boxHeight = 30;
    const zoomed = document.createElement("div");
    zoomed.classList.add("zoomed-view"); 
    el.parentNode.insertBefore(zoomed, el);
    document.addEventListener("mousemove", (event) => {
      const zoomX =
        Math.max (
          0, 
          Math.min(
            event.pageX - this.api.offsetLeft,
            this.api.getBoundingClientRect().width
          )
        );
      setPx(`--zoomed-left`, zoomX - (boxWidth / 2)); 
      const zoomY = 
        Math.max (
          0,
          Math.min(
            event.pageY - this.api.offsetTop,
            this.api.getBoundingClientRect().height
          )
        );
      setPx(`--zoomed-top`, zoomY - (boxHeight / 2)); 
      const bgX = lerpIt(0, el.naturalWidth, zoomX / this.api.clientWidth) * -1;
      const bgY = lerpIt(0, el.naturalHeight, zoomY / this.api.clientHeight) * -1;
      setPx(`--zoomed-bg-x`, bgX); 
      setPx(`--zoomed-bg-y`, bgY); 
    });
  }
}

TODO

The purpose of this viewer was to act as a prototype. It's fully functional, but there are some improvements I'll make if I come back to it:

  • deal with borders. right now they aren't taken into account so if there's a big border it should show up as 0.0 even and the corder of the image would be like 20x20y (assuming a border size of 20)
  • Set up so the output goes below the pointer like a picture in picture type thing.
  • Hide the viewer when the mouse isn't over the image.
  • Set the sizes from data-params so they can be configured via variables.
  • Deduplicate the formula for setting zoomX and zoomY.

Future Component

I'll turn this into a web component if I come back to it. It's a perfect use case for one that uses progressive enhancement.

It'd be a fun tool to have in the toolbox.

-a

end of line

Endnotes

bitty is a web component that uses signals to add reactivity to pages without requiring a framework. You can check it out here:

bitty.alanwsmith.com

I found the example I got the technique from on a thecodeplayer.com. I'd never heard of the site before. It's another version of the idea I kick around from time to time about updating code to show tutorials.

There's no date on the page. Given that the github repo I found it from is 11 years old it's been around for at least that long. I was expecting the link to be dead. Credit to whoever is keeping it up and the link intact.

(Of course, the page refreshed and trigged an ad reload every few minutes which helps to explain the longevity. Same add every time though which is funny or, you know silly since the trigger almost certainly counts as a new ad even though nothing changed for me.)

References

This was a new one to me. I didn't realized you could straight pull the size of an image. I used to do all kinds of hacks to send the info from the server for that. Of course, if you need height, that's covered too.

The original example I saw used a new Image() object and added the img tag's src attribute to it. I like .naturalWidth and .naturalHeight a lot better since it doesn't require the new object.

This is the original page I got the technique from. I had a pretty good idea of how things world work, but it's always nice to start with an example. It included this style call to make a more magnifiying glass effect. I dropped it since I just wanted the zoom.

.zoomed-view {
  border-radius: 100%;
  box-shadow: 0 0 0 7px rgba(255, 255, 255, 0.85), 
  0 0 7px 7px rgba(0, 0, 0, 0.25), 
  inset 0 0 40px 2px rgba(0, 0, 0, 0.25);
}

The jQuery plugin that pointed me to the codeplayer.com post. It's the home page for this repo.