javascriptcssdom-eventssettimeoutcleartimeout

clearTimeout() doesn't work when called from an event


This is a summarized example of another larger project I have. In this example I set a new style for my node when I click on it, once the new style is set I fire a setTimeout() to allow the style for a few seconds until it desappear. on the other hand, I have a keydown event that is supposed to cancel the Timeout when I press any key. So if the timeout is canceled with clearTimeout(), the box is supposed to stay styled, but it is not working. The clearTimeout() doesn't cancel the timeout, and doesn't prevent the style to be deleted.

Any idea about what is wrong with the clearTimeout()?

const box = document.querySelector("div")

let timeout

box.onclick = () =>{
    box.classList.add("black")
}

box.ontransitionend = () =>{
    timeout = setTimeout(() =>{
        box.classList.remove("black")
    }, 3000)
}

window.onkeydown = () =>{
    clearTimeout(timeout)
}
*{
    margin: 0px;
    padding: 0px;
}

.box{
    width: 200px;
    height: 200px;
    margin: auto;
    border: #333 1px solid;

    transition: all 1s ease-in-out 0s;
}

.black{
    background-color: black;
    border: #ccc 1px solid;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>
    <link rel="stylesheet" href="./styles.css">
</head>
<body>
    
    <div class="box">

    </div>

    <script src="./main.js"></script>
</body>
</html>


Solution

  • The ontransitionend event gets called for each transitioned style, in your case, that's your background-color, as well as border-top-color, border-right-color, border-bottom-color and border-left-color for the border style change due to the all in your transitional: all ... style. You could apply a check in your event handler to only apply your timeout when the transition is for CSS style background-color by using event.propertyName. This way you won't end up creating multiple timeouts and will be able to cancel it.:

    const box = document.querySelector("div");
    
    let timeout;
    
    box.onclick = () => {
      box.classList.add("black")
    }
    
    box.ontransitionend = (event) => {
      if (event.propertyName === "background-color") {
        timeout = setTimeout(() => {
          box.classList.remove("black")
        }, 3000);
      }
    }
    
    window.onkeydown = () => {
      clearTimeout(timeout);
    }
    * {
      margin: 0px;
      padding: 0px;
    }
    
    .box {
      width: 200px;
      height: 200px;
      margin: auto;
      border: #333 1px solid;
      transition: all 1s ease-in-out 0s;
    }
    
    .black {
      background-color: black;
      border: #ccc 1px solid;
    }
    <div class="box"></div>