Skip to content

Instantly share code, notes, and snippets.

@CodeMyUI
Created August 7, 2018 20:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CodeMyUI/3e43a53b17795a28eb4bb8733a1b4121 to your computer and use it in GitHub Desktop.
Save CodeMyUI/3e43a53b17795a28eb4bb8733a1b4121 to your computer and use it in GitHub Desktop.
Animated List

Animated List

Experimenting with list interactions using css animations.

Disclaimer: Far from robust or bug free, but does succeed in demonstrating the concept

A Pen by Che on CodePen.

License.

.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');
# 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
*
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=)
<link href="https://fonts.googleapis.com/css?family=Roboto+Slab" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment