.container
  .container__header
    %h1 Bouncy List
    
  .container__description
    Go on, add or remove items from the list and behold some sweet css animations

  %input.list-input(type="text" placeholder="Add Todo" value="Take out the Trash")
  %ul.list
    - initial_notes = ["Buy Milk", "Get Haircut", "Take Dog for a Walk"]
    - initial_notes.each do |a|
      %li.list__item
        #{a}
        %i.icon-close
    
    
       
%footer
  %a(href="https://twitter.com/code_dependant" class="twitter-follow-button" data-show-count="false")Follow @code_dependant
  %script !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');
View Compiled
*
  margin: 0
  padding: 0
  box-sizing: border-box

.cf
  &:before
    content: " "
    display: table
  &:after
    content: " "
    display: table
    clear: both
  *zoom: 1

$timing-function: ease-in-out
$duration: 1s
$delayRatio: 0.075s

$bounceLimitKeyframe: 30%
$bounceLimit: 50%

body
  margin-top: 30px

  background-color: #FFE181
  color: #696969
  font-family: 'Roboto Slab', sans-serif
  font-size: 14px
  
h1
  font-size: 48px
  color: #927963
  position: relative
  display: inline-block
  margin: 0
  margin-bottom: .5rem
  line-height: 1em
  
      
.container__header
  text-align: center
    
.container__description
  margin-bottom: 2.5em
  text-align: center
    
.padded
  padding: 1em
  
.container
  width: 50%
  @media(max-width: 500px)
    width: 80%
  
  margin: 10px auto


.pull-right
  float: right
    
input
  width: 100%
  @extend .padded
  box-shadow: inset 1px 1px 3px 0px rgba(134, 134, 134, 0.21)
  border: 1px solid #C5C5C5

.list
  list-style: none


.list__item--inserting
  transform: translateY(-100%)
  animation-name: slide-from-top
  animation-timing-function: $timing-function
    
.list__item--inserting-new
  transform: translateY(-200%) scaleY(1)
  animation-name: slide-from-top--new
      
.list__item--inserting-removed
  animation-name: slide-from-bottom--removed
    
.list__item--removing-sibling
  animation-name: slide-from-bottom
    
