Skip to content

Modal not opening after closing previous one with setTimeout

I have a modal that will open with a fading animation (opacity 0 to 1) and will close with the same animation (opacity 1 to 0). Everything is working except for the closing animation. I have a “fade” class and use JS in order to change the “animationName” depending if the user closes/opens the modal. I have setTimeout so the closing animation can execute, and the modal will be displayed to “none” otherwise the modal would close instantly with no animation because the display would execute immediately.

Due to the delay of setTimeout, whenever I close the modal & instantly spam click another image, the modal will NOT open until the delay for setTimeout is finished for some reason. However if I just wait even a split second after the modal closes & click another image, it will open.

There is probably much better ways of implementing an animation to open/close my modal, but this is the only one I could get to work. Open to new ideas of implementing an animation, thanks!

Here is a video explaining my issue. https://streamable.com/jflu55

https://jsfiddle.net/Boros/kseaoz1h/4/

"use strict";

const $ = selector => document.querySelector(selector);
const $all = selector => document.querySelectorAll(selector);

const gallery = $all("#gallery img, #gallery .video_container");
console.log(gallery.length);

const slides = $all("#my_modal div");
console.log(slides.length);

const closeModal = evt => {
    // Loops to find the slide that the user clicked on if needed
    for ( let i in slides ) {
        /* Checks the correct slide container the user clicked against the index of the slides.
         Loops until it finds it, or if clicked the close button */
        if ( evt.target == slides[i] || evt.target == $("#close_button") ) {

            $(".fade").style.animationName = "fadeOut";

            // Closes modal after animation finishes
            setTimeout( () => {
                $("#my_modal").style.display = "none";

                /* Will set the display of all the slides to none no matter what 
                in order to prevent undefined errors when clicking the close button */
                for (let i = 0; i < slides.length; i++) {
                    slides[i].style.display = "none";
                }
            }, 1998);
            
            // Allows page to be scrollable
            $("body").style.overflow = "initial";
        
            // Allows images to be tab accessible
            for (let i = 0; i < gallery.length; i++) {
                gallery[i].setAttribute("tabindex", "1");
            }

            const videos = $all(".video_slides video");
            // Will pause the video when you close out of the modal
            for (let p = 0; p < videos.length; p++) {
                videos[p].pause();
            }
        }
    }
}

const openModal = evt => {
    // Loops to find the index of the image or video that the user clicked on
    for ( let i in gallery ) {
        /* Checks the image or video the user clicked against the index of the gallery.
         Loops until it finds it */
        if ( evt.currentTarget == gallery[i] ) {
            // Prevents page from being scrollable inside the modal
            $("body").style.overflow = "hidden";

            // Prevents images inside #gallery from being tabbed to 
            for (let t = 0; t < gallery.length; t++) {
                gallery[t].removeAttribute("tabindex");
            }

            $("#my_modal").style.display = "initial";

            // Opening animation for modal
            $(".fade").style.animationName = "fadeIn";

            
            // Displays the correct image or video
            slides[i].style.display = "initial";

            // Closes modal when clicked outside the image
            slides[i].addEventListener("click", closeModal);
        }
    }
}

gallery.forEach(item => {
    item.addEventListener('click', evt => {
        openModal(evt);
    })
})

gallery.forEach(item => {
    item.addEventListener('keyup', evt => {
        if ( evt.keyCode == 13 ) {
            openModal(evt);
        }
    });
})

$("#close_button").addEventListener("click", closeModal);

$("#close_button").addEventListener("keyup", evt => {
    if ( evt.keyCode == 13 ) {
        closeModal(evt);
    }
});
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}
@keyframes fadeOut {
    from { opacity: 1; }
    to { opacity: 0; }
}
.fade {
    animation-duration: 2s;
}
#my_modal {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 9999;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.4);
    overflow: auto;
    margin: 0;
    padding: 0;
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
.img_slides, .video_slides {
    display: none;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 0;
    margin: 0;
    width: 100%;
    height: 100%;
}

EDIT: I found my issue only occurs if you spam click another image in the area that the closing image was NOT in. If you spam click another image where the closing image was in, it will NOT happen.

Answer

My issue was caused by the setTimeout delay getting reset due to spam clicking since there was an eventListener firing every time I clicked outside the image.

I removed the eventListener once the user clicks outside the image to close the modal which prevents the setTimeout delay from getting reset thus fixing the problem.

// Prevents  setTimeout delay from resetting due to spam clicking outside the image

slides[i].removeEventListener("click", closeModal);