Experimenting with list interactions using css animations.
Disclaimer: Far from robust or bug free, but does succeed in demonstrating the concept
.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" /> |