1

I'm sure that I'm making a rookie mistake here, but I've researched this solution all day, and I can't seem to understand why my code below isn't working.

The use case is a button that opens up a modal box inside a semi-transparent overlay, with the overlay covering everything else on the screen, including the button that opened it. The button is currently opening the modal and overlay just fine, and clicking anywhere outside of the modal box does indeed close it. But I don't understand why my set CSS transition isn't working.

I'm at a loss on this one, so I'd very much appreciate any advice that more seasoned developers can offer. Thank you so much in advance!

Best, Josh

var modalOverlay = document.getElementById('modalOverlay');
var modalButton = document.getElementById('modalButton');

modalButton.addEventListener('click', openModal);
window.addEventListener('click', closeModal);

function openModal() {
    modalOverlay.style.display = "flex";
    modalOverlay.style.opacity = "1";
}

function closeModal(event) {
    if (event.target == modalOverlay) {
        modalOverlay.style.opacity = "0";
        modalOverlay.style.display = "none";
    }
}
.modal-overlay {
    display: none;
    opacity: 0;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 10;
    width: 100vw;
    height: 100vh;
    align-items: center;
    justify-content: center;
    background-color: rgba(0,0,0,0.5);
    transition: all 1s ease-in-out;
}
.modal-box {
    width: 200px;
    height: 200px;
    background-color: blue;
}
<button id="modalButton" class="modal-button">Open Modal</button>
<div id="modalOverlay" class="modal-overlay">
    <div id="modalBox" class="modal-box"></div>
</div>
Toryn
  • 27
  • 7

2 Answers2

0

The display property doesn't play well with transition. Instead, just toggle between opacity 1 and 0.

const modalOverlay = document.getElementById('modalOverlay');
const modalButton = document.getElementById('modalButton');

modalButton.addEventListener('click', openModal);
window.addEventListener('click', closeModal);

function openModal() {
  modalOverlay.style.opacity = "1";
  modalButton.style.opacity = "0";
}

function closeModal(event) {
  if (event.target == modalOverlay) {
    modalOverlay.style.opacity = "0";
    modalButton.style.opacity = "1";
  }
}
.modal-overlay {
  opacity: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.5);
}

.modal-overlay,
.modal-button {
  transition: opacity 1s ease-in-out;
}

.modal-box {
  width: 200px;
  height: 200px;
  background-color: blue;
}

.modal-button {
  position: absolute;
  z-index: 2;
}
<button id="modalButton" class="modal-button">Button</button>
<div id="modalOverlay" class="modal-overlay">
  <div id="modalBox" class="modal-box"></div>
</div>
symlink
  • 11,984
  • 7
  • 29
  • 50
  • Thank you for responding so quickly, @symlink. Unless I'm misunderstanding, however, if I have the modalOverlay's display set to anything other than "none" by default, because it is covering the entire viewport, I won't have access to anything underneath the modalOverlay, even when its opacity is set to zero. I just tested this out, and that was the result that I got: no access to anything on the screen, including the "Open Modal" button. – Toryn Dec 10 '19 at 21:17
  • @Toryn that's why I gave the `button` element a position of absolute and a z-index of 2 – symlink Dec 10 '19 at 21:20
  • @Toryn position:absolute takes care of this. Click "Run code snippet" in my example – symlink Dec 10 '19 at 21:21
  • Ah, I now see that you added a z-index of 2 to the button. However, the use case that I'm looking for is for the modalOverlay to cover everything else on the screen when the modal is open, including the button. For example, a navbar icon that opens up a login modal with an overlay that covers everything else on the screen, including the navbar. The search continues. Thank you all the same! – Toryn Dec 10 '19 at 21:23
  • @Toryn just toggle the `opacity` of the button in the opposite direction as the overlay. See my edit. – symlink Dec 10 '19 at 21:28
0

display is not a property that can be transitioned. You need to make your animation take multiple steps. When you click the button, the modal should be made flex, but it should still be transparent. Then you need to transition the opacity up to 1 which is what CSS transitions can do.

