Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Poordeveloper/d0858417c5f0550f7fe1 to your computer and use it in GitHub Desktop.
Save Poordeveloper/d0858417c5f0550f7fe1 to your computer and use it in GitHub Desktop.
Elastic Pull To Refresh Concept
<div class="phone">
<div class="phone__screen">
<div class="demo__top">
<p class="demo__text">Pull down content part</p>
<p class="demo__text half">Activities</p>
<p class="demo__text half">Saved</p>
<svg class="demo__svg" viewBox="0 0 375 37">
<path class="demo__svg__line" stroke-width="3" fill="none" d="M0,1 187,1 a18,18 0 0,1 0,36 a18,18 0 0,1 -12.6,-30.6"/>
<path class="demo__svg__smile" stroke-width="3" fill="none" d="M176.61,25 Q187,14 197.39,25" />
</svg>
</div>
<div class="demo__body">
<svg class="demo__svg-bg" viewBox="0 0 375 667">
<path class="demo__svg-bg__path" d="M0,0 C125,0 250,0 375,0 S375,0 375,0 L375,667 0,667z" />
</svg>
<div class="demo__content">
<div class="demo__el"></div>
<div class="demo__el"></div>
<div class="demo__el"></div>
</div>
</div>
</div>
</div>
$(document).ready(function() {
var animating = false;
var $body = $(".demo__body");
var $content = $(".demo__content");
var $line = $(".demo__svg__line");
var $smile = $(".demo__svg__smile");
var $bgPath = $(".demo__svg-bg__path");
var resetD = "M0,0 C125,0 250,0 375,0 S375,0 375,0 L375,667 0,667z";
var smileInit = "M176.61,25 Q187,19 197.39,25";
var smileMid = "M176.61,25 Q187,25 197.39,25";
var smileEnd = "M176.61,25 Q187,34 197.39,25";
var totalLen = $line[0].getTotalLength();
var upperLen = 187;
var circleLen = totalLen - upperLen;
var minLineLen = 60;
var lineDragBoundries = upperLen - minLineLen;
var pullDeltaY = 0;
var pullSlowCoef = 1.2;
var maxPullY = 150;
var minReleaseY = 120;
var contentYCoef = 0.4;
var maxContentY = maxPullY*contentYCoef;
var bodyOffsetX = 0;
var bodyW = $body.outerWidth();
var resetAT = 500;
var releaseStep1AT = 800;
var releaseWaitTime = 3000;
var easings = {
elastic: 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 SvgAddClass(el, cl) {
var regEx = new RegExp('(?:^|\\s)'+cl+'(?!\\S)', "gi");
if (regEx.test(el.attr("class"))) return;
el.attr("class", el.attr("class") + " " + cl);
};
function SvgRemoveClass(el, cl) {
var regEx = new RegExp('(?:^|\\s)'+cl+'(?!\\S)', "gi");
el.attr("class", el.attr("class").replace(regEx, ""));
};
function SvgPathTween(from, to, time, easing) {
var regex = /\d+(\.\d{1,2})?/g;
var fromD = from.attr("d");
var fm = fromD.match(regex);
var tm = to.match(regex);
var diff = [];
for (var i = 0; i < fm.length; i++) {
diff.push(+fm[i] - (+tm[i]));
}
var time = time || 600;
var curFrame = 0;
var frames = time / 1000 * 60;
var easing = easing || "elastic";
function animate() {
if (curFrame > frames) return;
var i = 0;
var newD = fromD.replace(regex, function(m) {
if (+m === 0 || // if nothing changed - skip
i % 2 === 0) { // in this demo I want to animate only y values
i++;
return m;
}
return +easings[easing](curFrame, +fm[i], 0 - diff[i++], frames).toFixed(2);
});
from.attr("d", newD);
curFrame++;
requestAnimationFrame(animate);
};
animate();
};
function pullChange(y, x) {
var pullY = (y <= maxPullY) ? y : maxPullY;
if (pullY < 0) pullY = 0;
var pullCoef = pullY / maxPullY;
var sdo = circleLen + pullCoef * lineDragBoundries;
var pullYCont = +(pullY*contentYCoef).toFixed(2);
var bodyX = parseInt(x - bodyOffsetX, 10);
if (bodyX < 0) bodyX = 0;
if (bodyX > bodyW) bodyX = bodyW;
var leftX = bodyX - 50;
if (leftX < 0) leftX = 0;
var rightX = bodyX + 50;
if (rightX > bodyW) rightX = bodyW;
$body.css("transform", "translate3d(0,"+ pullY +"px,0)");
$content.css("transform", "translate3d(0,"+ pullYCont +"px,0)");
$line.css("stroke-dashoffset", sdo);
$bgPath.attr("d", "M0,0 C"+(leftX-25)+",0 "+(leftX)+","+pullYCont+" "+bodyX+","+pullYCont+" S"+(rightX+25)+",0 375,0 L375,667 0,667z");
};
function reset() {
$body.addClass("reset");
$content.addClass("reset");
SvgAddClass($line, "reset");
SvgPathTween($bgPath, resetD);
setTimeout(function() {
$body.removeClass("reset").attr("style", "");
$content.removeClass("reset").attr("style", "");
SvgRemoveClass($line, "reset");
$line.attr("style", "");
animating = false;
}, resetAT);
};
function performMagic() {
$body.addClass("waiting");
SvgAddClass($line, "show-circle");
SvgPathTween($bgPath, resetD);
$content.addClass("reset");
setTimeout(function() {
SvgAddClass($line, "rotating");
}, releaseStep1AT*0.32);
setTimeout(function() {
$content.removeClass("reset").attr("style", "");
SvgAddClass($smile, "active");
setTimeout(function() {
SvgPathTween($smile, smileMid, 300, "inCubic");
setTimeout(function() {
SvgPathTween($smile, smileEnd, 300, "inCubic");
SvgAddClass($smile, "minified");
}, 500);
}, 700);
}, resetAT);
setTimeout(function() {
SvgRemoveClass($line, "rotating");
SvgRemoveClass($line, "show-circle");
SvgAddClass($line, "reset");
$body.removeClass("waiting").addClass("reset");
setTimeout(function() {
$body.removeClass("reset").attr("style", "");
SvgRemoveClass($line, "reset");
$line.attr("style", "");
SvgRemoveClass($smile, "minified");
SvgRemoveClass($smile, "active");
SvgPathTween($smile, smileInit, 50, "inCubic");
animating = false;
}, resetAT);
}, releaseWaitTime);
};
function release() {
animating = true;
if (pullDeltaY < minReleaseY) {
reset();
} else {
performMagic();
}
};
$(document).on("mousedown touchstart", ".demo__body", function(e) {
if (animating) return;
var startY = e.pageY || e.originalEvent.touches[0].pageY;
bodyOffsetX = $body.offset().left;
$(document).on("mousemove touchmove", function(e) {
var y = e.pageY || e.originalEvent.touches[0].pageY;
var x = e.pageX || e.originalEvent.touches[0].pageX;
pullDeltaY = (y - startY) / pullSlowCoef;
if (!pullDeltaY) return;
pullChange(pullDeltaY, x);
});
$(document).on("mouseup touchend", function() {
$(document).off("mousemove touchmove mouseup touchend");
if (!pullDeltaY) return;
release();
});
});
});
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
*, *:before, *:after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
font-size: 62.5%;
}
body {
background: #393A56;
}
$screenW: 37.5rem;
$screenH: 66.7rem;
$padSide: 2.4rem;
$padVert: 10.6rem;
$phoneW: $screenW + $padSide*2;
$phoneH: $screenH + $padVert*2;
$phoneBorderW: 0.4rem;
$topH: 9rem;
$lineColor: #4EDDC8;
$totalLen: 285.53px;
$lineLen: 187px;
$circleLen: $totalLen - $lineLen;
$resetAT: 0.5s;
$releaseStep1AT: 0.5s;
$releaseStep2AT: 0.3s;
.phone {
position: relative;
width: $phoneW;
height: $phoneH;
padding: $padVert - $phoneBorderW $padSide - $phoneBorderW;
margin: 3rem auto 3rem;
background: #000000;
border-radius: 5.6rem;
border: $phoneBorderW solid #B5B5B5;
&__screen {
overflow: hidden;
position: relative;
width: $screenW;
height: $screenH;
background: #39425A;
}
}
.demo {
position: relative;
height: 100%;
&__top {
position: relative;
height: $topH;
padding-top: 3rem;
text-align: center;
font-size: 0;
color: #CECED6;
text-transform: uppercase;
}
&__text {
font-size: 1.2rem;
margin-bottom: 1.6rem;
&.half {
display: inline-block;
vertical-align: top;
width: 50%;
}
}
&__body {
position: relative;
min-height: $screenH;
padding: 1.5rem;
transform: translate3d(0,0,0);
user-select: none;
&.reset {
transition: transform $resetAT;
transform: translate3d(0,0,0) !important;
}
&.waiting {
transition: transform $releaseStep2AT;
transform: translate3d(0,5rem,0) !important;
}
}
&__svg-bg {
overflow: visible;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
&__path {
fill: #FAFAFB;
stroke: transparent;
}
}
&__content {
&.reset {
transition: transform $resetAT;
transform: translate3d(0,0,0) !important;
}
}
&__el {
position: relative;
height: 16rem;
margin-bottom: 2rem;
background: #ffffff;
&:before {
content: "";
position: absolute;
left: 1.5rem;
top: 1.5rem;
width: 9rem;
height: 9rem;
border-radius: 1.5rem;
background: #5A534F;
}
&:after {
content: "";
position: absolute;
left: 12rem;
top: 1.5rem;
width: $screenW - 16.5rem;
height: 1.5rem;
border-radius: 0.7rem;
background: #5A534F;
color: #5A534F;
box-shadow: 0 3rem, 0 6rem;
}
}
&__svg {
overflow: visible;
position: absolute;
left: 0;
top: $topH - 0.3rem;
width: 100%;
height: 3.7rem;
&__line {
stroke: $lineColor;
stroke-dashoffset: $circleLen;
stroke-dasharray: $totalLen, $totalLen;
transform-origin: 187px 19px;
&.show-circle {
transition: stroke-dashoffset $releaseStep1AT*0.65;
stroke-dashoffset: -$lineLen !important;
}
&.rotating {
animation: rotating 0.5s infinite linear;
}
&.reset {
transition: stroke-dashoffset $resetAT;
stroke-dashoffset: $circleLen !important;
transform: rotate(0);
}
}
&__smile {
stroke: $lineColor;
stroke-dashoffset: 30px;
stroke-dasharray: 30px, 30px;
transform-origin: 187px 19px;
opacity: 0;
transition: opacity 0.1s, stroke-dashoffset 0.5s;
transition-delay: 0.2s;
transform: rotate(0);
&.active {
opacity: 1;
stroke-dashoffset: 0;
}
&.minified {
transition: stroke-dashoffset 0.5s 0.85s, opacity 0.1s 1.25s;
stroke-dashoffset: 25px;
opacity: 0;
animation: rotating 0.5s 0.35s infinite linear;
}
}
}
}
@keyframes rotating {
to {
transform: rotate(360deg);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment