Based on this awesome dribbble shot - https://dribbble.com/shots/1949368-Sidebar-animation
Completed without any animation libs, just simple js with requestAnimationFrame and custom easings.
A Pen by Nikolay Talanov on CodePen.
<div class="demo"> | |
<svg class="sidebar" viewBox="0 0 300 500"> | |
<path class="s-path" fill="#fff" d="M0,0 50,0 a0,250 0 1,1 0,500 L0,500" /> | |
</svg> | |
<div class="static"> | |
<div class="static__text">Pull white sidebar to the right</div> | |
</div> | |
<div class="sidebar-content"> | |
<div class="contact"> | |
<img src="http://i.imgur.com/ae5r9VA.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Ethan Hawke</span> | |
<span class="contact__status online"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/tnMJGyT.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Natalie Portman</span> | |
<span class="contact__status online"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/ae5r9VA.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Kevin Spacey</span> | |
<span class="contact__status online"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/tnMJGyT.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Rosamund Pike</span> | |
<span class="contact__status online"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/ae5r9VA.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Robert Redford</span> | |
<span class="contact__status online"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/tnMJGyT.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Scarlett Johansson</span> | |
<span class="contact__status online"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/ae5r9VA.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Tom Cruise</span> | |
<span class="contact__status"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/tnMJGyT.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Eva Green</span> | |
<span class="contact__status"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/ae5r9VA.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Paul Newman</span> | |
<span class="contact__status"></span> | |
</div> | |
<div class="contact"> | |
<img src="http://i.imgur.com/tnMJGyT.png" alt="" class="contact__photo" /> | |
<span class="contact__name">Thomas Break</span> | |
<span class="contact__status"></span> | |
</div> | |
<div class="search"> | |
<img src="http://i.imgur.com/t8TeL1S.png" alt="" class="search__img" /> | |
<input type="text" class="search__input" placeholder="Search" /> | |
</div> | |
</div> | |
<div class="chat"> | |
<span class="chat__back"></span> | |
<span class="chat__status">status</span> | |
<div class="chat__person"> | |
<span class="chat__online active"></span> | |
<span class="chat__name">Huehue Huehue</span> | |
</div> | |
<div class="chat__messages"> | |
<div class="chat__msgRow"> | |
<div class="chat__message mine">Such SVG, much Javascript, very CSS!</div> | |
</div> | |
<div class="chat__msgRow"> | |
<div class="chat__message notMine">Wow!</div> | |
</div> | |
<div class="chat__msgRow"> | |
<div class="chat__message notMine">Very elastic! Such easings!</div> | |
</div> | |
<div class="chat__msgRow"> | |
<div class="chat__message mine"> | |
Check out my other <a href="http://codepen.io/suez/public/" target="_blank">pens</a> | |
</div> | |
</div> | |
</div> | |
<input type="text" class="chat__input" placeholder="Your message" /> | |
</div> | |
</div> |
$(document).ready(function() { | |
var $svg = $(".sidebar"), | |
$demo = $(".demo"), | |
$path = $(".s-path"), | |
$sCont = $(".sidebar-content"), | |
$chat = $(".chat"), | |
demoTop = $demo.offset().top, | |
demoLeft = $demo.offset().left, | |
diffX = 0, | |
curX = 0, | |
finalX = 0, | |
frame = 1000 / 60, | |
animTime = 600, | |
sContTrans = 200, | |
animating = false; | |
var easings = { | |
smallElastic: function(t,b,c,d) { | |
var ts = (t/=d)*t; | |
var tc = ts*t; | |
return b+c*(33*tc*ts + -106*ts*ts + 126*tc + -67*ts + 15*t); | |
}, | |
inCubic: function(t,b,c,d) { | |
var tc = (t/=d)*t*t; | |
return b+c*(tc); | |
} | |
}; | |
function createD(top, ax, dir) { | |
return "M0,0 "+top+",0 a"+ax+",250 0 1,"+dir+" 0,500 L0,500"; | |
} | |
var startD = createD(50,0,1), | |
midD = createD(125,75,0), | |
finalD = createD(200,0,1), | |
clickMidD = createD(300,80,0), | |
clickMidDRev = createD(200,100,1), | |
clickD = createD(300,0,1), | |
currentPath = startD; | |
function newD(num1, num2) { | |
var d = $path.attr("d"), | |
num2 = num2 || 250, | |
nd = d.replace(/\ba(\d+),(\d+)\b/gi, "a" + num1 + "," + num2); | |
return nd; | |
} | |
function animatePathD(path, d, time, handlers, callback, easingTop, easingX) { | |
var steps = Math.floor(time / frame), | |
curStep = 0, | |
oldArr = currentPath.split(" "), | |
newArr = d.split(" "), | |
oldLen = oldArr.length, | |
newLen = newArr.length, | |
oldTop = +oldArr[1].split(",")[0], | |
topDiff = +newArr[1].split(",")[0] - oldTop, | |
nextTop, | |
nextX, | |
easingTop = easings[easingTop] || easings.smallElastic, | |
easingX = easings[easingX] || easingTop; | |
$(document).off("mousedown mouseup"); | |
function animate() { | |
curStep++; | |
nextTop = easingTop(curStep, oldTop, topDiff, steps); | |
nextX = easingX(curStep, curX, finalX-curX, steps); | |
oldArr[1] = nextTop + ",0"; | |
oldArr[2] = "a" + Math.abs(nextX) + ",250"; | |
oldArr[4] = (nextX >= 0) ? "1,1" : "1,0"; | |
$path.attr("d", oldArr.join(" ")); | |
if (curStep > steps) { | |
curX = 0; | |
diffX = 0; | |
$path.attr("d", d); | |
currentPath = d; | |
if (handlers) handlers1(); | |
if (callback) callback(); | |
return; | |
} | |
requestAnimationFrame(animate); | |
} | |
animate(); | |
} | |
function handlers1() { | |
$(document).on("mousedown touchstart", ".s-path", function(e) { | |
var startX = e.pageX || e.originalEvent.touches[0].pageX; | |
$(document).on("mousemove touchmove", function(e) { | |
var x = e.pageX || e.originalEvent.touches[0].pageX; | |
diffX = x - startX; | |
if (diffX < 0) diffX = 0; | |
if (diffX > 300) diffX = 300; | |
curX = Math.floor(diffX/2); | |
$path.attr("d", newD(curX)); | |
}); | |
}); | |
$(document).on("mouseup touchend", function() { | |
$(document).off("mousemove touchmove"); | |
if (animating) return; | |
if (!diffX) return; | |
if (diffX < 40) { | |
animatePathD($path, newD(0), animTime, true); | |
} else { | |
animatePathD($path, finalD, animTime, false, function() { | |
$sCont.addClass("active"); | |
setTimeout(function() { | |
$(document).on("click", closeSidebar); | |
}, sContTrans); | |
}); | |
} | |
}); | |
} | |
handlers1(); | |
function closeSidebar(e) { | |
if ($(e.target).closest(".sidebar-content").length || | |
$(e.target).closest(".chat").length) return; | |
if (animating) return; | |
animating = true; | |
$sCont.removeClass("active"); | |
$chat.removeClass("active"); | |
$(".cloned").addClass("removed"); | |
finalX = -75; | |
setTimeout(function() { | |
animatePathD($path, midD, animTime/3, false, function() { | |
$chat.hide(); | |
$(".cloned").remove(); | |
finalX = 0; | |
curX = -75; | |
animatePathD($path, startD, animTime/3*2, true); | |
animating = false; | |
}, "inCubic"); | |
}, sContTrans); | |
$(document).off("click", closeSidebar); | |
} | |
function moveImage(that) { | |
var $img = $(that).find(".contact__photo"), | |
top = $img.offset().top - demoTop, | |
left = $img.offset().left - demoLeft, | |
$clone = $img.clone().addClass("cloned"); | |
$clone.css({top: top, left: left}); | |
$demo.append($clone); | |
$clone.css("top"); | |
$clone.css({top: "1.8rem", left: "25rem"}); | |
} | |
function ripple(elem, e) { | |
var elTop = elem.offset().top, | |
elLeft = elem.offset().left, | |
x = e.pageX - elLeft, | |
y = e.pageY - elTop; | |
var $ripple = $("<div class='ripple'></div>"); | |
$ripple.css({top: y, left: x}); | |
elem.append($ripple); | |
} | |
$(document).on("click", ".contact", function(e) { | |
if (animating) return; | |
animating = true; | |
$(document).off("click", closeSidebar); | |
var that = this, | |
name = $(this).find(".contact__name").text(), | |
online = $(this).find(".contact__status").hasClass("online"); | |
$(".chat__name").text(name); | |
$(".chat__online").removeClass("active"); | |
if (online) $(".chat__online").addClass("active"); | |
ripple($(that),e); | |
setTimeout(function() { | |
$sCont.removeClass("active"); | |
moveImage(that); | |
finalX = -80; | |
setTimeout(function() { | |
$(".ripple").remove(); | |
animatePathD($path, clickMidD, animTime/3, false, function() { | |
curX = -80; | |
finalX = 0; | |
animatePathD($path, clickD, animTime*2/3, true, function() { | |
$chat.show(); | |
$chat.css("top"); | |
$chat.addClass("active"); | |
animating = false; | |
}); | |
}, "inCubic"); | |
}, sContTrans); | |
}, sContTrans); | |
}); | |
$(document).on("click", ".chat__back", function() { | |
if (animating) return; | |
animating = true; | |
$chat.removeClass("active"); | |
$(".cloned").addClass("removed"); | |
setTimeout(function() { | |
$(".cloned").remove(); | |
$chat.hide(); | |
finalX = 100; | |
animatePathD($path, clickMidDRev, animTime/3, false, function() { | |
curX = 100; | |
finalX = 0; | |
animatePathD($path, finalD, animTime*2/3, true, function() { | |
$sCont.addClass("active"); | |
$(document).on("click", closeSidebar); | |
animating = false; | |
}); | |
}, "inCubic"); | |
}, sContTrans); | |
}); | |
$(window).on("resize", function() { | |
demoTop = $demo.offset().top; | |
demoLeft = $demo.offset().left; | |
}); | |
}); |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> |
@import "compass/css3"; | |
*, *:before, *:after { | |
box-sizing: border-box; | |
margin: 0; | |
padding: 0; | |
} | |
$openSans: 'Open Sans', Helvetica, Arial, sans-serif; | |
$transTime: 200ms; | |
html, body { | |
font-size: 62.5%; | |
height: 100%; | |
} | |
button, input { | |
border: 0; | |
outline: none; | |
} | |
body { | |
background: linear-gradient( 45deg, #636F85, #6960A0); | |
} | |
.demo { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin-top: -25rem; | |
margin-left: -15rem; | |
width: 30rem; | |
height: 50rem; | |
box-shadow: 0 1rem 5rem rgba(0,0,0,0.3); | |
} | |
.static { | |
height: 100%; | |
font-size: 1.8rem; | |
font-family: $openSans; | |
background: #6D7ADA; | |
&:before, | |
&:after { | |
content: ""; | |
position: absolute; | |
left: 7rem; | |
width: 2rem; | |
height: 2rem; | |
margin-left: -1rem; | |
margin-top: -1rem; | |
border: 2px solid #fff; | |
border-left: none; | |
border-bottom: none; | |
transform: rotate(45deg); | |
animation: arrows 1.5s infinite; | |
} | |
&:before { | |
top: 15rem; | |
} | |
&:after { | |
top: 35rem; | |
} | |
&__text { | |
width: 9rem; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
margin-left: -5rem; | |
transform: translate3d(0,-50%,0); | |
color: #fff; | |
perspective: 1px; | |
backface-visibility: hidden; | |
} | |
} | |
.sidebar-content { | |
z-index: -1; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 20rem; | |
height: 100%; | |
padding-top: 1rem; | |
opacity: 0; | |
transition: opacity $transTime, z-index 0s 0s; | |
background-color: #E9EAF3; | |
overflow: hidden; | |
&.active { | |
z-index: 2; | |
opacity: 1; | |
} | |
} | |
.contact { | |
position: relative; | |
width: 100%; | |
height: 5rem; | |
padding-left: 1.7rem; | |
display: flex; | |
align-items: center; | |
cursor: pointer; | |
overflow: hidden; | |
&__photo { | |
border-radius: 50%; | |
margin-right: 1.5rem; | |
} | |
&__name { | |
font-size: 1.2rem; | |
font-family: $openSans; | |
} | |
&__status { | |
position: absolute; | |
top: 2.1rem; | |
right: 1.5rem; | |
width: 8px; | |
height: 8px; | |
border: 2px solid #00B570; | |
border-radius: 50%; | |
opacity: 0; | |
transition: opacity 0.3s; | |
&.online { | |
opacity: 1; | |
} | |
} | |
} | |
.search { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
width: 100%; | |
height: 5.5rem; | |
padding-left: 1.5rem; | |
background: #fff; | |
display: flex; | |
align-items: center; | |
} | |
svg { | |
overflow: visible; | |
} | |
.sidebar { | |
z-index: 1; | |
position: absolute; | |
top: 0; | |
left: 0; | |
display: block; | |
width: 100%; | |
height: 100%; | |
} | |
.s-path { | |
cursor: grab; | |
} | |
.cloned { | |
position: absolute; | |
z-index: 10; | |
transition: top 0.3s, left 0.3s; | |
transition-delay: 0.2s; | |
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); | |
&.removed { | |
transition: opacity 0.2s 0; | |
opacity: 0; | |
} | |
} | |
.chat { | |
display: none; | |
z-index: 5; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
padding: 2.5rem 0 5.5rem 2.5rem; | |
transition: opacity $transTime; | |
opacity: 0; | |
&.active { | |
opacity: 1; | |
&:before { | |
width: 100%; | |
} | |
} | |
&:before { | |
content: ""; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 0.5rem; | |
background: #1CC6AE; | |
width: 0; | |
transition: width 0.2s; | |
} | |
&__back { | |
position: relative; | |
display: inline-block; | |
width: 2rem; | |
height: 2rem; | |
margin-top: 0.5rem; | |
margin-left: -0.6rem; | |
cursor: pointer; | |
&:hover:before { | |
transform: translateX(-0.3rem) rotate(-45deg); | |
} | |
&:before { | |
content: ""; | |
position: absolute; | |
display: block; | |
top: 0.4rem; | |
left: 0.6rem; | |
width: 1.2rem; | |
height: 1.2rem; | |
border: 2px solid #545675; | |
border-right: none; | |
border-bottom: none; | |
transform: rotate(-45deg); | |
transition: transform 0.3s; | |
} | |
} | |
&__status { | |
position: absolute; | |
top: 2rem; | |
right: 6.5rem; | |
font-size: 1.2rem; | |
font-family: $openSans; | |
text-transform: uppercase; | |
color: #B1A9A9; | |
} | |
&__person { | |
display: inline-block; | |
position: absolute; | |
top: 3rem; | |
right: 6.5rem; | |
font-size: 2rem; | |
font-family: $openSans; | |
color: #36343D; | |
} | |
&__online { | |
position: absolute; | |
top: 50%; | |
left: -1.5rem; | |
margin-top: -3px; | |
width: 8px; | |
height: 8px; | |
border: 2px solid #00B570; | |
border-radius: 50%; | |
opacity: 0; | |
transition: opacity 0.3s; | |
&.active { | |
opacity: 1; | |
} | |
} | |
&__messages { | |
position: absolute; | |
top: 7.5rem; | |
left: 2.5rem; | |
width: 27.5rem; | |
height: 37rem; | |
padding-right: 2.5rem; | |
overflow-y: auto; | |
} | |
&__msgRow { | |
margin-bottom: 0.5rem; | |
&:after { | |
content: ""; | |
display: table; | |
clear: both; | |
} | |
} | |
&__message { | |
display: inline-block; | |
max-width: 60%; | |
padding: 1rem; | |
font-size: 1.4rem; | |
font-family: $openSans; | |
&.mine { | |
color: #2B2342; | |
border: 1px solid #DFDFDF; | |
} | |
&.notMine { | |
float: right; | |
color: #23244E; | |
background: #E9EAF3; | |
} | |
} | |
&__input { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
width: 100%; | |
height: 5.5rem; | |
padding: 1rem 1rem 1rem 4rem; | |
background-image: url('http://i.imgur.com/cM3yCT9.png'); | |
background-repeat: no-repeat; | |
background-position: 1rem 1.5rem; | |
background-color: #E9EAF3; | |
color: #2B2342; | |
font-size: 1.4rem; | |
font-family: $openSans; | |
} | |
} | |
.ripple { | |
position: absolute; | |
width: 10rem; | |
height: 10rem; | |
margin-left: -5rem; | |
margin-top: -5rem; | |
background: rgba(0,0,0,0.4); | |
transform: scale(0); | |
animation: animRipple 0.3s; | |
border-radius: 50%; | |
} | |
@keyframes animRipple { | |
to { | |
transform: scale(2.5); | |
opacity: 0; | |
} | |
} | |
@keyframes arrows { | |
to { | |
transform: translateX(100%) rotate(45deg); | |
opacity: 0; | |
} | |
} |
<link href="http://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" /> |