Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Drone Delivery Submit CSS Animation
<div class="demo">
<div class="demo__drone-cont demo__drone-cont--takeoff">
<div class="demo__drone-cont demo__drone-cont--shift-x">
<div class="demo__drone-cont demo__drone-cont--landing">
<svg viewBox="0 0 136 112" class="demo__drone">
<g class="demo__drone-leaving">
<path class="demo__drone-arm" d="M52,46 c0,0 -15,5 -15,20 l15,10" />
<path class="demo__drone-arm demo__drone-arm--2" d="M52,46 c0,0 -15,5 -15,20 l15,10" />
<path class="demo__drone-yellow" d="M28,36 l20,0 a20,9 0,0,1 40,0 l20,0 l0,8 l-10,0 c-10,0 -15,0 -23,10 l-14,0 c-10,-10 -15,-10 -23,-10 l-10,0z" />
<path class="demo__drone-green" d="M16,12 a10,10 0,0,1 20,0 l-10,50z" />
<path class="demo__drone-green" d="M100,12 a10,10 0,0,1 20,0 l-10,50z" />
<path class="demo__drone-yellow" d="M9,8 l34,0 a8,8 0,0,1 0,16 l-34,0 a8,8 0,0,1 0,-16z" />
<path class="demo__drone-yellow" d="M93,8 l34,0 a8,8 0,0,1 0,16 l-34,0 a8,8 0,0,1 0,-16z" />
</g>
<path class="demo__drone-package demo__drone-green" d="M50,70 l36,0 l-4,45 l-28,0z" />
</svg>
</div>
</div>
</div>
<div class="demo__circle">
<div class="demo__circle-inner">
<svg viewBox="0 0 16 20" class="demo__circle-package">
<path d="M0,0 16,0 13,20 3,20z" />
</svg>
<div class="demo__circle-grabbers"></div>
</div>
<svg viewBox="0 0 40 40" class="demo__circle-progress">
<path class="demo__circle-progress-line" d="M20,0 a20,20 0 0,1 0,40 a20,20 0 0,1 0,-40" />
<path class="demo__circle-progress-checkmark" d="M14,19 19,24 29,14" />
</svg>
</div>
<div class="demo__text-fields">
<div class="demo__text demo__text--step-0">Checkout</div>
<div class="demo__text demo__text--step-1">
Processing
<span class="demo__text-dots"><span>.</span></span>
</div>
<div class="demo__text demo__text--step-2">
Delivering
<span class="demo__text-dots"><span>.</span></span>
</div>
<div class="demo__text demo__text--step-3">It's on the way</div>
<div class="demo__text demo__text--step-4">Delivered</div>
</div>
</div>
<a href="https://dribbble.com/shots/7269049-Drone-Delivery-Progressing" target="_blank" class="icon-link">
<img src="http://icons.iconarchive.com/icons/uiconstock/socialmedia/256/Dribbble-icon.png">
</a>
<a href="https://twitter.com/NikolayTalanov/status/1195004656163807232" target="_blank" class="icon-link icon-link--twitter">
<img src="https://cdn1.iconfinder.com/data/icons/logotypes/32/twitter-128.png">
</a>
const $demo = document.querySelector('.demo');
let processing = false;
$demo.addEventListener('click', () => {
if (processing) return;
processing = true;
const $endListener = document.createElement('div');
$endListener.classList.add('demo-transitionend-listener');
$demo.appendChild($endListener);
const layoutTrigger = $demo.offsetTop;
$demo.classList.add('s--processing');
$endListener.addEventListener('transitionend', () => (
$demo.classList.add('s--reverting')
));
setTimeout(() => {
$demo.removeChild($endListener);
$demo.classList.remove('s--processing', 's--reverting');
processing = false;
}, 10000);
});
*, *:before, *:after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto', Helvetica, Arial, sans-serif;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
$baseClass: '.demo';
$mainColor: #61d4f1;
$darkerColor: #3dc1da;
$successColor: #53e2c2;
$darkerSuccess: #36d09d;
$yellow: #ecb400;
$demoW: 300px;
$droneLeft: 16px;
$circleSize: 40px;
$circleLeftPad: 30px;
$numOfSteps: 4;
$stepAT: 2s;
$grabPause: $stepAT / 5;
$grabPartAT: ($stepAT - $grabPause) / 2;
$grabRaiseDelay: $grabPartAT + $grabPause;
$grabbersShiftY: 15px;
$grabYChange: -70px;
$textYShift: 20px;
$textAT: $stepAT / 5;
$bgAT: 1s;
$progressAT: 0.5s;
$droneShiftX: $demoW * 0.85 - $droneLeft - 26px;
$droneShiftXDelay: $stepAT * 1.2;
$droneShiftXAT: $stepAT * 1.3;
$droneLandingDelay: $droneShiftXDelay + $droneShiftXAT;
$droneLandingAT: 0.3s;
$droneArmsDelay: $droneLandingDelay + $droneLandingAT - 0.1s;
$droneArmsAT: 0.3s;
$droneLeaveDelay: $droneArmsDelay + $droneArmsAT;
$leaveAT: 1.1s;
$revertDelay: $droneLeaveDelay + $leaveAT;
$revertShiftXAT: 0.8s;
$bgAnimDelay: $stepAT * ($numOfSteps - 1.8) + 0.2s;
$progressAnimDelay: $bgAnimDelay + 0.3s;
#{$baseClass} {
$bgTrans: background-color $bgAT;
position: relative;
width: $demoW;
height: 64px;
padding-left: $circleSize + $circleLeftPad;
padding-right: $circleLeftPad / 2;
border-radius: 10px;
background: $mainColor;
transition: $bgTrans;
cursor: pointer;
&:before,
&:after {
content: '';
position: absolute;
left: 5%;
bottom: 100%;
width: 14%;
height: 6px;
background: $darkerColor;
transition: transform $stepAT * 0.3, background-color $bgAT;
transform: scaleX(0);
transform-origin: 0 100%;
}
&:after {
$time: ($demoW * 0.66) / $droneShiftX * $droneShiftXAT;
left: 19%;
width: 66%;
transition: transform $time, background-color $bgAT;
}
&.s--processing {
background-color: $successColor;
transition-delay: $bgAnimDelay;
&:before,
&:after {
transform: scaleX(1);
background-color: $darkerSuccess;
}
&:before {
transition-delay: $grabRaiseDelay, $bgAnimDelay;
}
&:after {
transition-delay: $droneShiftXDelay, $bgAnimDelay;
}
}
&.s--reverting {
background-color: $mainColor;
transition: background-color $revertShiftXAT $revertShiftXAT;
&:before {
transform: scaleX(0) scaleY(0);
opacity: 0;
transition: transform 0.2s $revertShiftXAT - 0.05s, opacity 0.3s $revertShiftXAT - 0.2s;
}
&:after {
transform: scaleX(0);
opacity: 0;
transition: transform $revertShiftXAT - 0.05s, opacity 0.3s $revertShiftXAT - 0.2s;
}
}
@mixin isProcessing {
#{$baseClass}.s--processing & {
@content;
}
}
@mixin isReverting {
#{$baseClass}.s--reverting & {
@content;
}
}
svg {
overflow: visible;
fill: none;
stroke-linejoin: round;
}
&-transitionend-listener {
transition: opacity $revertDelay;
@include isProcessing {
opacity: 0;
}
}
&__drone-cont {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
&--takeoff {
z-index: -1;
opacity: 0;
@include isProcessing {
opacity: 1;
transform: translateY($grabYChange);
transition: transform $grabPartAT, opacity 0.2s;
transition-delay: $grabRaiseDelay;
}
}
&--shift-x {
@include isProcessing {
transition: transform $droneShiftXAT $droneShiftXDelay;
transform: translateX($droneShiftX);
}
}
&--landing {
@include isProcessing {
transform: translateY(24px);
transition: transform $droneLandingAT $droneLandingDelay;
}
}
}
&__drone {
position: absolute;
left: $droneLeft;
top: -12px;
width: 68px;
height: 56px;
stroke: #000;
stroke-width: 2px;
fill: none;
@keyframes tiltAnim {
8%, 24% {
transform: rotate(0);
}
35%, 70% {
transform: rotate(8deg);
}
85% {
transform: rotate(-4deg);
}
95%, 100% {
transform: rotate(0);
}
}
@include isProcessing {
$animTime: $droneShiftXAT + ($droneShiftXDelay - $grabRaiseDelay);
transform-origin: 50% 100%;
animation: tiltAnim $animTime $grabRaiseDelay;
}
&-leaving {
@include isProcessing {
transform: translate(150px, -150px) rotate(20deg) scale(0.3);
opacity: 0;
transition: transform $leaveAT $droneLeaveDelay, opacity $leaveAT/2 $droneLeaveDelay + $leaveAT/2;
}
}
&-arm {
--rotation: 0deg;
transform-origin: 68px 56px;
transform: rotate(var(--rotation));
&--2 {
transform: scaleX(-1) rotate(var(--rotation));
}
@include isProcessing {
--rotation: 25deg;
transition: transform $droneArmsAT $droneArmsDelay;
}
}
&-green {
fill: $mainColor;
@include isProcessing {
fill: $successColor;
transition: fill $bgAT $bgAnimDelay;
}
}
&-yellow {
fill: $yellow;
}
&-package {
$x: $droneShiftX * -2; // doubling, since drone size 1/2 of viewBox sizes
@keyframes revertAnim {
40%, 45% {
transform: translate($x, 0);
}
75% {
transform: translate($x, -100px);
}
100% {
transform: translate($x, 100px);
}
}
@include isReverting {
opacity: 0;
transition: opacity 0s $revertShiftXAT*2.5;
animation: revertAnim $revertShiftXAT*2.5;
}
}
}
&__circle {
position: absolute;
left: $circleLeftPad;
top: 50%;
width: $circleSize;
height: $circleSize;
margin-top: $circleSize / -2;
border-radius: 50%;
background: $darkerColor;
@include isProcessing {
background-color: $successColor;
transition: $bgTrans;
transition-delay: $bgAnimDelay;
}
@include isReverting {
background-color: $darkerColor;
transition: background-color $progressAT $revertShiftXAT * 1.2;
}
&-inner {
overflow: hidden;
position: absolute;
left: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
border-radius: inherit;
}
&-package {
width: 14px;
height: 18px;
stroke: #fff;
stroke-width: 3px;
stroke-linecap: round;
@include isProcessing {
transform: translateY($grabYChange);
transition: transform $grabPartAT $grabRaiseDelay;
}
@include isReverting {
transform: translateY(0);
transition: transform $revertShiftXAT/5 $revertShiftXAT*2;
}
}
&-grabbers {
--grabY: 0px;
--grabRotate: 0;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
&:before,
&:after {
content: '';
position: absolute;
right: 5px;
top: -12px;
width: 14px;
height: 8px;
border: 2px solid #000;
border-left: none;
border-bottom: none;
transform: translateY(var(--grabY)) rotate(var(--grabRotate));
transition: transform $grabPartAT;
}
&:before {
right: auto;
left: 5px;
transform: translateY(var(--grabY)) scaleX(-1) rotate(var(--grabRotate));
}
@keyframes grabAnim {
40%, 59.999% {
--grabY: #{$grabbersShiftY};
--grabRotate: 55deg;
}
60%, 100% {
--grabY: #{$grabYChange + $grabbersShiftY};
--grabRotate: 55deg;
}
}
@include isProcessing {
animation: grabAnim $stepAT forwards;
}
}
&-progress {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
stroke: #fff;
stroke-width: 2px;
@mixin pathParams($len) {
stroke-dasharray: $len, $len;
stroke-dashoffset: $len;
@include isProcessing {
stroke-dashoffset: 0;
transition: all $progressAT $progressAnimDelay;
}
@include isReverting {
stroke-dashoffset: $len;
transition: all $progressAT $revertShiftXAT*1.2;
}
}
&-line {
@include pathParams(125.68138122558594);
}
&-checkmark {
@include pathParams(21.21320343017578);
}
}
}
&__text-fields {
position: relative;
width: 100%;
height: 100%;
color: #fff;
font-size: 20px;
letter-spacing: 1.3px;
}
&__text {
position: absolute;
left: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
opacity: 0;
transform: translateY($textYShift);
will-change: opacity, transform;
pointer-events: none;
@keyframes textAnimation {
20%, 80% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY($textYShift * -1);
}
}
&--step-0 {
opacity: 1;
transform: translateY(0);
}
@include isProcessing {
transition: all $textAT;
&--step-0 {
opacity: 0;
transform: translateY($textYShift * -1);
}
@for $i from 1 through $numOfSteps - 1 {
&--step-#{$i} {
$delay: ($stepAT - $textAT) * ($i - 1);
animation: textAnimation $stepAT $delay;
}
}
&--step-#{$numOfSteps} {
transition-delay: ($stepAT - $textAT) * ($numOfSteps - 1);
transform: translateY(0);
opacity: 1;
}
}
@include isReverting {
&--step-0 {
opacity: 1;
transform: translateY(0);
transition: all $textAT $revertShiftXAT + 0.2s;
}
&--step-#{$numOfSteps} {
opacity: 0;
transform: translateY($textYShift);
transition: all $textAT $revertShiftXAT;
}
}
&-dots {
letter-spacing: -0.5px;
font-size: 26px;
@keyframes dotAnimation {
10%, 90% {
opacity: 0;
}
40%, 60% {
opacity: 1;
}
}
span {
opacity: 0;
animation: dotAnimation 1.2s 0.4s infinite;
}
&:before,
&:after {
content: '.';
opacity: 0;
}
&:before {
animation: dotAnimation 1.2s infinite;
}
&:after {
animation: dotAnimation 1.2s 0.8s infinite;
}
}
}
}
.icon-link {
z-index: 100;
position: absolute;
left: 5px;
bottom: 5px;
width: 32px;
img {
width: 100%;
vertical-align: top;
}
&--twitter {
left: auto;
right: 5px;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment