Modal con imagen destacada animada al abrir el modal

Animación de modal al levantar el modal, moviendo de posición inicial hasta posición final en modal
Modal con animación de imagen destacada

Construcción de modal con animación de la imagen. La imagen que hace levantar el modal es usada como imagen de modal. Se realiza con animación de movimiento de imagen desde la posición original hasta la posición final que ocupa dentro del modal.

Puede verse funcionando en este Pen:

Código Html

Los modales son levantados mediante el atributo data-modal que están ubicadas en etiquetas <img>. El valor de data-modal apunta a la id del modal. Se crea una capa con la clase md-overlay para posicionarla detrás del modal y darle animación color con transparencia.

<svg display="none" xmlns="http://www.w3.org/2000/svg">
    <symbol id="svg-icon-close" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" id=".5786439775930057" xmlns="http://www.w3.org/2000/svg">
        <path d="M18 6L6 18M6 6l12 12"></path>
    </symbol>
</svg>

<div class="img-wrapper">
    <img src="https://dummyimage.com/800x500/444/fff" data-modal="1" alt="">
</div>
<div class="img-wrapper">
    <img src="https://dummyimage.com/800x400/666/fff" data-modal="2" alt="">
</div>
<div class="img-wrapper">
    <img src="https://dummyimage.com/600x400/555/fff" data-modal="3" alt="">
</div>

<div class="md" id="modal-1">
    <div class="md-image"></div>
    <div class="md-content">
        <header class="md-header">
            <h2 class="md-title">Modal 01</h2>
            <button class="md-close" type="button">
                <svg class="svg-icon-close">
                    <use xlink:href="#svg-icon-close" xmlns:xlink="http://www.w3.org/1999/xlink"></use>
                </svg>
            </button>
        </header>
        <div class="md-body">
            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur soluta, asperiores consequatur libero suscipit doloribus! Quidem deleniti nemo veritatis, ratione et totam maxime illo optio quaerat, perferendis, quos labore repellendus.</p>
            <p>Aspernatur soluta, asperiores consequatur libero suscipit doloribus! Quidem deleniti nemo veritatis, ratione et totam maxime illo optio.</p>
        </div>
        <footer class="md-footer">
            <a class="md-link" href="#">Go to</a>
            <a class="md-link" href="#">Go to</a>
            <button class="md-button" type="button">Action</button>
        </footer>
    </div>
</div>
<div class="md" id="modal-2">
    <div class="md-image"></div>
    <div class="md-content">
        <header class="md-header">
            <h2 class="md-title">Modal 02</h2>
            <button class="md-close" type="button">
                <svg class="svg-icon-close">
                    <use xlink:href="#svg-icon-close" xmlns:xlink="http://www.w3.org/1999/xlink"></use>
                </svg>
            </button>
        </header>
        <div class="md-body">
            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur soluta, asperiores consequatur libero suscipit doloribus! Quidem deleniti nemo veritatis, ratione et totam maxime illo optio quaerat, perferendis, quos labore repellendus.</p>
        </div>
    </div>
</div>
<div class="md" id="modal-3">
    <div class="md-image"></div>
    <div class="md-content">
        <header class="md-header">
            <h2 class="md-title">Modal 03</h2>
            <button class="md-close" type="button">
                <svg class="svg-icon-close">
                    <use xlink:href="#svg-icon-close" xmlns:xlink="http://www.w3.org/1999/xlink"></use>
                </svg>
            </button>
        </header>
        <div class="md-body">
            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur soluta, asperiores consequatur libero suscipit doloribus! Quidem deleniti nemo veritatis, ratione et totam maxime illo optio quaerat, perferendis, quos labore repellendus.</p>
            <p>Aspernatur soluta, asperiores consequatur libero suscipit doloribus! Quidem deleniti nemo veritatis, ratione et totam maxime illo optio.</p>
        </div>
        <footer class="md-footer">
            <a class="md-link" href="#">Go to</a>
            <button class="md-button" type="button">Action</button>
        </footer>
    </div>
</div>

<div class="md-overlay"></div>

Código Javascript

Cuando el DOM está cargado se crean listeners para registrar acciones de levantado y cierre de modales.

Se han creado dos funciones para levantado y cierre de modal:

  • const openModal = event => {}
  • const closeModal = () => {}

Con const closeModal = () => {} se cierra el modal actualmente activo, registrando y asociando el evento click tanto al botón de cierre como a la capa md-overlay

Con const openModal = event => {} se abre el modal asociado a la imgen clickeada. Dicha asociación se hace obteniendo del target el atributo data-modal y buscando la id del modal. Para hacer la animación se crea con JS un <div> con background-image correspondiente a la imagen seleccionada. A este nuevo elemento se le añade un clase CSS con posición fija y sus coordenadas son añadidas con JS con la posición de la imagen clickeada, posteriormente se cambia las coordenadas con la posición que ocupa la imagen dentro del modal. Las animaciones se realizan con CSS mediante transiciones y delays.

Existen dos funciones más como utilidad a la animación:

  • const imgPosition = el => {}
  • const move = (el, position) => {}

La función const imgPosition = el => {} devuelve, partiendo de un elemento HTML pasado como párametro, un objeto Javascript con las coordenadas X e Y y su ancho y alto.