You need to do the inverse whenever you close the modal. Transition back to opacity 0 and after the transition is done, mark it display: none.

var modalOverlay = document.getElementById('modalOverlay');
var modalButton = document.getElementById('modalButton');

modalButton.addEventListener('click', openModal);
window.addEventListener('click', closeModal);

function openModal() {
    // This will cause the browser to know 
    // that the element is display flex for a frame
    requestAnimationFrame(() => {
        modalOverlay.classList.add("modal-overlay--open");
        
        // Then when we wait for the next frame
        // the browser will now know that it needs 
        // to do the transition.  If we don't make
        // them separate actions, the browser
        // will try to optimize the layout and skip
        // the transition
        requestAnimationFrame(() => {
            modalOverlay.classList.add("modal-overlay--open-active");
        });
    });
}

function closeModal(event) {
    if (event.target == modalOverlay) {
        modalOverlay.classList.remove("modal-overlay--open-active");
        setTimeout(() => {
          modalOverlay.classList.remove("modal-overlay--open");
        }, 1100);
    }
}
.modal-overlay {
    display: none;
    opacity: 0;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 10;
    width: 100vw;
    height: 100vh;
    align-items: center;
    justify-content: center;
    background-color: rgba(0,0,0,0.5);
    transition: all 1s ease-in-out;
}

.modal-overlay.modal-overlay--open {
    display: flex;
}

.modal-overlay.modal-overlay--open-active {
    opacity: 1;
}

.modal-box {
    width: 200px;
    height: 200px;
    background-color: blue;
}
<button id="modalButton" class="modal-button">Open Modal</button>
<div id="modalOverlay" class="modal-overlay">
    <div id="modalBox" class="modal-box"></div>
</div>

Let's draw a little insight from how some other frameworks deal with these kinds of transitions. For example, Vue.js separates its enter/leave transitions into 6 sorts of phases:

  1. Enter: Starting state, added before entry (for you, display: flex and fully transparent)
  2. Enter Active: The transitioning state that sets the transition "target" (opacity towards 1 in your case)
  3. Enter To: What it should be after the transition is complete (we aren't going to bother with this)
  4. Leave: About to start leaving (nothing really needs to change here for us)
  5. Leave Active: Set the target state for your element so that it knows what to transition to (for us, we just remove the class that says opacity: 1)
  6. Leave To: We don't need this either

The main thing that we need to consider is that we need the browser to have the element in the page and "rendered" so that it will consider it for transitioning. That's why we add, in our example, the modal-overlay--open class which makes it flex. We then wait just a second and add the transition target class modal-overlay--open-active which causes the element to actually transition.

Then we do the same thing in reverse: remove modal-overlay--open-active so the browser knows to transition the element back to the "normal" style. We set a timeout to remove the display: flex class after the transition is done. You could use event listeners for this, but it's overkill for such an example.

tony19
  • 125,647
  • 18
  • 229
  • 307
zero298
  • 25,467
  • 10
  • 75
  • 100
  • Thank you for that insight, @zero298! I apologize for asking this, but do you mind helping me with how to rewrite my code above to accomplish this? I've been researching this for hours, but can't seem to wrap my head around how to structure the code. I really appreciate it! – Toryn Dec 10 '19 at 21:31
  • @Toryn Try with the example above. – zero298 Dec 10 '19 at 21:31
  • Aha, thank you so much, @zero298! I really appreciate it. I'd run into the concepts of adding and removing classes while researching a solution, but I hadn't been able to put the pieces together into how to structure this appropriately. Your response and example were super helpful! – Toryn Dec 10 '19 at 21:41
  • @Toryn sure, I added a little bit more detail and a comparison library that implements enter/leave transitions in a way that might make more sense than how I did in my example. – zero298 Dec 10 '19 at 21:44
  • You're a rock star, @zero298. That added explanation really does help, and the use of requestAnimationFrame is a great idea. Thank you again! – Toryn Dec 10 '19 at 22:12