JSFiddle - React, Tailwind, and code Playground

by BurpmanJunior

HTML

<img src="http://i.imgur.com/bBoJbVe.jpg" data-zoom />
<img src="http://i.imgur.com/FHtxfuG.jpg" data-zoom />

SCSS

.zoom-wrap{
    position: relative;
    box-sizing: border-box;
    img{
        display: block;
    }
    &.zoom-init{
        cursor: none;
    }
}
.zoom-loupe{
    transform: scale(0) translateX(-50%) translateY(-50%);
    transform-origin: top left;
    width: 40vmin;
    height: 40vmin;
    max-width: 200px;
    max-height: 200px;
    position: absolute;
    border-radius: 50%;
    box-sizing: border-box;
    border: 2px solid rgba(255, 255, 255, 0.5);
    background-color: rgba(200, 200, 200, 0.3);
    box-shadow: 0 5px 10px -5px rgba(0, 0, 0, 0.5),
                inset 0 5px 0px 5px rgba(255, 255, 255, 0.1);
    opacity: 0;
    transition: transform 300ms, opacity 300ms;
    pointer-events: none;
    z-index: 2;
    overflow: hidden;
    &:after{
        content: '';
        display: block;
        position: absolute;
        top: -20%;
        left: -20%;
        right: -20%;
        bottom: 20%;
        background-color: rgba(255, 255, 255, 0.08);
        border-radius: 50%;
        box-shadow: inset 0 75px 50px rgba(255, 255, 255, 0.3);
    }
    &.active{
        transform: scale(1) translateX(-50%) translateY(-50%);
        opacity: 1;
    }
    &.hide{
        display: none;
    }
    top: 0;
    left: 0;
}

img{
    max-width: 100%;
}
body{
    overflow-x: hidden;
}

JavaScript

/**
 * Zoom Loupe viewer
 */

var zoomLoupe = {
    init: function(){
        this.wrapElems();
        this.bindEvents();
    },
    wrapElems: function(){
        var elems = document.querySelectorAll('[data-zoom]');
        for(var i = 0; i < elems.length; i++){
            var zoom = elems[i].getAttribute('data-zoom');
            if(zoom === ''){ zoom = elems[i].src; }
            var img = elems[i].outerHTML.replace(/\sdata\-zoom[^\s\/\>]*/ig, '');
            var o = document.createElement('div');
            o.className = 'zoom-wrap';
            o.innerHTML = img + '<div class="zoom-loupe" style="background-image: url(' + zoom + ');"></div>';
            elems[i].parentNode.insertBefore(o, elems[i]);
            elems[i].parentNode.removeChild(elems[i]);
        }
    },
    bindEvents: function(){
        var _z = zoomLoupe;
        var elems = document.querySelectorAll('.zoom-wrap:not(.zoom-init)');
        for(var i = 0; i < elems.length; i++){
            var elem = elems[i];
            var zoom = elem.getAttribute('data-zoom-level');
            elem.className = elem.className + ' zoom-init';
            elem.addEventListener('mousemove', function(e){
                _z.calcPos(this, e);
            });
            elem.addEventListener('mouseenter', function(e){
                _z.openLoupe(this, e);
            });
            elem.addEventListener('mouseout', function(e){
                _z.closeLoupe(this, e);
            });
        }
    },
    calcPos: function(elem, e){
        var loupe =  elem.getElementsByClassName('zoom-loupe')[0];
        var rect = elem.getBoundingClientRect();
        var offsets = {
            x: (e.offsetX / rect.width) * 100,
            y: (e.offsetY / rect.height) * 100
        }
        loupe.style.left = offsets.x + '%';
        loupe.style.top = offsets.y + '%';
        loupe.style.backgroundPosition = offsets.x + '% ' + offsets.y + '%';
    },
    openLoupe: function(elem, e){
        var loupe = ...