Superposción de imágenes para comparar con Javascript

Maquetación de componente que permite comparar dos imágenes mediante superposición de una segunda imágen encima de la anterior
Superposción de imágenes para comprar con Javascript

Mediante Javascript se da la posibilidad de cambiar la porción a mostrar de una segunda imagen que se ha maquetado y colocado sobre otra imagen.

Puede verse funcionando en este Pen:

Código html de la superposición de imágenes

Para este ejemplo se han creado dos cajas para contener dos versiones de superposición.

La caja contenedora tiene la clase viewport. Dentro tendremos tres cajas

  • <div class="viewport__lightbox" data-img="480,550">
  • <div class="viewport__button">
  • <ul class="viewport__gallery">

Al primero, <div class="viewport__lightbox" data-img="480,550">, mediente el atributo data-img="480,550" se le pasa el tamaño de la imagen: ancho por alto, para luego usarlo con javascript para manipular el CSS: crear una nueva media querie en función de sus dimensiones y para calcular el ratio de la imagen y aplicarlo al padding de la caja. Tanto este <div> como su hijo tendrán ambos una imagen de fondo, que serán cambiadas con javascript

El segundo, <div class="viewport__button">, será para ubicar el botón que habilita/deshablita la funcionalidad de aplicar la superposición de imágenes

El tercero, <ul class="viewport__gallery">, contiene las imágenes en miniatura, sobre las que se habilita evento click para cambiar las imágenes y cuyas rutas son usadas como background del lightbox

<div class="viewport">
    <div class="viewport__lightbox" data-img="480,550">
        <div class="comparator"></div>
    </div>
    <div class="viewport__button">
        <button class="btn-compare js-compare" type="button">Comparar colores</button>
    </div>
    <ul class="viewport__gallery">
        <li><img class="img-compare js-img-compare" src="ws02-red_main.jpg" alt=""></li>
        <li><img class="img-compare js-img-compare" src="ws02-blue_main.jpg" alt=""></li>
        <li><img class="img-compare js-img-compare" src="ws02-green_main.jpg" alt=""></li>
    </li>
</div>

<div class="viewport">
    <div class="viewport__lightbox" data-img="480,550">
        <div class="comparator"></div>
    </div>
    <div class="viewport__button">
        <button class="btn-compare js-compare" type="button">Comparar colores</button>
    </div>
    <ul class="viewport__gallery">
        <li><img class="img-compare js-img-compare" src="ws06-gray_main.jpg" alt=""></li>
        <li><img class="img-compare js-img-compare" src="ws06-purple_main.jpg" alt=""></li>
        <li><img class="img-compare js-img-compare" src="ws06-red_main.jpg" alt=""></li>
    </li>
</div>

Código javascript de la superposición de imágenes

En este caso voy a poner los snippets de cada caso y comentarlo. En total son cinco funciones:

  • const handleCompare = event => {}
  • const handleActiveCompare = event => {}
  • const handleMove = event => {}
  • const loadViewportImages = viewport => {}
  • const createMediaQuerie = img => {}

Comparar imágenes con javascript

La función que permite añadir imagen como background es const handleCompare = event => {}

Se asocia esta función al evento click sobre cada una de las imágenes en miniatura. Se obtiene el atributo src para añadirlo al elemento lightbox como imagen de fondo

const handleCompare = event => {
    const currentViewport = event.target.closest('.viewport');
    const src = event.target.getAttribute('src');
    currentViewport.querySelector('.js-active-comparator').style.backgroundImage = `url(${src})`;
}

Activar comparación de imágenes con javascript

Esta función const handleActiveCompare = event => {} hace de "toggle" para activar / desactivar la funcionalidad. Se registra o se quita listener del evento mousemove asociada a la función handleMove.

Se usa la clase js-active-comparator para añadirla al DOM y poder hacer ajustes en el front

const handleActiveCompare = event => {
    const currentViewport = event.target.closest('.viewport');
    const lightbox = currentViewport.querySelector('.viewport__lightbox');
    const comparator = currentViewport.querySelector('.js-active-comparator');
    if (comparator) {
        comparator.querySelector('.comparator').style.width = '0px';
        comparator.removeEventListener('mousemove', handleMove, false );
        lightbox.classList.remove('js-active-comparator');
    } else {
        lightbox.classList.add('js-active-comparator');
        currentViewport.querySelector('.js-active-comparator .comparator').style.width = '0px';
        currentViewport.querySelector('.js-active-comparator').addEventListener('mousemove', handleMove, false );
    }
}

Detectar la posición del cursor para comparación de imágenes con javascript

Con la función const handleMove = event => {} se detecta la posición X dentro del lightbox. Dicho valor es pasado mediante javascript a la propiedad css width de <div class="comparator"></div>. Este elemento contiene una de las imagénes de fondo, está posicionado con "absolute" por encima del otro <div> para conseguir el efecto de comparación

const handleMove = event => {
    const currentViewport = event.target.closest('.viewport');

    let viewportX = event.clientX;
    let boxRectangle = event.target.getBoundingClientRect();
    let localX = viewportX - boxRectangle.left;
    let borderWidth = parseInt( window.getComputedStyle( event.target ).borderTopWidth, 10 );
    localX -= borderWidth;

    currentViewport.querySelector( ".js-active-comparator .comparator" ).style.width = localX + "px";
}