La funcion const move = (el, position) => {} asigna atributos CSS mediante JS al elemento pasado como primer parámetro y sus atributos pasado como segundo parámetro (usando la función anterior).

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

  //Activar Listeners Modales
  var mds = document.querySelectorAll('[data-modal]');
  mds.forEach(function(md) {
    md.addEventListener('click', openModal, false);
  });

  var closes = document.querySelectorAll('.md-close');
  closes.forEach(function(close) {
    close.addEventListener('click', closeModal, false);
  });
  document.querySelector('.md-overlay').addEventListener('click', closeModal, false);

});

const imgPosition = el => {
  const rect = el.getBoundingClientRect();
  return { 
    top: rect.top+'px',
    left: rect.left+'px',
    width: rect.width+'px',
    height: rect.height+'px',
   }
}

const move = (el, position) => {
  el.style.left = position.left;
  el.style.top = position.top;
  el.style.width = position.width;
  el.style.height = position.height;
}

const openModal = event => {
  const target = event.target;
  target.style.pointerEvents = 'none';
  const srcImageClicked = target.getAttribute('src');
  const dataModalID = 'modal-'+target.getAttribute('data-modal');
  const modal = document.querySelector('#'+dataModalID);
  const mdImage = modal.querySelector('.md-image');
  modal.classList.add('md--active');
  mdImage.style.backgroundImage = 'url('+srcImageClicked+')';
  
  const bg = document.createElement('div');
  bg.classList.add('bg-animation');
  bg.style.backgroundImage = 'url('+srcImageClicked+')';
  
  //Inicio posición de la imagen
  move(bg, imgPosition(target))
  
  target.parentNode.append(bg);
  
  //Fin posición de la imagen
  move(bg, imgPosition(mdImage))

  setTimeout(() => {
    bg.remove();
    target.style.pointerEvents = '';
  }, 800);
}

const closeModal = () => {
  if (document.querySelector('.md--active')) document.querySelector('.md--active').classList.remove('md--active');
}

Código CSS

Este código no lo voy a explicar, es sencillo. Tan solo comentar que las animaciones son realizadas con transform y transition a diferentes capas, controlando duraciones y delays.

* {
    box-sizing: border-box;
    margin: 0;
    font-family: 'Montserrat', sans-serif;
    color: #1a1a1a;
    line-height: 1.4;
}
html, body {height: 100%;}
body {
    display: flex;
    flex-wrap: nowrap;
    flex-direction: column;
    align-items: center;
}
img {max-width: 100%; display: inline-block;}
p:not(:last-of-type) {
    margin-bottom: .8rem;
}
button {
    border: 0;
    padding: 0;
    background-color: transparent;
    cursor: pointer;
}
a {
    text-decoration: none;
}
.svg-icon-close {
    width: 24px;
    height: 24px;
}
[data-modal] {
    cursor: pointer;
}
.img-wrapper {
    text-align: center;
    img {
        max-width: 90%;
    }
}

.md {
    position: fixed;
    top: 50%;
    left: 50%;
    width: 96%;
    max-width: 1024px;
    min-width: 320px;
    z-index: 2000;
    visibility: hidden;
    backface-visibility: hidden;
    transform: translateX(-50%) translateY(-50%);

    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: minmax(100px, 200px) auto;
    
    &-overlay {
        position: fixed;
        width: 100%;
        height: 100%;
        visibility: hidden;
        top: 0;
        left: 0;
        z-index: 1000;
        opacity: 0;
        background: rgba(0,117,60,0.95);
        transition: all .3s ease .5s;
    }

    &--active {
        visibility: visible;
        transition: all .3s ease .5s;
        & ~ .md-overlay {
            opacity: 1;
            visibility: visible;
        }
        & .md-content {
            opacity: 1;
            transform: translateY(0);
            transition: transform .5s ease-in-out .8s, opacity .8s ease .8s;
        }
    }

    &-image {
        background-repeat: no-repeat;
        background-position: center;
        background-size: cover;
        z-index: 3000;
    }

    &-content {
        padding: 16px;
        background-color: #fff;
        opacity: 0;
        transform: translate(0, -100px);
        z-index: 2500;
    }

    &-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 16px;
    }

    &-body + &-footer {
        margin-top: 20px;
    }
    
    &-footer {
        display: flex;
        justify-content: space-between;
        align-items: center;
        .md-button {
            margin-left: auto;
            background-color: tomato;
            color: #fff;
            padding: 5px 15px;
        }
    }

    &-title {
        font-weight: 300;
        color: #000;
    }
    &-close {
        height: 24px;
    }
    &-link {
        border-bottom: 1px solid tomato;
        &:not(:first-of-type) {
            margin-left: 1rem;
        }
    }
}

.bg-animation {
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
    position: fixed;
    transition: all .5s;
    z-index: 3000;
}

@media (min-width: 768px) {
    .md {
        grid-template-columns: minmax(350px, 2fr) 5fr;  
        grid-template-rows: 1fr;

        &-content {
            padding: 2rem 2rem 1.5rem;
            transform: translate(-100px, 0);
        }
        &-header {
            margin-bottom: 1rem;
        }
        &-body + &-footer {
            margin-top: 2rem;
        }
    }
}

@media (max-height: 420px) {
    .md {
        position: absolute;
        top: 0;
        transform: translate(-50%, 0);
    }
}

Código en mi GitHub