Zoomable

A simple jQuery plugin that renders a medium-size image and punches in to show detail on hover.

by Andrew DeFanco

HTML

<body>
  <div class="zoom">
    <img src="http://visualparadox.com/images/no_linking_allowed_/1920/knownworld1920.jpg">
  </div>
  <p>

    Proin pulvinar tortor libero, sit amet malesuada mauris vestibulum nec. Pellentesque sodales dignissim lectus quis ultrices. Pellentesque porttitor risus eget nunc efficitur, vel consequat lacus faucibus. Etiam faucibus at enim mattis fermentum. Curabitur
    a tellus vestibulum, dictum massa eu, euismod ipsum. Nunc eget dictum augue, at consequat arcu. Nulla eget ex nec lectus sodales fermentum. Aenean ornare faucibus turpis at varius. Vivamus interdum auctor lacinia. Aenean venenatis massa ac metus commodo,
    a laoreet mauris imperdiet. Aenean ac mollis elit. Sed pretium accumsan pharetra. Vivamus mollis molestie nisi at elementum. Sed pellentesque nisi ac erat congue, et eleifend elit ultrices. Aenean vel sollicitudin ex.


  </p>
  <p>
    Duis efficitur justo elit, condimentum faucibus sem gravida et. Aenean pulvinar vestibulum augue eu ultricies. Duis eu venenatis ipsum. Phasellus id tincidunt magna. Aliquam erat volutpat. In vel orci sagittis, dictum elit ut, pharetra eros. Nullam ac
    dui neque. Donec eleifend convallis dictum. Aliquam consectetur tortor nec tincidunt elementum. Pellentesque ornare imperdiet ex nec consequat. In augue tortor, viverra ut egestas id, tincidunt non erat. Nullam bibendum urna in leo dignissim faucibus.
    Mauris semper libero at dui imperdiet interdum. Cras interdum eget eros ac eleifend. Aliquam placerat tellus et ultrices fermentum. Ut molestie lectus non sagittis condimentum.</p>

  <p>

    Nullam pulvinar id ante ac maximus. Duis non metus venenatis, gravida lorem vel, ullamcorper mi. Vestibulum id bibendum nisl. Suspendisse potenti. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec augue dui,
    blandit elementum efficitur vitae, venenatis ac libero. Pellentesque a enim eleifend, auctor magna ut, pellentesque nibh. Morbi porttitor...

CSS

html,
body,
section,
article,
h1,
h2,
p,
strong,
em,
a,
ul,
li,
div {
  margin: 0;
  border: 0;
  padding: 0;
  outline: 0;
  box-sizing: inherit;
  font: inherit;
  text-decoration: inherit;
  text-align: inherit;
  color: inherit;
  background: transparent;
}

body {
  text-align: left;
  font-size: 18px;
  font-family: sans-serif;
  background-color: lightgray;
  overflow: hidden;
}

p {
  padding: 20px;
}

div.zoom img {
  width: 100%;
}

ul {
  list-style: none;
}

.group:after {
  content: "";
  clear: both;
  display: block;
}

.zoom {
  margin: 20px;
  position: relative;
  width: 400px;
  height: auto;
  float: left;
}

.focusBox {
  position: absolute;
  transform: translate(-50%, -50%);
  border: 2px solid white;
}

.zoomed-image {
  top: 0;
  right: 0;
  bottom: 0;
  position: absolute;
  background-repeat: no-repeat;
  overflow: hidden;
}

.zoomed-image.active {
  display: block;
}

JavaScript

$.Zoom = function(el) {
  var $el = $(el);
  var img = $el.find("img:first-child");
  this.$el = $el;
  var boxSize = img[0].height * $(window).height() / img[0].naturalHeight;
  this.focusBoxX = boxSize;
  this.focusBoxY = boxSize;

  this.$el.on("mousemove", function(e) {
    this.showFocusBox(e);
    this.showZoom(e);
  }.bind(this));

  this.$el.on("mouseleave", function(e) {
    this.removeFocusBox();
    this.removeZoom();
  }.bind(this));
};

$.Zoom.prototype.showFocusBox = function(e) {
  if (this.$el.find(".focusBox").length === 0) {
    this.$el.append($('<div class="focusBox"></div>')
      .css("height", this.focusBoxY)
      .css("width", this.focusBoxX));

  }
  var newY = e.clientY - this.$el[0].offsetTop;
  var newX = e.clientX - this.$el[0].offsetLeft;
  var coords = this.inBounds(newX, newY);

  this.$el.find(".focusBox")
    .css("top", (coords[1]))
    .css("left", (coords[0]));
};

$.Zoom.prototype.inBounds = function(x, y) {
  var halfFocusBoxX = this.focusBoxX / 2
  if (x < halfFocusBoxX) {
    x = halfFocusBoxX;
  } else if (x > (this.$el.find("img:first-child").width() - halfFocusBoxX)) {
    x = (this.$el.find("img:first-child").width() - halfFocusBoxX);
  }
  
  var halfFocusBoxY = this.focusBoxY / 2
  if (y < halfFocusBoxY) {
    y = halfFocusBoxY;
  } else if (y > (this.$el.find("img:first-child").height() - halfFocusBoxY)) {
    y = (this.$el.find("img:first-child").height() - halfFocusBoxY);
  }
  
  return [x, y];
};

$.Zoom.prototype.removeFocusBox = function() {
  this.$el.find("div.focusBox").remove();
};


$.Zoom.prototype.showZoom = function(e) {
  var newY = e.clientY - this.$el[0].offsetTop;
  var newX = e.clientX - this.$el[0].offsetLeft;
  var zimg = $(".zoomed-image");
  var smallImg = this.$el.find("img:first-child");

  var getPercentage = function(val, focusBox, smallImg) {
    var scaled = ((val - (focusBox / 2)) / (smallImg - focusBox));
    if (scaled < 0) {
      scaled = 0;
    } else if (scaled > 1) {
     ...