.list__item
  @extend .padded
  margin-bottom: 0.5em
  background-color: white
  border: 1px solid #C5C5C5
  border-radius: 3px
  backface-visibility: hidden
  box-shadow: 1px 1px 3px 0px rgba(134, 134, 134, 0.21)
  animation-duration: $duration
  animation-fill-mode: forwards
  @for $i from 1 through 30
    &:nth-child(#{$i})
      animation-delay: $i * $delayRatio
      z-index: $i

  .icon-close
    display: none
  &:hover
    .icon-close
      display: block

.list__item--new
  background-color: white
  transform-origin: 50% 0
  
  
.list--removing
  @for $i from 1 through 30
    &:nth-child(#{$i})
      animation-delay: $i * $delayRatio
      z-index: $i
  
  
// keyframe animations
@keyframes slide-from-top
  #{$bounceLimitKeyframe}
    transform: translateY($bounceLimit)
    
  100%
    transform: translateY(0)

@keyframes slide-from-top--new
 
  0%
    background-color: white

  10%
    background-color: #D3EBD3
    
  20%
    background-color: #D3EBD3
  
  #{$bounceLimitKeyframe}
    transform: translateY($bounceLimit) scaleY(1)
    
  100%
    background-color: white
    transform: translateY(0)
      
      
@keyframes slide-from-bottom
  0%
    transform: translateY(0)
    
  100%
    transform: translateY(-115%)
    
    
@keyframes slide-from-bottom--removed
  0%
    transform: translateY(0)
    
  99.9%
    transform: translateY(-115%)
    opacity: 0
    

.list__item--inserting-removed
  z-index: -1 !important
  
.hidden
  position: absolute

footer
  position: absolute
  bottom: 12px
  left: 20px



.icon-close
  cursor: pointer
  float: right
  min-width: 16px
  min-height: 16px
  opacity: 0.5
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADaGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjEgNjQuMTQwOTQ5LCAyMDEwLzEyLzA3LTEwOjU3OjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOkRGNDlBRTZGM0QyMDY4MTE4OEM2Q0I2MzFENzZGODEyIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkI0OTFDMUI4MkY0RTExRTJBRDhCRkIyOTY3QzFGRUNDIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkI0OTFDMUI3MkY0RTExRTJBRDhCRkIyOTY3QzFGRUNDIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzUuMSBNYWNpbnRvc2giPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpERUQwNTMyMzRCMjA2ODExODhDNkNCNjMxRDc2RjgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpERjQ5QUU2RjNEMjA2ODExODhDNkNCNjMxRDc2RjgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PiE1KOEAAAAhdEVYdENyZWF0aW9uIFRpbWUAMjAxMjoxMToyNSAxODowMzoxMlUcr24AAACHSURBVDhP7ZPBCYAwDEWLBwfz5GCCIDiS2wiCI3jR/yWR2qYYwaMPnq1pEj00oUANOziL3DPmpod7ImNuVsiiRuSesSITjL/mkTWh4gNssr4hq9HOT9zy9A9SNCFOvopiSg3c/A0+aBCj17c932x4xhzzWlsDVHKAGRzXES7QKqI8Y46MdggHJ8M7/n0+sSMAAAAASUVORK5CYII=)
View Compiled
# no jQuery, just for "fun". It was no fun at all.

prefix = (->
  styles = window.getComputedStyle(document.documentElement, "")
  pre = (Array::slice.call(styles).join("").match(/-(moz|webkit|ms)-/) or (styles.OLink is "" and [
    ""
    "o"
  ]))[1]
  dom = ("WebKit|Moz|MS|O").match(new RegExp("(" + pre + ")", "i"))[1]
  dom: dom
  lowercase: pre
  css: "-" + pre + "-"
  js: pre[0].toUpperCase() + pre.substr(1)
)()

animationEndEvent = prefix.lowercase+"AnimationEnd"
animationEndEvent = "animationend" if prefix.lowercase is "moz"
console.log animationEndEvent

window.insertAnimation = (text, list)->
  items = list.querySelectorAll(".list__item")
 
  for item in list.querySelectorAll(".list__item")
    item.classList.add("list__item--inserting")
    item.addEventListener animationEndEvent, (event)->
      event.target.classList.remove("list__item--inserting")
  
  
  newItem = document.createElement("li")
  newItem.classList.add "list__item" 
  newItem.classList.add "list__item--inserting-new"
  newItem.innerHTML = text
  closeIcon = document.createElement("i")
  closeIcon.classList.add "icon-close"
  newItem.appendChild closeIcon
  closeIcon.addEventListener "click", itemClickHandler  

  firstItem = items[0]
  list.insertBefore(newItem, firstItem)
  newItem.addEventListener animationEndEvent, (event)->
      event.target.classList.remove("list__item--inserting-new")
    
window.removeAnimation = (index, list)->
  items = list.querySelectorAll(".list__item")
  item = items.item(index)  
  item.classList.add("list__item--inserting-removed")
  postItems = document.querySelectorAll(".list__item:nth-child(1n+#{(index+2)})")

  postCount = 0
  handler = (event)->
    event.target.removeEventListener animationEndEvent, arguments.callee
    postCount++
    if postCount is postItems.length or postCount > 5
      list.removeChild(item)
      for removeItem in postItems
        removeItem.classList.remove("list__item--removing-sibling")
        
        
  
  for postItem in postItems
    postItem.classList.add("list__item--removing-sibling")
    postItem.addEventListener animationEndEvent, handler
    
  if postItems.length is 0
    item.addEventListener animationEndEvent, -> list.removeChild(item)
    
      
addListItem = ->
  input = document.querySelectorAll(".list-input").item(0)
  list = document.querySelectorAll(".list").item(0)
  
  todoText = input.value
  input.value = ""
  if todoText isnt ""
    insertAnimation(todoText, list)

    
removeListItem = (index)->
  list = document.querySelectorAll(".list").item(0)
  removeAnimation(index, list)
  
input = document.querySelectorAll(".list-input")[0]

input.addEventListener "keyup", (event)->
  if event.keyCode is 13
    addListItem()

itemClickHandler = (event)->
  listItem = event.target.parentNode
  event.target.removeEventListener "click", arguments.callee
  itemsNodeList = document.querySelectorAll(".list .list__item")
  itemsArray = Array.prototype.slice.call(itemsNodeList)
  index = itemsArray.indexOf(listItem)
  removeListItem(index)
  
for item in document.querySelectorAll(".list .list__item .icon-close")
  item.addEventListener "click", itemClickHandler
  
input.style.marginBottom = String(input.offsetHeight)+"px"

setTimeout ->
  addListItem()
, 1000



View Compiled

External CSS

  1. https://fonts.googleapis.com/css?family=Roboto+Slab

External JavaScript

This Pen doesn't use any external JavaScript resources.