How can I make this transition smooth instead of it jumping up

Viewed 186

I am trying to implement a notification system in my app without the use of a library. Here is a gif of the issue: https://imgur.com/oRc11dM

And here is the jsfiddle of the gif: https://jsfiddle.net/w9yk7n54/

When you click on new notification button, already displayed notifications jump up to make room for the new notification and new notification slides in. I was wondering how I could make it so that they all smoothly go up together.

The notifications wont all be the same dimensions so I cant set static values for height/etc.

Thank you!

let btn = document.querySelector('button')
let container = document.querySelector('.notifications-container')

let notif_contents = [
  "<p>1</p><p>1</p><p>1</p><p>1</p>",
  "<p>test</p>",
  "<div><h1>testtesttest</h1><p>yoloalsdfasdf</p></div>"
]
let current = 0

btn.onclick = () => {

  let notif = document.createElement('div')
  notif.classList.add('notif')
  notif.innerHTML = notif_contents[current]
  notif.addEventListener('animationend', () => {
    notif.parentElement.removeChild(notif)
  })
  current++

  container.append(notif)
}
* {
  box-sizing: border-box;
}

.container {
  height: 300px;
  width: 400px;
  border: 1px solid black;
  position: absolute;
}

.notifications-container {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 200px;
  background: rgba( 0, 0, 0, 0.2);
  display: flex;
  flex-flow: column nowrap;
  justify-content: flex-end;
  overflow: hidden;
}

.notif {
  position: relative;
  width: 100%;
  padding: 10px;
  border: 1px solid black;
  animation: notifAnim 5s forwards;
  transition: all .2s;
  background: white;
}

button {
  z-index: 100;
  background: lightcoral;
  color: white;
  border: none;
  padding: 10px;
  font-size: 20px;
  margin: 5px;
}

@keyframes notifAnim {
  0% {
    transform: translateY( 100%)
  }
  20% {
    transform: translateY( 0)
  }
  80% {
    transform: translateY( 0)
  }
  100% {
    transform: translateY( 100%)
  }
}
<div class="container">
  <button>New Notification</button>
  <div class="notifications-container"></div>
</div>

2 Answers

Your notif container has justify-content: flex-end. This means that whenever you add a new one, the previous ones will be pushed up with the height of the new one.

The "fix" is to give each element a negative margin-top equal to its height and integrate in your current transition getting margin-top back to 0.

Example:

let btn = document.querySelector('button'),
  container = document.querySelector('.notifications-container'),
  notif_contents = [
  "<p>1</p><p>1</p><p>1</p><p>1</p>",
  "<p>test</p>",
  "<div><h1>testtesttest</h1><p>yoloalsdfasdf</p></div>",
  "<code>another.test()</code>",
  "<strong>another</strong> <i>test</i>"
]

btn.onclick = () => {
  let notif = document.createElement('div'),
    index = Math.floor(Math.random() * notif_contents.length)
  notif.classList.add('notif')
  notif.innerHTML = notif_contents[index]
  notif.addEventListener('animationend', () => {
    notif.parentElement.removeChild(notif)
  })

  container.append(notif)
  notif.style.marginTop = '-' + notif.offsetHeight + 'px'
}
* {
  box-sizing: border-box;
}

.container {
  height: 300px;
  width: 400px;
  border: 1px solid #888;
  position: absolute;
}

.notifications-container {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 200px;
  background: rgba( 0, 0, 0, 0.2);
  display: flex;
  flex-flow: column nowrap;
  justify-content: flex-end;
  overflow: hidden;
}

.notif {
  position: relative;
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-bottom: none;
  animation: notifAnim 5s forwards;
  background: white;
}

button {
  z-index: 100;
  background: lightcoral;
  color: white;
  border: none;
  padding: 10px;
  font-size: 20px;
  margin: 5px;
}

@keyframes notifAnim {
  0%,
  100% {
    transform: translateY( 100%)
  }
  20%,
  80% {
    transform: translateY( 0);
    margin-top: 0
  }
}
<div class="container">
  <button>New Notification</button>
  <div class="notifications-container"></div>
</div>

An idea is to insert new element with a height equal to 0 and you animate the height in addition to translate. Of course, you should use max-height since height are unknown and we cannot animate to auto:

let btn = document.querySelector('button')
let container = document.querySelector('.notifications-container')

let notif_contents = [
  "<p>1</p><p>1</p><p>1</p><p>1</p>",
  "<p>test</p>",
  "<div><h1>testtesttest</h1><p>yoloalsdfasdf</p></div>"
]
let current = 0

btn.onclick = () => {

  let notif = document.createElement('div')
  notif.classList.add('notif')
  notif.innerHTML = notif_contents[current]
  notif.addEventListener('animationend', () => {
    notif.parentElement.removeChild(notif)
  })
  current++

  container.append(notif)
}
* {
  box-sizing: border-box;
}

.container {
  height: 300px;
  width: 400px;
  border: 1px solid black;
  position: absolute;
}

.notifications-container {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 200px;
  background: rgba( 0, 0, 0, 0.2);
  display: flex;
  flex-flow: column nowrap;
  justify-content: flex-end;
  overflow: hidden;
}

.notif {
  position: relative;
  width: 100%;
  padding:0 10px;
  max-height:0px;
  border: 1px solid black;
  animation: notifAnim 5s forwards;
  transition: all .2s;
  background: white;
}

button {
  z-index: 100;
  background: lightcoral;
  color: white;
  border: none;
  padding: 10px;
  font-size: 20px;
  margin: 5px;
}

@keyframes notifAnim {
  0% {
    transform: translateY( 100%);
    max-height:0;
    padding:0 10px;
  }
  30% {
    transform: translateY( 0);
    max-height:300px;
    padding:10px;
  }
  80% {
    transform: translateY( 0);
    max-height:300px;
    padding:10px;
  }
  100% {
    transform: translateY( 100%);
    max-height:300px;
    padding:10px;
  }
}
<div class="container">
  <button>New Notification</button>
  <div class="notifications-container"></div>
</div>

Related