[Svelte Series 14] Build File Upload UI with Progress Tracking

한국어 버전

Submitting assignments or project screenshots involves waiting. Clear progress and retry states keep students calm.

Key terms

  1. Multipart upload: the HTTP format for sending files; set enctype="multipart/form-data" on the form.
  2. Progress event: the information emitted from onprogress that reports bytes sent so you can compute percentages.
  3. XMLHttpRequest: the older browser API that still offers granular upload control and progress events.
  4. AbortController / xhr.abort(): mechanisms that cancel network requests immediately when the user presses stop.

Core ideas

Multipart transfer splits files into chunks so the server can parse them; browsers only send files when enctype="multipart/form-data" is present. Progress equals the ratio of bytes uploaded to total bytes, available via XMLHttpRequest or fetch with streams. Offer cancellation with xhr.abort() or AbortController so user intent takes effect instantly.

Code examples

Form markup, progress handling, and server actions all matter.

<script>
  let pending = false;
  let progress = 0;

  function handleUpload({ action, form }) {
    const xhr = new XMLHttpRequest();
    xhr.upload.onprogress = (event) => {
      if (!event.lengthComputable) return;
      progress = Math.round((event.loaded / event.total) * 100);
    };
    xhr.onloadstart = () => { pending = true; };
    xhr.onloadend = () => { pending = false; progress = 0; };
    xhr.open('POST', action);
    xhr.send(new FormData(form));
    return () => xhr.abort();
  }
</script>

<form method="POST" enctype="multipart/form-data" use:enhance={handleUpload}>
  <input type="file" name="attachments" accept="image/*,application/pdf" multiple required />
  <textarea name="comment" placeholder="Notes" rows="3"></textarea>
  <button type="submit" disabled={pending}>{pending ? 'Uploading…' : 'Submit'}</button>
</form>

<div class="progress" aria-live="polite">
  <div class="bar" style={`width:${progress}%`}>
    <span>{progress}%</span>
  </div>
</div>
// src/routes/(app)/uploads/+page.server.ts

export const actions = {
  default: async ({ request, locals }) => {
    const data = await request.formData();
    const files = data.getAll('attachments');
    if (files.length > 5) {
      return fail(400, { error: 'You can upload up to 5 files.' });
    }
    await saveFiles(locals.user.id, files);
    return { success: true };
  }
};

The function returned from handleUpload runs when the component unmounts, cancelling in-flight uploads. The server double-checks file count (and should check size) to block malicious requests.

Why it matters

Large uploads often fail. Without progress or cancel options, users close the tab in confusion. Clear states and retry paths reduce support pings for assignment portals or contest submissions.

Practice tasks

  • Follow along: use use:enhance={handleUpload} to build a multi-file form and show the progress bar.
  • Extend it: add cancel/retry buttons that call xhr.abort() and reset the UI.
  • Debugging: if progress is stuck at 0%, verify event.lengthComputable and the enctype attribute.
  • Done when: up to five files upload successfully and retries work after failure.

Wrap-up

Once the upload flow is solid, users feel safe even while waiting. Next we'll review component accessibility and responsive navigation.

💬 댓글

이 글에 대한 의견을 남겨주세요