분류 design

재생 및 일시 정지 애니메이션 업로드-멋진 업 로더 구성 요소

컨텐츠 정보

  • 조회 274 (작성일 )

본문

재생 및 일시 정지 애니메이션 업로드




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);

});