GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

파일 업로드 진행률 로더 9종 — 드래그부터 체크까지 계산

파일 업로드 진행률 UI는 계산으로 그리는 로더입니다. 파일을 끌어다 놓으면 바이트와 시간을 계산해 링이 돌고 속도가 표시되고 끝나면 체크로 바뀝니다. 9가지 상태를 라이브러리 없이 계산으로 만드는 로더 — 코드는 비밀번호 zip으로 그대로 드립니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

데모

아홉 판 전부 is-demo 상태로 열려 시드 난수가 가상의 바이트를 흔들다가, 포인터를 올리거나 파일을 실제로 놓는 순간(pointerdown·focusin·change·paste·drop) 같은 계산 경로로 그대로 넘어갑니다. 24프레임 녹화에서 아홉 판 모두 11~21프레임이 움직였고, 값이 튀지 않도록 표시값은 목표치를 관성(lerp)으로 뒤따라갑니다.

24프레임 실측 한눈에

항목 초점 누적면적(%) 강도 평균 움직인프레임
01 드롭존 하이라이트 depth 카운터로 상쇄한 테두리 13.246 55.6 17
02 원형 퍼센트 링 둘레 계산 stroke-dashoffset 1.533 121.8 20
03 전송 속도 라벨 바이트/시간 이동평균 KB/s 3.254 163.6 20
04 다중 파일 스태거 인덱스 지연으로 줄 세운 등장 11.172 76.7 17
05 썸네일 팝인 FileReader onload 트리거 4.903 145.3 16
06 완료 모프 링에서 체크 path 로 전환 1.049 74.6 19
07 실패 흔들림과 재시도 loaded 보존한 재시도 2.978 161.2 21
08 청크 일시정지·재개 lastConfirmed 부터 재개 3.703 87.6 11
09 붙여넣기 캡처 clipboardData 파이프라인 18.947 93.4 15

낮은 누적면적이 정상인 이유

컴포넌트 편은 캔버스 전체가 아니라 링·바·배지 같은 좁은 조각만 바뀌므로 누적면적이 1~19% 대에 모이는 것이 정상입니다. 06 완료 모프가 1.049% 로 가장 작은 것은 원에서 체크로 바뀌는 좁은 SVG 영역만 움직이기 때문이고, 09 붙여넣기 캡처가 18.947% 로 가장 넓은 것은 인디터미네이트 링과 썸네일 스왑이 화면 절반 가까이를 바꾸기 때문입니다. 강도 평균은 03 전송 속도 라벨이 163.6 으로 가장 밝고 07 실패 흔들림 161.2·05 썸네일 팝인 145.3 이 뒤를 잇는데, 이 편의 정교함은 면적이 아니라 바이트·시간·조각 번호를 다시 계산하는 겹에 있습니다.

드롭존부터 붙여넣기까지, 업로드 여정을 따라간 아홉 단계

순서는 파일이 화면에 들어오는 순간부터 끝나는 순간까지를 한 단계씩 따라갑니다. 먼저 파일이 화면 위로 들어오는 순간의 테두리부터 다루고(01), 단일 파일의 퍼센트를 링으로(02), 전송 속도를 숫자로 보여준 뒤(03), 여러 파일이 한꺼번에 들어올 때의 줄 세우기로 넘어갑니다(04). 다섯째는 파일을 읽어 들이는 순간의 미리보기이고(05), 여섯째는 100%에 닿는 순간의 마무리이며(06), 일곱째는 실패라는 반대 경우를 다룹니다(07). 여덟째는 큰 파일을 조각내 나눠 보내는 경우이고(08), 마지막은 드래그 없이 붙여넣기만으로 같은 파이프라인에 들어가는 경우로 닫습니다(09). 파일이 들어오는 자리를 계산으로 지키는 점에서는 네모 버튼 안쪽까지 눌림을 계산하는 CSS 버튼 애니메이션 9종과 같은 계보이고, 목록 한 줄 한 줄이 상태를 쌓아 가는 점에서는 항목이 펼쳐지는 높이를 계산하는 체크리스트 패턴과도 닮았습니다.

항목별 코드

01드롭존 하이라이트 — 자식 요소에서도 안 깜빡이는 드래그오버

