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" /> |