Using Javascript, we will make an animated ‘dismiss all’ icon similar to what you find on android. To hide our messages/alerts/toasts.

When I need icons for a web project, I usually use the easy and fantastic Font Awesome. Just because it’s easy and has everything…. Almost.

While working on a web app, I had the need for a dismiss all icon, to quickly dismiss any notifications/messages/alerts/toasts. But currently there is no dismiss all icon, which I found surprising considering the vast amount of icons they do have, blender phone anyone?

It’s a pretty simple icon, three bars stacked on top of each other, each offset slightly more than the previous. Indicating/imitating the notifications being pushed off the page and removed. Should be easy enough to make ourselves, and might as well throw in some animation too.

Creating the icon

Dismiss all icon

The HTML is simple, just a div with three more divs inside.

  <div class="dismiss-all-icon">
    <div class="icon-bar top"></span>
    <div class="icon-bar middle"></span>
    <div class="icon-bar bottom"></span>
  </div>

And then styling it to look like the icon. We turn the divs into small bars. and absolute position them inside it’s parent.

/* SCSS */  
.dismiss-all-icon {
    height: 13px;
    width: 20px;
    position: relative;
    overflow: hidden;
    .icon-bar {
      position: absolute;
      width: 14px;
      height: 3px;
      background-color: white;
      &.top {
        left:0px
      }
      &.middle {
        top: 5px;
        left: 3px;
      }
      &.bottom {
        right: 0px;
        top: 10px;
      }
    }
}

That’s it. That’s our icon, now we can do things with it.

Making the animation

We will be using one css keyframe animation to animate all three bars. called slide-out-right, here is the css for the keyframe:

//animation
@keyframes slide-out-right {
  0% {
    opacity: 1;
    transform: translateX(0);
  }
  100% {
    opacity: 0;
    transform: translateX(60%);
  }
}

Really simple, two frames. Fading out using opacity and transform: translateX to shift it right by 60%

Then creating a class called .slide-out, which will get applied to the parent element on click, animating the individual .icon-bar elements.

.slide-out .icon-bar {
  &.top {
    animation: slide-out-right 0.1s 0.1s forwards;
  }
  &.middle {
    animation: slide-out-right 0.1s 0.05s forwards;
  }
  &.bottom {
    animation: slide-out-right 0.1s 0s forwards;
  }
}

What I have done here, is animate for 0.1s(100ms), but apply a 0.05s animation delay between each element. Giving that familiar staggered look. You might want to tweak this to your taste. I have decided to keep it very quick at a total time of 0.2s.

Make it click-able

Put it all inside a button element, and add event listeners.

<button class="dismiss-all-btn" data-no-focus-on-click>
  <div class="dismiss-all-icon">
    <div class="icon-bar top"></div>
    <div class="icon-bar middle"></div>
    <div class="icon-bar bottom"></div>
  </div>
</button>

You noticed I added a data-attribute, data-no-focus-on-click. That is so we can prevent the button being automatically focused, (and given an outline) when clicked. While still allowing users to tab to it from the keyboard.

We do this by using preventDefault() on the ‘mousedown’ event, but only if an element has this attribute. (Be aware, if you do this, css :active styles in firefox will not work and you will need a workaround).

//Prevent focus on click
document.addEventListener("mousedown", event => {
  if (event.target.hasAttribute("data-no-focus-on-click")) {
    event.preventDefault();
  }
});

Since using event.target to check for an attribute wont work with our icon in the way, we add pointer-events:none to the icon. so it is ignored/clicked through. Or you can also use event.currentTarget in other situations where you have applied the event on the actual element.

Adding some Javascript, to disable the button on click, remove focus if it was focused (because it looks odd while animating), and adding our animation class:

const dismissAllBtn = document.querySelector('dismiss-all-icon');
dismissAllBtn.addEventListener('click', () => {
  dismissAllBtnEl.setAttribute('disabled','');
  dismissAllBtnEl.blur();
  dismissAllBtnEl.classList.add('slide-out');
...

We also want to remove the button once the animation is complete. So inside this we add a timeout, to add a class when it the animation is complete. which is just: display:none !important

...
  //remove button when animation complete 
  setTimeout(()=>{
    dismissAllBtn.classList.add('hidden');
  },210);
}

Result

See the Pen Animated dismiss all button by IpsumLorem16 (@ipsumlorem16) on CodePen.

That’s ok, but it’s not ready to use just yet. What about being able to hide/unhide when ever it is needed? You don’t want the button visible when there is nothing or little to dismiss. and you need to recall it when more notifications are added.

Finishing up

Adding more functions, and grouping everything into a single object. So we can do things like, dismissAllBtn.hide(); or dismissAllBtn.show(); to simplify it’s use.

First lets recreate what we have already done, then add onto it. Create the object, and add a slideout() method:

const dismissAllBtn = {
  element: document.querySelector(".dismiss-all-btn"),
  slideout() {
    this.disable();
    this.element.classList.add("slide-out");
    setTimeout(() => {
      this.element.classList.add("hidden");
      this.element.classList.remove("slide-out");
    }, 210);
  },
...

I also added a disable() method and enable() method, for whenever we need to disable and re-enable the button.

...
  enable() {
    this.element.removeAttribute("disabled");
  },
  disable() {
    this.element.setAttribute("disabled", "");
    this.element.blur();
  },
...

Add an init() function that for now, simply adds our click event listener. Also add handleClick() function):

...
  handleClick() {
    this.slideout();
    //dismiss notifications..or whatever you need to do goes here.
  },
  init() {
    this.element.addEventListener("click", () => {
      this.handleClick();
    });
  }
};

dismissAllBtn.init();

Now we can add some new functionality, such as show() and hide() to fade-in/out the button. Lets start with show() :

dismissAllBtn = {
...
  show() {
    this.element.classList.remove("hidden");
    this.enable();
  },
...

All that does is remove a class, how does it fade in? It used to be a bit tricky to smoothly animate in an element that has just had display: none; removed/added to the DOM.

Because if we give the button element an animation. Animations set on an element will trigger automatically on those changes. So as soon as we remove the .hidden class, our button fades in. And you can do this over and over. slideout(), then show().

.dismiss-all-btn {
  ...
  animation: fade-in 0.14s both;
}
@keyframes fade-in {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

You can see I added the fade-in animation with a time of 0.14s, as well as animation-fill-mode: both , which just means it starts at 0% as default, before the animation. And stays on 100% when done. This is not strictly necessary.

Adding the hide(), requires a timeout to wait for the css animation to end(or you can use an event listener), before hiding it, and removing that animating class.

dismissAllBtn = {
...
  hide() {
    this.disable();
    this.element.classList.add("fade-out");
    setTimeout(() => {
      this.element.classList.add("hidden");
      this.element.classList.remove("fade-out");
    }, 140);
  },

And the css:

/*Well actually SCSS..*/
dismiss-all-btn {
  ...
  &.fade-out {
    opacity:1;
    animation: fade-out .14s;
  }
  ...
}
@keyframes fade-out {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

Completed result:

See the Pen Animated dismiss all button { as an object } by IpsumLorem16 (@ipsumlorem16) on CodePen.

And that’s it, we are now finally done. It seems a lot of effort when broken down and written like this, for such a small component. But it’s not when you do it a few times, and makes it so much easier to use in your code/extend. And also for other people who come along, (or future you).