dragenter·dragleave 는 자식 노드를 넘나들 때마다 쌍으로 더 발생하는데, 이 판은 진입마다 +1·이탈마다 -1 하는 depth 카운터 하나로 그 중복을 상쇄해 파일을 끄는 동안 테두리가 한 번만 커지고 줄어듭니다. 24프레임 중 17프레임이 움직이고 누적면적은 13.246% 로 아홉 판 중 두 번째로 넓습니다.

드롭존 하이라이트 — 자식 요소에서도 안 깜빡이는 드래그오버
새 창에서 열기

01 · 드롭존 하이라이트 — 자식 요소에서도 안 깜빡이는 드래그오버 — 측정 누적면적 13.246% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

dragenterdragleavedataTransfer

depth 카운터가 자식 경계를 무시하는 방식

  var depth = 0;

  function paint() {
    zone.classList.toggle('is-over', depth > 0);
    badge.style.setProperty('--n', depth);
  }
  function enter(e) { e.preventDefault(); depth += 1; paint(); }
  function leave(e) { e.preventDefault(); depth = Math.max(0, depth - 1); paint(); }
  function over(e) { e.preventDefault(); }
  function drop(e) {
    e.preventDefault();
    var fileCount = (e.dataTransfer && e.dataTransfer.files) ? e.dataTransfer.files.length : 0;
    depth = 0; paint();
    zone.setAttribute('data-drop-count', String(fileCount));
    zone.classList.add('is-dropped');
    setTimeout(function () { zone.classList.remove('is-dropped'); }, 640);
  }

02원형 퍼센트 링 — 둘레를 계산해 도는 링

getBoundingClientRect 로 실측한 반지름에서 둘레(2πr)를 구해 stroke-dasharray 상수로 넣고, 목표치를 mulberry32 시드 난수로 흔들며 그 값만큼 stroke-dashoffset 을 되감으면 숫자 라벨과 링이 항상 같은 값을 그립니다. 강도 평균 121.8·움직인프레임 20으로 아홉 판 중 네 번째로 밝고, 움직인프레임 수로는 03·07 다음으로 자주 갱신됩니다.

원형 퍼센트 링 — 둘레를 계산해 도는 링
새 창에서 열기

02 · 원형 퍼센트 링 — 둘레를 계산해 도는 링 — 측정 누적면적 1.533% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

stroke-dashoffsetgetBoundingClientRectrequestAnimationFrame

실측 반지름에서 둘레 상수를 한 번만 구한다

  var r = prog.r.baseVal.value;
  var box = prog.getBoundingClientRect();
  var measuredR = box.width > 0 ? box.width / 2 : r;
  var circ = 2 * Math.PI * measuredR;
  prog.style.setProperty('--circ', String(circ));

  var target = 0, shown = 0, raf = null;
  function paint() {
    shown += (target - shown) * 0.18;
    if (Math.abs(target - shown) < 0.05) shown = target;
    prog.style.setProperty('--p', shown.toFixed(2));
    pct.style.setProperty('--n', Math.round(shown));
    if (shown < 100) raf = requestAnimationFrame(paint);
  }

03전송 속도 라벨 — 시간차로 계산하는 KB/s

progress 이벤트가 올 때마다 이전 시점과의 바이트 차를 performance.now 기준 시간 차로 나눠 KB/s 를 구하고, 최근 5개 표본의 이동평균을 목표치로 삼아 값이 튀지 않게 합니다. 강도 평균 163.6 으로 아홉 판 중 가장 밝고, 24프레임 중 20프레임이 움직입니다.

전송 속도 라벨 — 시간차로 계산하는 KB/s
새 창에서 열기

03 · 전송 속도 라벨 — 시간차로 계산하는 KB/s — 측정 누적면적 3.254% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

performance.nowProgressEventrequestAnimationFrame

바이트 차를 시간 차로 나눠 매번 다시 잰다

  bus.addEventListener('progress', function (e) {
    var now = performance.now();
    var dt = (now - lastT) / 1000;
    var db = e.loaded - lastLoaded;
    var kbps = dt > 0 ? (db / 1024) / dt : 0;
    samples.push(kbps);
    if (samples.length > 5) samples.shift();
    var sum = 0;
    for (var i = 0; i < samples.length; i++) sum += samples[i];
    var target = sum / samples.length;
    lastT = now; lastLoaded = e.loaded;
    fill.style.transform = 'scaleX(' + (e.loaded / e.total) + ')';
    if (!raf) tick(target);
  });

04다중 파일 스태거 — 목록에 순서대로 쌓이는 진행률

목록 행마다 등록 순서 i 에 STAGGER_MS(80ms)를 곱한 만큼 animation-delay 를 주어 순서대로 나타나게 하고, 채움은 각 행의 실제 data-target(100·72·45·18)까지 매 프레임 시드 난수로 흔들며 올립니다. 누적면적 11.172% 로 아홉 판 중 세 번째로 넓고 애니메이션 트랙 9개가 한 목록 안에서 각자 움직입니다.

다중 파일 스태거 — 목록에 순서대로 쌓이는 진행률
새 창에서 열기

04 · 다중 파일 스태거 — 목록에 순서대로 쌓이는 진행률 — 측정 누적면적 11.172% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

Array.prototype.mapanimation-delayrequestAnimationFrame

인덱스 지연으로 줄 세우고 목표치까지 채운다

  var rows = Array.prototype.map.call(list.querySelectorAll('.ms__row'), function (row, i) {
    row.style.animationDelay = (i * STAGGER_MS) + 'ms';
    return { row: row, fill: row.querySelector('.ms__fill'), value: 0 };
  });

  function step() {
    if (root.classList.contains('is-demo')) return;
    var done = true;
    rows.forEach(function (r) {
      var target = Number(r.fill.getAttribute('data-target'));
      if (r.value < target) {
        r.value = Math.min(target, r.value + 2 + rand() * 6);
        r.fill.style.transform = 'scaleX(' + (r.value / 100) + ')';
        done = false;
      }
    });
    if (!done) requestAnimationFrame(step);
  }

05썸네일 팝인 — 읽어 들인 순간 튀어 오르는 미리보기

FileReader.onload(dataURL 을 다 읽는 시점)를 신호로 썸네일이 오버슈트로 커지고, 그 아래 진행 바는 그제서야 900ms 동안 채워지기 시작합니다. 첨부 파일 업로드 ui 에서 이미지가 선택되는 순간을 눈에 보이게 만드는 방식이고, 강도 평균 145.3 으로 아홉 판 중 세 번째로 밝습니다.

썸네일 팝인 — 읽어 들인 순간 튀어 오르는 미리보기
새 창에서 열기

05 · 썸네일 팝인 — 읽어 들인 순간 튀어 오르는 미리보기 — 측정 누적면적 4.903% · 움직인 프레임 16/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

FileReadercreateObjectURLtransform

onload 를 신호로 삼는 순서

  function fillTo(pct, dur) {
    var t0 = performance.now();
    function step(now) {
      var k = Math.min(1, (now - t0) / dur);
      fill.style.transform = 'scaleX(' + (pct * k / 100) + ')';
      if (k < 1) requestAnimationFrame(step);
    }
    requestAnimationFrame(step);
  }

  input.addEventListener('change', function (e) {
    var file = e.target.files && e.target.files[0];
    if (!file) return;
    root.classList.remove('is-demo');
    var reader = new FileReader();
    reader.onload = function () {
      thumb.style.backgroundImage = 'url(' + reader.result + ')';
      thumb.classList.add('is-pop');
      fillTo(100, 900);
    };
    reader.readAsDataURL(file);
    // 더 빠른 미리보기가 필요하면 createObjectURL 로도 즉시 참조를 만들 수 있다(여기선 즉시 해제).
    var objUrl = URL.createObjectURL(file);
    URL.revokeObjectURL(objUrl);
  });

06완료 모프 — 링이 체크 모양으로 바뀌는 순간

링과 체크 path 각각 getBoundingClientRect·getTotalLength 로 둘레와 경로 길이를 실측해 커스텀 프로퍼티로 저장해 두고, 진행률이 100 에 닿는 프레임에 is-done 클래스를 얹어 체크의 stroke-dashoffset 을 그 길이에서 0 으로 되감습니다. 누적면적은 1.049% 로 아홉 판 중 가장 작지만 24프레임 중 19프레임이 움직여 좁은 영역 안에서 계속 갱신됩니다.

완료 모프 — 링이 체크 모양으로 바뀌는 순간
새 창에서 열기

06 · 완료 모프 — 링이 체크 모양으로 바뀌는 순간 — 측정 누적면적 1.049% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

stroke-dasharraypathtransition

둘레와 경로 길이를 미리 구해 두고 100%에서 스왑한다

  var ringBox = ring.getBoundingClientRect();
  var ringR = ringBox.width > 0 ? ringBox.width / 2 : ring.r.baseVal.value;
  var circ = 2 * Math.PI * ringR;
  ring.style.setProperty('--circ', String(circ));
  var checkLen = check.getTotalLength();
  check.style.setProperty('--checklen', String(checkLen));

  var target = 0, shown = 0, raf = null, timer = null;
  function paint() {
    shown += (target - shown) * 0.2;
    if (Math.abs(target - shown) < 0.1) shown = target;
    ring.style.setProperty('--p', shown.toFixed(2));
    if (shown >= 100) {
      root.classList.add('is-done');
      raf = null;
      return;
    }
    raf = requestAnimationFrame(paint);
  }

07실패 흔들림과 재시도 — 끊긴 지점을 기억하는 버튼

FAIL_AT(58%)에 처음 닿는 순간에만 ProgressEvent('error') 를 던져 흔들림을 걸고, 재시도 버튼은 AbortController 로 이전 타이머만 끊은 뒤 그 시점의 loaded 바이트부터 다시 시작합니다. 24프레임 중 21프레임이 움직여 아홉 판 중 가장 자주 갱신되고 강도 평균도 161.2 로 두 번째로 밝습니다.

실패 흔들림과 재시도 — 끊긴 지점을 기억하는 버튼
새 창에서 열기

07 · 실패 흔들림과 재시도 — 끊긴 지점을 기억하는 버튼 — 측정 누적면적 2.978% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

ProgressEventAbortControllerkeyframes

loaded 를 남긴 채 끊고 그 지점부터 이어간다

  function run(startAt) {
    loaded = startAt;
    controller = new AbortController();
    timer = setInterval(function () {
      if (controller.signal.aborted) return;
      loaded = Math.min(100, loaded + 4 + rand() * 8);
      if (!failedOnce && loaded >= FAIL_AT) {
        failedOnce = true;
        clearInterval(timer);
        bus.dispatchEvent(new ProgressEvent('error', { lengthComputable: true, loaded: loaded, total: 100 }));
        return;
      }
      bus.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: loaded, total: 100 }));
      if (loaded >= 100) clearInterval(timer);
    }, 220);
  }

  retry.addEventListener('click', function () {
    controller.abort();
    root.classList.remove('is-error');
    run(loaded);
  });

08청크 일시정지·재개 — 보낸 조각만큼만 이어 붙는 바

파일을 Blob.slice 로 CHUNK_BYTES(256KB) 조각 TOTAL_CHUNKS(6개)로 나눠 순서대로 보내다가, 일시정지는 진행 중인 조각만 끊고 lastConfirmed(마지막으로 확인된 조각 번호)를 저장해 재개가 그 번호부터 다시 계산합니다. 모바일 파일 업로드 ui 처럼 네트워크가 끊겼다 이어지는 상황을 흉내 낸 판이고, 24프레임 중 11프레임만 움직여 아홉 판 중 가장 적게 갱신됩니다.

청크 일시정지·재개 — 보낸 조각만큼만 이어 붙는 바
새 창에서 열기

08 · 청크 일시정지·재개 — 보낸 조각만큼만 이어 붙는 바 — 측정 누적면적 3.703% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

Blob.slicefetchAbortController

lastConfirmed 부터 다시 계산하는 재개

  async function sendFrom(startIdx) {
    running = true;
    controller = new AbortController();
    for (var i = startIdx; i < TOTAL_CHUNKS; i++) {
      if (paused) { running = false; return; }
      var chunk = blob.slice(i * CHUNK_BYTES, (i + 1) * CHUNK_BYTES);
      try {
        await fetch(location.href, { method: 'HEAD', signal: controller.signal });
      } catch (err) {
        // 이 데모엔 실제 업로드 서버가 없다 — 실패해도 다음 조각으로 진행한다.
      }
      if (paused) { running = false; return; }
      lastConfirmed = i + 1;
      paint(lastConfirmed);
      await delay(160 + rand() * 140);
    }
    running = false;
  }

09붙여넣기 캡처 — Ctrl+V 로 시작하는 업로드

paste 이벤트의 clipboardData(DataTransfer 인스턴스)가 있는지, items 안에 kind === 'file' 이고 type 이 image/ 로 시작하는 항목이 있는지 확인해 getAsFile() 로 파일을 꺼냅니다. 드래그 없이도 같은 파이프라인(인디터미네이트 링 620ms → 썸네일)에 들어가고 누적면적 18.947% 로 아홉 판 중 가장 넓게 화면을 바꿉니다.

붙여넣기 캡처 — Ctrl+V 로 시작하는 업로드
새 창에서 열기

09 · 붙여넣기 캡처 — Ctrl+V 로 시작하는 업로드 — 측정 누적면적 18.947% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

pasteclipboardDataDataTransfer

clipboardData.items 에서 이미지 파일만 골라낸다

  function process(file) {
    root.classList.remove('is-idle');
    root.classList.add('is-spin');
    if (objectUrl) URL.revokeObjectURL(objectUrl);
    objectUrl = URL.createObjectURL(file);
    setTimeout(function () {
      img.src = objectUrl;
      root.classList.remove('is-spin');
      root.classList.add('is-done');
    }, 620);
  }

  root.addEventListener('paste', function (e) {
    if (!e.clipboardData) return;
    var data = e.clipboardData;
    if (!(data instanceof DataTransfer)) return;
    var items = data.items;
    for (var i = 0; i < items.length; i++) {
      if (items[i].kind === 'file' && items[i].type.indexOf('image/') === 0) {
        var file = items[i].getAsFile();
        if (file) { process(file); break; }
      }
    }
  });

왜 진행률 숫자와 링이 항상 같은 값인가 — 둘레와 시간차 계산

숫자 라벨과 링이 어긋나지 않는 이유는 둘 다 같은 계산에서 나오기 때문입니다. 02·06 은 화면에 그려진 뒤의 실제 반지름을 getBoundingClientRect 로 한 번 재서 둘레(2πr) 상수를 구해 두고, 그 상수 위에서 stroke-dashoffset 을 진행률만큼 되감습니다 — 숫자가 바뀌면 같은 진행률 값으로 링도 같이 바뀌니 어긋날 수가 없습니다. 03 도 같은 원리를 시간 축에 적용한 경우입니다 — KB/s 를 임의로 증가시키지 않고 바이트 차를 시간 차로 나눈 값을 매 틱 다시 계산하기 때문에 숫자가 실제 전송 속도(여기서는 시드로 흉내 낸 속도)와 항상 일치합니다.

상수는 한 번, 계산은 매 프레임

둘레·경로 길이 같은 기하값은 레이아웃이 끝난 뒤 한 번만 재서 CSS 커스텀 프로퍼티로 고정하고, 진행률·속도 같은 시간에 따라 변하는 값은 매 틱 다시 계산합니다. 이 둘을 뒤섞어 둘레를 매 프레임 다시 재면 리플로우 비용이 불필요하게 커지고, 반대로 진행률을 한 번만 계산해 놓으면 전송이 끊겼다 이어져도 화면이 그 사실을 반영하지 못합니다.

링 두께(02)·스태거 간격(04)·청크 크기(08) 를 바꾸면 접근성 처리도 함께 드러난다

값 하나를 바꾼 세 변형

이름 바꾼 값 느낌
링 두께 02 stroke-width 6 → 14 두꺼워질수록 링이 도넛에 가까워져 퍼센트보다 색 자체가 먼저 읽힌다
스태거 간격 04 delay 80ms → 220ms 간격이 벌어질수록 목록이 쌓이는 리듬이 또렷해지지만 다섯 번째 파일까지 체감 대기가 길어진다
청크 크기 08 256KB → 1MB 조각이 커질수록 재개 지점 수는 줄지만 실패했을 때 다시 보낼 손실분이 커진다

동작을 줄인 사용자에게 남기는 상태

prefers-reduced-motion: reduce 사용자에게는 루프 애니메이션을 끄고 대표 진행 상태(예: 62%, 완료 일부)를 고정된 값으로 보여줍니다. 빈 화면이나 0%로 되돌리지 않는 이유는 정지 요청이 애니메이션을 끄라는 뜻이지 정보를 지우라는 뜻은 아니기 때문입니다.

포인터와 스크린리더로 만지는 아홉 판

모든 판에는 사람이 읽을 수 있는 aria-label 이 붙어 있고, 07·08 처럼 버튼이 조작을 맡는 판은 그 버튼 자체가 네이티브로 포커스를 받는 대신 나머지 일곱 판(04 는 role="list"/listitem)은 패널에 role·tabindex 를 직접 얹어 포인터 없이도 접근할 수 있게 했습니다. 화면 아래 한 줄에는 그 판이 쓰는 실제 웹 API 이름을 표시합니다. 화면에 보이는 글자는 한국어지만 변수·함수 이름은 전부 영어 camelCase 라 zip 을 열어 코드 도구에 붙여 넣어도 그대로 읽힙니다.

depth 카운터 없이 dragenter·dragleave 를 다루면 깨지는 지점

dragenter·dragleave 를 카운터 없이 토글하면

01 처럼 자식 요소가 있는 드롭존에서 dragenter·dragleave 를 단순 true/false 토글로 처리하면, 포인터가 자식 노드 경계를 넘을 때마다 진입·이탈 쌍이 한 번씩 더 발생해 테두리가 깜빡입니다. depth 카운터로 진입마다 +1·이탈마다 -1 해 depth > 0 일 때만 하이라이트를 켜야 자식 경계와 무관하게 한 번만 켜지고 한 번만 꺼집니다.

재시도를 처음부터 다시 보내면

07 의 재시도 버튼이 loaded 를 0 으로 되돌리고 다시 시작하면 사용자는 이미 보낸 바이트를 다시 기다려야 합니다. 08 도 같은 함정이 있어 lastConfirmed 없이 재개하면 어디까지 보냈는지 몰라 조각을 다시 자르거나 건너뛰게 됩니다. 아홉 판 모두 파일 하나씩 index.html 로 들어 있고 이 원본을 그대로 묶은 암호 zip 의 비밀번호는 q73rkhs7 인데, 풀어서 여는 순간 본문의 코드 조각과 줄 단위로 나란히 맞춰 볼 수 있습니다.

이런 화면에서 쓴다 — 업로드 폼을 다시 짜는 팀

첨부 파일을 받는 폼을 다시 짜는 팀이라면 01 의 드롭존 하이라이트부터 붙입니다. 이력서·계약서 같은 단일 파일 칸에도 아이콘·안내 문구 같은 자식 요소가 들어가는 경우가 많은데, depth 카운터 덕분에 그 경계를 넘나들어도 테두리가 한 번만 켜지고 한 번만 꺼집니다.

상품 이미지나 첨부 자료를 한꺼번에 여러 개 받는 관리자 화면에는 04 의 다중 파일 스태거가 맞습니다. 줄이 순서대로 쌓이는 리듬이 몇 개를 골랐는지 눈으로 바로 세게 해 주고, 다섯 번째 줄까지 기다리는 체감 시간은 스태거 간격 값 하나로 조절됩니다.

영상이나 백업 파일처럼 용량이 큰 전송에는 08 의 청크 일시정지·재개가 필요합니다. 재개가 lastConfirmed 부터 다시 시작하므로, 이동 중 와이파이가 끊기는 모바일 환경에서도 처음부터 다시 보내라고 하지 않습니다.

업로드 진행률 판을 실제 폼에 넣기 전에 자주 나오는 질문

파일 드래그 앤 드롭 업로드를 이 데모로 처음 만들어도 따라갈 수 있나요?

01 판의 depth 카운터 열 몇 줄이 핵심이라 어렵지 않습니다. zip 안에는 아홉 판을 같은 전제로 다시 짜라고 시키는 지시 문서가 들어 있어 코드 도구에 붙여 넣으면 같은 화면부터 시작하고, 그다음 상수(STAGGER_MS·CHUNK_BYTES 등)를 하나씩 바꿔 보는 순서가 가장 빠릅니다.

데모의 시드 난수를 실제 서버 응답으로 바꾸려면 어디를 고치나요?

is-demo 클래스가 있는 동안만 mulberry32 시드가 목표치를 흔들고, pointerdown·change·paste 같은 실제 이벤트가 오면 live() 함수가 그 클래스를 지우고 같은 paint·tick 계산 경로로 넘어갑니다. 03 의 진행률을 실어 나르던 이벤트 버스 자리에 실제 XMLHttpRequest 를 연결하면 나머지 계산은 그대로 재사용됩니다.

9종을 한 페이지에 다 넣어야 하나요?

아닙니다. 9개는 각자 다른 상황(단일 파일·다중 파일·실패·조각 전송·붙여넣기)을 위한 독립된 판이라 폼 하나에는 그 상황에 맞는 한두 개만 가져다 쓰면 됩니다. 04 다중 스태거와 08 청크 재개처럼 큰 파일을 여러 개 동시에 다루는 화면에서만 두 판을 같이 쓰는 조합이 자연스럽습니다.

더 읽을거리

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.