You were going in the right direction with getBoundingClientRect
. By using this and applying some calculations on it on, I was able to come up with this
let imageResizing = false;
function zoomUnzoomImage(resizeEvent) {
if (!resizeEvent && this.classList.contains('zoomed')) {
this.classList.remove('zoomed');
this.style.transform = "";
document.querySelector('.image-backdrop').classList.remove('zoomed');
removeZoomOutListeners();
removeResizeListener();
} else {
let imageCordinates
if (resizeEvent) {
imageCordinates = this._originalImageCordinates;
}
else {
imageCordinates = getBoundingClientRect(this);
this._originalImageCordinates = imageCordinates;
}
const deviceRatio = window.innerHeight / window.innerWidth;
const imageRatio = imageCordinates.height / imageCordinates.width;
// Scale image according to the device and image size
const imageScale = deviceRatio > imageRatio ?
window.innerWidth / imageCordinates.width :
window.innerHeight / imageCordinates.height;
const imageX = ((imageCordinates.left + (imageCordinates.width) / 2));
const imageY = ((imageCordinates.top + (imageCordinates.height) / 2));
const bodyX = (window.innerWidth) / 2;
const bodyY = (window.innerHeight) / 2;
const xOffset = (bodyX - imageX) / (imageScale);
const yOffset = (bodyY - imageY) / (imageScale);
this.style.transform = "scale(" + imageScale + ") translate(" + xOffset + "px," + yOffset + "px) ";
this.classList.add('zoomed');
document.querySelector('.image-backdrop').classList.add('zoomed');
registersZoomOutListeners();
registerResizeListener();
}
}
function registersZoomOutListeners() {
// zoom out on scroll
document.addEventListener('scroll', scrollZoomOut);
// zoom out on escape
document.addEventListener('keyup', escapeClickZoomOut);
// zoom out on clicking the backdrop
document.querySelector('.image-backdrop').addEventListener('click', backDropClickZoomOut);
}
function removeZoomOutListeners() {
document.removeEventListener('scroll', scrollZoomOut);
document.removeEventListener('keyup', escapeClickZoomOut);
document.querySelector('.image-backdrop').removeEventListener('click', backDropClickZoomOut);
}
function registerResizeListener() {
window.addEventListener('resize', onWindowResize)
}
function removeResizeListener() {
window.removeEventListener('resize', onWindowResize)
}
function scrollZoomOut() {
if (document.querySelector('.zoomable-image.zoomed') && !imageResizing) {
zoomUnzoomImage.call(document.querySelector('.zoomable-image.zoomed'));
}
}
function backDropClickZoomOut() {
if (document.querySelector('.zoomable-image.zoomed')) {
zoomUnzoomImage.call(document.querySelector('.zoomable-image.zoomed'));
}
}
function escapeClickZoomOut(event) {
if (event.key === "Escape" && document.querySelector('.zoomable-image.zoomed')) {
zoomUnzoomImage.call(document.querySelector('.zoomable-image.zoomed'));
}
}
function onWindowResize() {
imageResizing = true;
if (document.querySelector('.zoomable-image.zoomed')) {
debounce(
function () {
zoomUnzoomImage.call(document.querySelector('.zoomable-image.zoomed'), true)
imageResizing = false;
}, 100)()
}
}
function getBoundingClientRect(element) {
var rect = element.getBoundingClientRect();
return {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left,
width: rect.width,
height: rect.height,
x: rect.x,
y: rect.y
};
}
function debounce(func, delay) {
let debounceTimer
return function () {
const context = this
const args = arguments
clearTimeout(debounceTimer)
debounceTimer
= setTimeout(() => func.apply(context, args), delay)
}
}
document.addEventListener('click', function (event) {
if (event && event.target && event.target.className.includes('zoomable-image')) {
zoomUnzoomImage.call(event.target)
}
});
figure {
margin: 0 0 0 0;
display: inline-block;
/* Stays same width as image contents */
background-color: whitesmoke;
}
img {
max-width: 100%;
/* Images should fit within their container by default */
height: auto;
background-color: lightgrey;
margin: auto;
transition: transform 0.3s;
}
.zoomable-image {
cursor: zoom-in;
}
.zoomable-image.zoomed {
cursor: zoom-out;
z-index: 100;
position: relative;
}
.image-backdrop.zoomed {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 50;
background-color: rgba(255, 255, 255, 0.95);
}
<div class="image-grid">
<img class="zoomable-image" src="https://picsum.photos/200/400?random=1" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/400/200?random=2" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/600/200?random=3" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/600/100?random=3" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/100/400?random=4" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/400/100?random=5" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/1000?random=6" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/300/400?random=7" loading="auto" />
<img class="zoomable-image" src="https://picsum.photos/400/300?random=8" loading="auto" />
</div>
<div class="image-backdrop"></div>