Carga de imágenes para comparación con javascript

Con la función const loadViewportImages = viewport => {} se cargan dos imágenes dentro del lightbox, partiendo de la lista de miniaturas disponibles.

A parte de obtener el src de la imágen para ubicarla con background, también se hacen dos operaciones. Una para obtener el ratio de la imagen llegada mediante el atributo data-img y así poder aplicar la proporción del contenedor lightbox. La otra operación, también usando el mismo atributo, se trata de realizar una nueva regla css para generar una mediaquerie basada en el ancho de la imagen y poder mantener "misma posición" de las dos imágenes a comprar

const loadViewportImages = viewport => {
    const lightbox = viewport.querySelector('.viewport__lightbox');
    const imageData = lightbox.getAttribute('data-img').split(',');
    const ratio = ( imageData[1] / imageData[0] ) * 100;

    const imagesGallery = viewport.querySelectorAll('.viewport__gallery .js-img-compare');
    for (let index = 0; index < imagesGallery.length; index++) {
        const img = imagesGallery[index];
        const src = img.getAttribute('src');
        if (index == 0) {
            lightbox.style.paddingTop = `${ratio}%`;
            lightbox.style.backgroundImage = `url(${src})`;
            createMediaQuerie(imageData);
        } else if (index == 1) {
            lightbox.querySelector('.comparator').style.backgroundImage = `url(${src})`;
        } else {
            break;
        }
    }
}

Crear media querie para comparación de imágenes con javascript

Como se ha comentado más arriba, se crea una función, const createMediaQuerie = img => {}, para generar una media querie para la regla del comparador lightbox. La media querie usa como breakpoint el ancho de la imagen. Se cambia la posición X de la imágen con cálculos del ancho del viewport

const createMediaQuerie = img => {
    let css = `
    @media screen and (max-width:${img[0]}px) {
        .viewport__lightbox .comparator {
            background-position-x: calc(calc(calc(100vw - 20px) / 2) - ${img[0]/2}px);
        }
    }`;
    let head = document.head || document.getElementsByTagName('head')[0];
    let style = document.createElement('style');

    head.appendChild(style);
    style.type = 'text/css';
    style.appendChild(document.createTextNode(css));
}

Registrar listener para comparación de imágenes con javascript

Ahora toca, una vez el DOM esté cargado, inicializar la aplicación y registrar los listener

Las imágenes a comparar son cargadas con las imágenes en miniatura que existan en el front

Se registra la activación al evento click para la comparación de imágenes y los eventos en cada una de las imágenes

document.addEventListener('DOMContentLoaded', () => {

    let viewports = document.querySelectorAll('.viewport');
    if (viewports) {
        viewports.forEach(viewport => {
            
            loadViewportImages(viewport);

            viewport.querySelector('.js-compare').addEventListener('click', handleActiveCompare, false);

            let compares = viewport.querySelectorAll('.js-img-compare');
            compares.forEach(compare => {
                compare.addEventListener( "click", handleCompare, false );
            });
        });
    }
    
});

Código css de la superposición de imágenes

Antes de comentar el código css, rescato una parte del html. Se trata de <div class="viewport__lightbox" data-img="480,550"> que tiene una imagen de fondo y otro div dentro <div class="comparator"> que tendrá otra imagen dentro.

El div interior será modificado con javascript para cambiar su ancho en función de la posición del cursor, y su imagen será posicionada en el eje X al comienzo del div

* {
    margin: 0;
    box-sizing: border-box;
}
html, body { height: 100%; }
body {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-around;
    background-color: rgba(0,0,0,.1);
}
ul {
    list-style-type: none;
    margin-block-start: 0;
    margin-block-end: 0;
    margin-inline-start: 0;
    margin-inline-end: 0;
    padding-inline-start: 0;
}
.viewport {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 10px;
    max-width: calc(100% - 20px);

    &__lightbox {
        max-width: 100%;
        width: 480px;
        background-repeat: no-repeat;
        background-position: center center;
        box-shadow: 0px 0px 5px 0px rgba(0,0,0,.2);
        margin-bottom: 16px;
        position: relative;
        padding-top: 50%;

        .comparator {
            width: 0;
            height: 100%;
            pointer-events: none;
            background-repeat: inherit;
            background-position: 0 center;
            position: absolute;
            top: 0;
            left: 0;
        }
        &.js-active-comparator {
            cursor: col-resize;
        }
    }
    &__button {
        margin-left: auto;
    }
    &__gallery {
        display: flex;
        justify-content: center;
        visibility: hidden;
        opacity: 0;
    }
}
.js-active-comparator {
    ~ .viewport__gallery {
        visibility: visible;
        opacity: 1;
        transition: all .3s ease-in;
        .js-img-compare {
            transform: translate(0,0);
            pointer-events: all;
            cursor: pointer;
        }
    }
}
.img-compare {
    width: 60px;
    margin: 10px 10px 16px;
}
.btn-compare {
    border: 0;
    color: #fff;
    background-color: #111;
    padding: 5px 10px;
    cursor: pointer;
}
.js-img-compare {
    transform: translate(15px,0);
    transition: all .3s ease-in;
    pointer-events: none;
}

Si se mira para tamaños de pantalla pequeños, y sin simulador de Mobile, las imágenes aparecen desalineadas (17px aproximado) por el espacio que ocupa la barra de Scroll

Código en mi GitHub