분류
design
재생 및 일시 정지 애니메이션 업로드-멋진 업 로더 구성 요소
본문
재생 및 일시 정지 애니메이션 업로드
- https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
- https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js
- https://cdnjs.cloudflare.com/ajax/libs/egjs-jquery-pauseresume/2.0.1/pauseresume.min.js
HTML :
<div class="upload">
<div class="text">
<strong><span>Uploading</span> 3 files</strong>
<div>
<small>%</small>
<div>
<small>
<span data-seconds>0</span> seconds left
</small>
<small>Paused</small>
</div>
</div>
</div>
<nav>
<ul>
<li>
<a href="" class="btn play">
<svg viewBox="0 0 16 16" fill="currentColor">
<path d="M4.09437962,6 L13,6 C14,5.89116285 14.5,5.39116285 14.5,4.5 C14.5,3.60883715 14,3.10883715 13,3 L12,3 L4.09437962,3 L3,3 C2,3.10728568 1.5,3.60728568 1.5,4.5 C1.5,5.39271432 2,5.89271432 3,6 L4.09437962,6 Z"></path>
</svg>
<svg viewBox="0 0 16 16" fill="currentColor">
<path d="M4.09437962,6 L13,6 C14,5.89116285 14.5,5.39116285 14.5,4.5 C14.5,3.60883715 14,3.10883715 13,3 L12,3 L4.09437962,3 L3,3 C2,3.10728568 1.5,3.60728568 1.5,4.5 C1.5,5.39271432 2,5.89271432 3,6 L4.09437962,6 Z"></path>
</svg>
</a>
</li>
<li>
<a href="" class="btn cancel"></a>
</li>
</ul>
<ul>
<li>
<a href="">
<svg viewBox="0 0 16 16" fill="currentColor">
<polygon points="7.4,10 6,8.6 3.3,11.3 0,8 0,16 8,16 4.7,12.7 "></polygon>
<polygon points="11.3,3.3 8.6,6 10,7.4 12.7,4.7 16,8 16,0 8,0 "></polygon>
</svg>
</a>
</li>
<li>
<a href="" class="dots">
<svg viewBox="0 0 16 16" fill="currentColor">
<circle cx="8" cy="8" r="2"></circle>
<circle cx="2" cy="8" r="2"></circle>
<circle cx="14" cy="8" r="2"></circle>
</svg>
</a>
</li>
</ul>
</nav>
<div class="percent">
<span></span>
<div>
<svg preserveAspectRatio="none" viewBox="0 0 600 12">
<path d="M0,1 L200,1 C300,1 300,11 400,11 L600,11" stroke="currentColor" fill="none"></path>
</svg>
</div>
</div>
</div>
<a href="" class="restart">
<svg viewBox="0 0 16 16" fill="currentColor">
<path d="M4.5,4.5c1.9-1.9,5.1-1.9,7,0c0.7,0.7,1.2,1.7,1.4,2.7l2-0.3C14.7,5.4,14,4.1,13,3.1c-2.7-2.7-7.1-2.7-9.9,0 L0.9,0.9L0.2,7.3l6.4-0.7L4.5,4.5z"></path>
<path d="M15.8,8.7L9.4,9.4l2.1,2.1c-1.9,1.9-5.1,1.9-7,0c-0.7-0.7-1.2-1.7-1.4-2.7l-2,0.3 C1.3,10.6,2,11.9,3,12.9c1.4,1.4,3.1,2,4.9,2c1.8,0,3.6-0.7,4.9-2l2.2,2.2L15.8,8.7z"></path>
</svg>
Restart
</a>
<!-- dribbble -->
<a class="dribbble" href="https://dribbble.com/shots/6730780-Upload-play-pause-animation" target="_blank"><img src="https://cdn.dribbble.com/assets/dribbble-ball-mark-2bd45f09c2fb58dbbfb44766d5d1d07c5a12972d602ef8b32204d28fa3dda554.svg" alt=""></a>
CSS :
:root {
--primary: #5628EE;
--success: #0EAC70;
--grey-light: #99A3BA;
--grey: #6C7486;
--grey-dark: #3F4656;
--light: #CDD9ED;
--lighter: #E4ECFA;
--lightest: #EEF4FF;
--pale: #F5F9FF;
--shadow: rgba(18, 22, 33, .05);
}
.upload {
--percent: 0;
counter-increment: percent var(--percent);
background: #fff;
border-radius: 8px;
width: 320px;
box-shadow: 0 4px 16px -1px var(--shadow);
display: flex;
align-items: center;
position: relative;
overflow: hidden;
padding: 32px 20px;
font-family: Roboto, Arial;
-webkit-mask-image: -webkit-radial-gradient(white, black);
}
.upload .percent {
background: var(--pale);
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
-webkit-transform-origin: 0 50%;
transform-origin: 0 50%;
overflow: hidden;
transition: background .6s ease, -webkit-transform .16s ease;
transition: background .6s ease, transform .16s ease;
transition: background .6s ease, transform .16s ease, -webkit-transform .16s ease;
-webkit-transform: scaleX(calc(var(--percent) / 100));
transform: scaleX(calc(var(--percent) / 100));
}
.upload .percent span {
display: block;
position: absolute;
right: 0;
width: 100%;
bottom: 19px;
height: 2px;
opacity: 0;
-webkit-transform: translateY(0.5px);
transform: translateY(0.5px);
transition: -webkit-transform .8s ease;
transition: transform .8s ease;
transition: transform .8s ease, -webkit-transform .8s ease;
}
.upload .percent span:before, .upload .percent span:after {
--r: 0;
--s: .5;
content: '';
position: absolute;
top: 0;
height: 2px;
border-radius: 1px;
background: var(--primary);
transition: background .8s ease, height .3s ease, -webkit-transform .8s ease;
transition: background .8s ease, transform .8s ease, height .3s ease;
transition: background .8s ease, transform .8s ease, height .3s ease, -webkit-transform .8s ease;
-webkit-transform: rotate(var(--r)) scaleY(var(--s));
transform: rotate(var(--r)) scaleY(var(--s));
}
.upload .percent span:before {
right: 0;
width: 64%;
-webkit-transform-origin: 0 50%;
transform-origin: 0 50%;
}
.upload .percent span:after {
left: 0;
width: 38%;
-webkit-transform-origin: 100% 50%;
transform-origin: 100% 50%;
}
.upload .percent div {
--x: 0;
-webkit-transform: translateX(var(--x));
transform: translateX(var(--x));
transition: -webkit-transform 1s ease;
transition: transform 1s ease;
transition: transform 1s ease, -webkit-transform 1s ease;
position: absolute;
left: 0;
bottom: 8px;
width: 300%;
}
.upload .percent svg {
display: block;
height: 12px;
width: 100%;
stroke-width: 1.2px;
color: var(--primary);
transition: color .5s ease;
}
.upload.paused:not(.finished) .percent div {
--x: -66.66%;
}
.upload.paused:not(.finished) .percent div svg {
color: var(--light);
-webkit-animation: down .8s linear forwards;
animation: down .8s linear forwards;
}
.upload.paused:not(.finished) .text > div div small:first-child {
opacity: 0;
}
.upload.paused:not(.finished) .text > div div small:last-child {
opacity: 1;
transition-delay: .4s;
}
.upload.finished .percent {
background: #fff;
}
.upload.finished .percent span {
opacity: 1;
-webkit-transform: translate(-20px, -19px);
transform: translate(-20px, -19px);
}
.upload.finished .percent span:before, .upload.finished .percent span:after {
--s: 1;
background: var(--grey-light);
transition: background .6s ease, -webkit-transform .6s ease .45s;
transition: background .6s ease, transform .6s ease .45s;
transition: background .6s ease, transform .6s ease .45s, -webkit-transform .6s ease .45s;
-webkit-animation: check .4s linear forwards .6s;
animation: check .4s linear forwards .6s;
}
.upload.finished .percent span:before {
--r: -50deg;
}
.upload.finished .percent span:after {
--r: 38deg;
}
.upload.finished .percent svg {
opacity: 0;
}
.upload.finished .text {
--y: 0;
}
.upload.finished .text > div {
opacity: 0;
}
.upload.finished nav {
opacity: 0;
pointer-events: none;
}
.upload .text {
--y: -18px;
position: relative;
z-index: 1;
-webkit-transform: translateY(var(--y));
transform: translateY(var(--y));
transition: -webkit-transform .6s ease;
transition: transform .6s ease;
transition: transform .6s ease, -webkit-transform .6s ease;
}
.upload .text strong {
font-weight: 400;
font-size: 14px;
display: block;
color: var(--grey-dark);
}
.upload .text > div {
position: absolute;
left: 0;
top: 100%;
-webkit-transform: translateY(6px);
transform: translateY(6px);
line-height: 20px;
display: flex;
align-items: center;
transition: opacity .4s ease;
}
.upload .text > div small {
white-space: nowrap;
vertical-align: top;
display: block;
font-size: 12px;
color: var(--grey-light);
}
.upload .text > div > small {
width: 30px;
text-align: center;
}
.upload .text > div > small:before {
content: counter(percent);
}
.upload .text > div div {
vertical-align: top;
display: inline-block;
position: relative;
margin-left: 4px;
}
.upload .text > div div:before {
content: '';
width: 2px;
height: 2px;
display: block;
border-radius: 50%;
background: var(--grey-light);
display: inline-block;
vertical-align: top;
margin-top: 9px;
}
.upload .text > div div small {
position: absolute;
top: 0;
left: 8px;
transition: opacity .3s ease;
}
.upload .text > div div small:first-child {
transition-delay: .4s;
}
.upload .text > div div small:last-child {
opacity: 0;
}
.upload nav {
z-index: 1;
position: relative;
display: flex;
align-items: center;
margin-left: auto;
transition: opacity .4s ease;
}
.upload nav ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
}
.upload nav ul:not(:last-child) {
margin-right: 16px;
}
.upload nav ul:first-child {
--y: 8px;
opacity: 0;
-webkit-transform: translateY(var(--y));
transform: translateY(var(--y));
transition: opacity .3s ease, -webkit-transform .4s ease;
transition: opacity .3s ease, transform .4s ease;
transition: opacity .3s ease, transform .4s ease, -webkit-transform .4s ease;
}
.upload nav ul li:not(:last-child) {
margin-right: 12px;
}
.upload nav ul li a {
--r: 0deg;
--s: 1.01;
display: block;
-webkit-transform: rotate(var(--r)) scale(var(--s)) translateZ(0);
transform: rotate(var(--r)) scale(var(--s)) translateZ(0);
transition: background .4s ease, -webkit-transform .6s ease;
transition: transform .6s ease, background .4s ease;
transition: transform .6s ease, background .4s ease, -webkit-transform .6s ease;
}
.upload nav ul li a svg {
display: block;
width: 14px;
height: 14px;
color: var(--grey-light);
}
.upload nav ul li a:active {
--s: .84;
transition: background .4s ease, -webkit-transform .3s ease;
transition: transform .3s ease, background .4s ease;
transition: transform .3s ease, background .4s ease, -webkit-transform .3s ease;
}
.upload nav ul li a.dots {
--r: 90deg;
}
.upload nav ul li a.btn {
width: 24px;
height: 24px;
border-radius: 50%;
position: relative;
background: var(--lightest);
}
.upload nav ul li a.btn svg {
position: absolute;
left: 6px;
top: 6px;
width: 12px;
height: 12px;
}
.upload nav ul li a.btn:hover {
background: var(--lighter);
}
.upload nav ul li a.btn.play {
--r: 90deg;
}
.upload nav ul li a.btn.play svg:last-child {
-webkit-transform: scale(-1) translateZ(0);
transform: scale(-1) translateZ(0);
}
.upload nav ul li a.btn.play.active {
--r: 0;
}
.upload nav ul li a.btn.cancel:before, .upload nav ul li a.btn.cancel:after {
--r: -45deg;
content: '';
display: block;
width: 2px;
border-radius: 1px;
height: 14px;
background: var(--grey-light);
position: absolute;
left: 50%;
top: 50%;
margin: -7px 0 0 -1px;
-webkit-transform: rotate(var(--r)) scale(0.9) translateZ(0);
transform: rotate(var(--r)) scale(0.9) translateZ(0);
}
.upload nav ul li a.btn.cancel:after {
--r: 45deg;
}
.upload.isMobile nav ul:first-child, .upload:hover nav ul:first-child {
--y: 0;
opacity: 1;
}
@-webkit-keyframes down {
40% {
-webkit-transform: translateY(2px);
transform: translateY(2px);
}
}
@keyframes down {
40% {
-webkit-transform: translateY(2px);
transform: translateY(2px);
}
}
@-webkit-keyframes check {
100% {
background: var(--success);
}
}
@keyframes check {
100% {
background: var(--success);
}
}
.upload.finished + .restart {
opacity: 1;
visibility: visible;
}
.restart {
position: absolute;
bottom: 20%;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
color: var(--grey-light);
font-size: 14px;
line-height: 16px;
text-decoration: none;
opacity: 0;
visibility: hidden;
transition: opacity .4s ease;
}
.restart svg {
width: 16px;
height: 16px;
margin-right: 4px;
display: inline-block;
vertical-align: top;
}
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: inherit;
}
*:before, *:after {
box-sizing: inherit;
}
body {
min-height: 100vh;
font-family: Roboto, Arial;
display: flex;
justify-content: center;
align-items: center;
background: var(--light);
padding: 20px;
}
body .dribbble {
position: fixed;
display: block;
right: 20px;
bottom: 20px;
}
body .dribbble img {
display: block;
height: 28px;
}
Javascript :
$(document).ready(function() {
var upload = $('.upload'),
animation,
pathPause = Snap.select('.play svg path').attr('d'),
pathRepeat = 'M4.5,4.5 C6.4,2.6 9.6,2.6 11.5,4.5 C12.2,5.2 12.7,6.2 12.9,7.2 L14.9,6.9 C14.7,5.4 14,4.1 13,3.1 C10.3,0.4 5.9,0.4 3.1,3.1 L0.9,0.9 L0.2,7.3 L6.6,6.6 L4.5,4.5 Z';
fakeUpload(7000);
upload.find('nav a').click(function(e) {
let btn = $(this);
if(btn.hasClass('play')) {
if(!btn.hasClass('active')) {
morhp(pathRepeat);
animation.pause();
} else {
morhp(pathPause);
setTimeout(() => {
animation.resume();
}, 600);
}
upload.toggleClass('paused');
btn.toggleClass('active');
}
return false;
});
function completeAnimation() {
upload.addClass('finished');
upload.find('.text > strong > span').text('Uploaded');
upload.find('.percent span').animate({
width: 20
}, 400);
}
function fakeUpload(duration) {
animation = $({
num: 0
}).animate({
num: 100
},
{
duration: duration,
easing: 'linear',
step() {
upload[0].style.setProperty('--percent', Math.floor(this.num));
upload.find('[data-seconds]').text(Math.floor((duration - duration * this.num / 100) / 1000) + 1);
},
complete() {
upload[0].style.setProperty('--percent', Math.floor(this.num));
completeAnimation();
}
});
}
function morhp(toPath) {
$('.play svg').each(function() {
let svg = $(this);
Snap(svg.children('path')[0]).animate({
d: toPath
}, 400);
});
}
$('.restart').on('click', e => {
fakeUpload(7000);
// reset classes
upload.removeClass('paused finished');
upload.find('nav a').removeClass('active');
upload.find('.text > strong > span').text('Uploading');
upload.find('.percent span').removeAttr('style');
morhp(pathPause);
return false;
});
var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
upload.toggleClass('isMobile', isMobile);
});
- 이전글6 분 안에 JavaScript 클로저 배우기 19.08.18
- 다음글Super Tiny Social Icons 19.08.17