Own alert is executed twice

Viewed 53

I have a small class with which I can make and display an alert. The duration of the alert and the content is given to the class via a class function and controlled.

When I trigger the toast it is shown and hidden again correctly after the durationTime. But then the alert appears again and is hidden again shortly afterwards. In no part of the code do I call the alert twice.

Question: I suppose this could be related to the context and the timeset. But I have little experience with this. Can someone please help me? Thanks in advance! Max

class ToastHawaii {
  constructor(config = {}) {
    this.duration = typeof(config.duration) == 'undefined' ? 1000 : config.duration;
    this.toastContainer = config.toastContainer;
  }

    makeToast(d) {
        const html = `<div class="toast">
          <div>${d.title}</div>
          <div class="right">${d.content}</div>
        </div>`;
        this.toastContainer.innerHTML = html;
        this.toastContainer.classList.add("show");
        setTimeout(() => { 
          this.toastContainer.classList.remove("show");
        }, this.duration);
    }
}

const mA = new ToastHawaii({
  duration: 4000,
  toastContainer: document.querySelector('#myAlertContainer')
});

const content = `ICON`;

document.querySelector("button").addEventListener('click', () => {
  mA.makeToast({
    title: "hello", 
    content: content 
  });  
})
#myAlertContainer {
  position: fixed;
  left: 0%;
  top: 30px;  
  visibility: hidden;
  width:100%;
  z-index: 1;
  height:60px;
}
.toast {
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-radius: 8px;
  background: gray;
  color: #000;
  height:100%;
  padding: 0 16px;
  font-size: 0.8rem;
  font-weight: bold;
  margin: 0 auto;
  max-width: 350px;
}

.right {
  display: flex;
  gap: 5px;
  align-items: center;
}

#myAlertContainer.show {
  visibility: visible;
  animation: fadein 0.5s, fadeout 0.5s 2.5s;
}

@-webkit-keyframes fadein {
  from {top: 0; opacity: 0;} 
  to {top: 30px; opacity: 1;}
}

@keyframes fadein {
  from {top: 0; opacity: 0;}
  to {top: 30px; opacity: 1;}
}

@-webkit-keyframes fadeout {
  from {top: 30px; opacity: 1;} 
  to {top: 0; opacity: 0;}
}

@keyframes fadeout {
  from {top: 30px; opacity: 1;}
  to {top: 0; opacity: 0;}
}
<div id="myAlertContainer"></div>
<button>makeAlert!</button>
<h1>hello</h1>
<p>lorem ipsum</p>

3 Answers

It happens because your timeout is for 4s, but the fade-out animation is 3s. Adjusting your timeout according to your CSS animation duration will fix it.

class ToastHawaii {
  constructor(config = {}) {
    this.duration = typeof(config.duration) == 'undefined' ? 1000 : config.duration;
    this.toastContainer = config.toastContainer;
  }

    makeToast(d) {
        const html = `<div class="toast">
          <div>${d.title}</div>
          <div class="right">${d.content}</div>
        </div>`;
        this.toastContainer.innerHTML = html;
        this.toastContainer.classList.add("show");
        setTimeout(() => { 
          this.toastContainer.classList.remove("show");
        }, this.duration);
    }
}

const mA = new ToastHawaii({
  duration: 3000,
  toastContainer: document.querySelector('#myAlertContainer')
});

const content = `ICON`;

document.querySelector("button").addEventListener('click', () => {
  mA.makeToast({
    title: "hello", 
    content: content 
  });  
})
#myAlertContainer {
  position: fixed;
  left: 0%;
  top: 30px;  
  visibility: hidden;
  width:100%;
  z-index: 1;
  height:60px;
}
.toast {
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-radius: 8px;
  background: gray;
  color: #000;
  height:100%;
  padding: 0 16px;
  font-size: 0.8rem;
  font-weight: bold;
  margin: 0 auto;
  max-width: 350px;
}

.right {
  display: flex;
  gap: 5px;
  align-items: center;
}

#myAlertContainer.show {
  visibility: visible;
  animation: fadein 0.5s, fadeout 0.5s 2.5s;
  animation-fill-mode: forwards;
}

@-webkit-keyframes fadein {
  from {top: 0; opacity: 0;} 
  to {top: 30px; opacity: 1;}
}

@keyframes fadein {
  from {top: 0; opacity: 0;}
  to {top: 30px; opacity: 1;}
}

@-webkit-keyframes fadeout {
  from {top: 30px; opacity: 1;} 
  to {top: 0; opacity: 0;}
}

@keyframes fadeout {
  from {top: 30px; opacity: 1;}
  to {top: 0; opacity: 0;}
}
<div id="myAlertContainer"></div>
<button>makeAlert!</button>
<h1>hello</h1>
<p>lorem ipsum</p>

HTMHell's answer explains what's wrong and shows one way to fix it, but the problem with that solution is that your JavaScript code is no longer in charge of how long the alert is showing.

Another approach is to use transition rather than animation; the transition back to the invisible state won't start until the class is removed (see marker comments in the CSS):

class ToastHawaii {
  constructor(config = {}) {
    this.duration = typeof(config.duration) == 'undefined' ? 1000 : config.duration;
    this.toastContainer = config.toastContainer;
  }

    makeToast(d) {
        const html = `<div class="toast">
          <div>${d.title}</div>
          <div class="right">${d.content}</div>
        </div>`;
        this.toastContainer.innerHTML = html;
        this.toastContainer.classList.add("show");
        setTimeout(() => { 
          this.toastContainer.classList.remove("show");
        }, this.duration);
    }
}

const mA = new ToastHawaii({
  duration: 4000,
  toastContainer: document.querySelector('#myAlertContainer')
});

const content = `ICON`;

document.querySelector("button").addEventListener('click', () => {
  mA.makeToast({
    title: "hello", 
    content: content 
  });  
})
#myAlertContainer {
  position: fixed;
  left: 0;
  top: 0;
  opacity: 0;                        /* <=== */
  width:100%;
  height:60px;
  z-index: -1;                       /* <=== */
  transition: top 0.5s, opacity 0.5s /* <=== */
}
#myAlertContainer.show {
  z-index: 1;                        /* <=== */
  top: 30px;                         /* <=== */
  opacity: 1;                        /* <=== */
}
.toast {
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-radius: 8px;
  background: gray;
  color: #000;
  height:100%;
  padding: 0 16px;
  font-size: 0.8rem;
  font-weight: bold;
  margin: 0 auto;
  max-width: 350px;
}

.right {
  display: flex;
  gap: 5px;
  align-items: center;
}
<div id="myAlertContainer"></div>
<button>makeAlert!</button>
<h1>hello</h1>
<p>lorem ipsum</p>

That way, it doesn't matter how long your code leaves the element up for, it will always takes half a second to appear, then half a second to disappear. (You could subtract that time from the delay before removing the class if you want to count it as part of the time "duration" controls.)

If you want the disappearance to take a different amount of time from the appearance, you can put a different transition in the #myAlertContainer.show rule. That will control the appearance, the the one in the #myAlertContainer rule will control disappearance. Here's a really slow disappearance:

class ToastHawaii {
  constructor(config = {}) {
    this.duration = typeof(config.duration) == 'undefined' ? 1000 : config.duration;
    this.toastContainer = config.toastContainer;
  }

    makeToast(d) {
        const html = `<div class="toast">
          <div>${d.title}</div>
          <div class="right">${d.content}</div>
        </div>`;
        this.toastContainer.innerHTML = html;
        this.toastContainer.classList.add("show");
        setTimeout(() => { 
          this.toastContainer.classList.remove("show");
        }, this.duration);
    }
}

const mA = new ToastHawaii({
  duration: 4000,
  toastContainer: document.querySelector('#myAlertContainer')
});

const content = `ICON`;

document.querySelector("button").addEventListener('click', () => {
  mA.makeToast({
    title: "hello", 
    content: content 
  });  
})
#myAlertContainer {
  position: fixed;
  left: 0;
  top: 0;
  opacity: 0;                        /* <=== */
  width:100%;
  height:60px;
  z-index: -1;                       /* <=== */
  transition: top 2.5s, opacity 2.5s /* <=== */
}
#myAlertContainer.show {
  z-index: 1;                        /* <=== */
  top: 30px;                         /* <=== */
  opacity: 1;                        /* <=== */
}
.toast {
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-radius: 8px;
  background: gray;
  color: #000;
  height:100%;
  padding: 0 16px;
  font-size: 0.8rem;
  font-weight: bold;
  margin: 0 auto;
  max-width: 350px;
}

.right {
  display: flex;
  gap: 5px;
  align-items: center;
}
<div id="myAlertContainer"></div>
<button>makeAlert!</button>
<h1>hello</h1>
<p>lorem ipsum</p>

The problem is with your animation duration. Your fadeout animation duration is 2.5s and you remove show class from your settimeout after 4000 milisecond. That's why they are conflicting. I changed you animation duration and it is solved.

class ToastHawaii {
  constructor(config = {}) {
    this.duration = typeof(config.duration) == 'undefined' ? 1000 : config.duration;
    this.toastContainer = config.toastContainer;
  }

    makeToast(d) {
        const html = `<div class="toast">
          <div>${d.title}</div>
          <div class="right">${d.content}</div>
        </div>`;
        this.toastContainer.innerHTML = html;
        this.toastContainer.classList.add("show");
        setTimeout(() => { 
          this.toastContainer.classList.remove("show");
        }, this.duration);
    }
}

const mA = new ToastHawaii({
  duration: 4000,
  toastContainer: document.querySelector('#myAlertContainer')
});

const content = `ICON`;

document.querySelector("button").addEventListener('click', () => {
  mA.makeToast({
    title: "hello", 
    content: content 
  });  
})
#myAlertContainer {
  position: fixed;
  left: 0%;
  top: 30px;  
  visibility: hidden;
  width:100%;
  z-index: 1;
  height:60px;
}
.toast {
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-radius: 8px;
  background: gray;
  color: #000;
  height:100%;
  padding: 0 16px;
  font-size: 0.8rem;
  font-weight: bold;
  margin: 0 auto;
  max-width: 350px;
}

.right {
  display: flex;
  gap: 5px;
  align-items: center;
}

#myAlertContainer.show {
  visibility: visible;
  animation: fadein 0.5s, fadeout 0.5s 3.6s;
}

@-webkit-keyframes fadein {
  from {top: 0; opacity: 0;} 
  to {top: 30px; opacity: 1;}
}

@keyframes fadein {
  from {top: 0; opacity: 0;}
  to {top: 30px; opacity: 1;}
}

@-webkit-keyframes fadeout {
  from {top: 30px; opacity: 1;} 
  to {top: 0; opacity: 0;}
}

@keyframes fadeout {
  from {top: 30px; opacity: 1;}
  to {top: 0; opacity: 0;}
}
<div id="myAlertContainer"></div>
<button>makeAlert!</button>
<h1>hello</h1>
<p>lorem ipsum</p>

Related