HTML5 네이티브 API로 플러그인 없이 만드는 리치 웹 앱

HTML5는 Canvas·SVG·Video/Audio·Geolocation·WebStorage·WebWorker를 브라우저 네이티브 API로 제공해 플러그인 없이 리치 인터넷 애플리케이션을 구현하는 표준이다. 실무 체크리스트와 코드 예시를 정리한다.

2026-08-12 · 최초 발행 2025-11-26

플러그인 없는 리치 웹이 표준이 되기까지

1999년 W3C가 HTML 4.01을 종료한 뒤, 2000년 발표된 XHTML 1.0은 2.0 개발에 집중했다. 하지만 2004년 XHTML은 실용성 한계로 업계에서 외면받았고, 브라우저 벤더가 주도한 WHATWG에서 HTML5 제정이 시작됐다. W3C가 이를 수용하면서 2009년 XHTML 2.0이 종료됐고, 이후 HTML5는 Living Standard로 계속 진화하고 있다.

HTML5는 마크업, DOM, 멀티미디어, 그래픽, 저장소, 스레딩 등 브라우저 플랫폼 전반을 포괄하는 통합 표준이다. 플러그인 종속 없이 네이티브 실행·렌더링·저장을 가능케 하는 API 집합을 제공한다. 하위 호환성 유지, 점진적 향상, 장치 독립성, 접근성 강화, 보안·프라이버시 내재화를 원칙으로 삼는다.

그래픽: Canvas와 SVG

Canvas는 즉시 모드 2D 래스터 그래픽을 제공한다. 픽셀 기반 드로잉 방식이라 차트·게임·이미지 처리처럼 고빈도로 갱신되는 장면에 적합하다. SVG는 XML 기반 2D 벡터 그래픽을 제공해 도형·경로·필터·애니메이션을 선언적으로 정의하고, 확대·축소해도 선명도를 유지한다.

미디어와 위치 정보

<video><audio> 태그는 네이티브 재생을 제공하며 컨트롤·자막·미디어 이벤트·소스 전환을 지원한다. 코덱 지원은 브라우저별로 차이가 있어 다중 소스와 MSE/EME 조합으로 호환성·DRM 대응이 필요하고, 이 부분은 최신 정보를 확인해야 한다.

Geolocation은 사용자 위치 접근을 권한 기반으로 제공하며 HTTPS 또는 localhost 환경이 필수다. 정확도·배터리·프라이버시 사이 트레이드오프가 존재하므로 권한 거부·타임아웃·오차 처리를 함께 설계해야 한다.

저장소와 백그라운드 연산

WebStorage는 localStorage(영구)와 sessionStorage(세션)를 제공한다. 키-값 문자열을 저장하는 동기 API라는 특성을 고려해서 써야 한다. 용량은 수MB 수준이고 트랜잭션이 없어서, 대용량이나 동시성이 필요한 경우에는 IndexedDB를 쓰는 게 낫다.

WebWorker는 메인 스레드와 분리된 백그라운드에서 연산을 수행한다. 메시지 기반으로 통신하며 DOM에는 직접 접근할 수 없다. input[multiple]로 다중 파일 업로드를 지원하고, File API와 FormData를 조합하면 스트리밍 업로드와 진행률 처리가 가능하다.

HTML4+플러그인과 HTML5 네이티브의 차이

측면 HTML4 + 플러그인 의존 HTML5 네이티브
멀티미디어 외부 런타임 설치 필요, 보안 리스크, 일관성 낮음 <video>/<audio>, MSE/Tracks, 접근성·성능·일관성 개선
오프라인 쿠키 남용, 용량·트랜잭션 한계 WebStorage/IndexedDB/Cache API로 용량·일관성·복원력 향상
모바일 지원 터치/센서 연계 제한, 배터리 비효율 터치/센서/미디어 하드웨어 가속, 적응형 미디어
시멘틱 웹 의미 태그 부족, SEO/접근성 취약 시멘틱 태그(header, section 등)와 ARIA로 구조·접근성 강화

플러그인을 제거하면 초기 로드가 10~30% 감소하고 메모리·CPU 오버헤드도 줄어든다. 브라우저 간 렌더·입력 처리가 표준화되면서 QA 비용을 20% 이상 절감할 수 있고, 플러그인 취약점이 사라지면서 권한·CSP·CORS 기반 제어로 공격면이 줄어든다.

처리 흐름: 입력부터 에러 분기까지

MediaCanvas/SVGGeolocation허용거부/타임아웃StorageWorker코덱 미지원CORS 실패입력: 사용자 액션/파일선택/위치 요청기능Media Pipeline:video/audio 재생렌더링 엔진: 2D 드로잉/벡터권한 요청위치 획득 - UI 업데이트에러 핸들링 - 대체 UI/기본값WebStorage/IndexedDB저장/조회WebWorker 연산 -postMessage출력: 프레임 표시/자막오류?대체 소스/다운로드 링크에러 메시지 + 재시도 옵션

실무 활용

데이터 시각화는 Canvas로 실시간 차트를, SVG로 인포그래픽·아이콘을 구현한다. 미디어 스트리밍은 다중 소스와 자막(track)으로 크로스 브라우저 재생을 지원하며 DRM이 필요하면 EME를 검토한다. 위치 기반 서비스는 Geolocation으로 근접 매장·배달 ETA·지도 마커를 제공한다. 오프라인 퍼스트 앱은 WebStorage/IndexedDB와 Service Worker로 캐시·동기화를 구성한다. 대규모 계산은 WebWorker로 이미지 처리·형태소 분석·데이터 압축을 비동기 처리하고, 파일 업로드는 input[multiple]과 FormData로 대용량·다중 파일 전송과 진행률 UI를 구현한다.

코드로 보는 HTML5 데모

전제조건은 현대 브라우저 최신 버전(Chrome/Edge/Firefox/Safari), HTTPS(지오로케이션·미디어 권장), CORS 허용된 미디어 소스이며, 정적 서버(예: Node.js 18+의 http-server)에서 제공한다.

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>HTML5 Demo</title>
    <style>
      canvas {
        border: 1px solid #ccc;
      }
    </style>
  </head>
  <body>
    <!-- Video/Audio -->
    <video id="vid" width="360" controls>
      <source src="movie.mp4" type="video/mp4" />
      <source src="movie.webm" type="video/webm" />
      미디어를 재생할 수 없습니다.
    </video>
    <audio id="aud" controls>
      <source src="audio.ogg" type="audio/ogg" />
    </audio>

    <!-- SVG -->
    <svg
      width="120"
      height="120"
      viewBox="0 0 200 200"
      role="img"
      aria-label="사각형"
    >
      <rect
        id="rect"
        x="0"
        y="0"
        width="100"
        height="100"
        fill="#4CAF50"
      ></rect>
    </svg>

    <!-- Canvas -->
    <canvas id="graph" width="400" height="160"></canvas>

    <!-- Geolocation -->
    <button id="locBtn">현재 위치</button>
    <pre id="locOut"></pre>

    <!-- WebStorage -->
    <input id="note" placeholder="메모 입력" />
    <button id="saveNote">저장</button>

    <!-- Multi-file Upload -->
    <input id="files-upload" type="file" multiple />
    <progress id="prog" max="100" value="0"></progress>

    <script>
      // Canvas: 즉시 모드 드로잉
      const ctx = document.getElementById("graph").getContext("2d");
      ctx.fillStyle = "#2196F3";
      ctx.fillRect(10, 10, 120, 40);
      ctx.beginPath();
      ctx.moveTo(10, 80);
      ctx.lineTo(200, 120);
      ctx.strokeStyle = "#f44336";
      ctx.lineWidth = 2;
      ctx.stroke();

      // Geolocation: 권한/오류 처리
      document.getElementById("locBtn").onclick = () => {
        if (!("geolocation" in navigator)) return alert("지오로케이션 미지원");
        navigator.geolocation.getCurrentPosition(
          (pos) =>
            (document.getElementById("locOut").textContent =
              `lat:${pos.coords.latitude}, lon:${pos.coords.longitude}, acc:${pos.coords.accuracy}m`),
          (err) =>
            (document.getElementById("locOut").textContent =
              `오류:${err.code} - ${err.message}`),
          { enableHighAccuracy: false, timeout: 5000, maximumAge: 10000 },
        );
      };

      // WebStorage: 동기 API 주의(작은 데이터용)
      document.getElementById("saveNote").onclick = () => {
        localStorage.setItem("note", document.getElementById("note").value);
        alert("저장 완료");
      };
      document.getElementById("note").value =
        localStorage.getItem("note") || "";

      // WebWorker
      if (window.Worker) {
        const worker = new Worker("worker.js");
        worker.onmessage = (e) => console.log("Worker 결과:", e.data);
        worker.postMessage({ cmd: "sum", payload: [1, 2, 3, 4, 5] });
      }

      // Multi File Upload
      const input = document.getElementById("files-upload");
      const prog = document.getElementById("prog");
      input.addEventListener("change", async () => {
        const fd = new FormData();
        [...input.files].forEach((f) => fd.append("files", f, f.name));
        try {
          const res = await fetch("/upload", { method: "POST", body: fd });
          if (!res.ok) throw new Error("업로드 실패");
          // 실제 구현 시 서버에서 개별 진행률을 SSE/WebSocket으로 수신 권장
          prog.value = 100;
        } catch (e) {
          alert(e.message);
        }
      });
    </script>
  </body>
</html>

worker.js

// 순수 계산 전용, DOM 접근 불가
self.onmessage = (e) => {
  const { cmd, payload } = e.data;
  if (cmd === "sum") {
    const s = payload.reduce((a, b) => a + b, 0);
    self.postMessage({ ok: true, sum: s });
  } else {
    self.postMessage({ ok: false, error: "unknown command" });
  }
};

참고 태그 샘플

<video src="movie.mp4" type="video/mp4"></video>
<audio src="audio.ogg" type="audio/ogg"></audio>
<svg width="100px" height="100px" viewBox="0 0 200 200">
  <rect id="rect" height="100" width="100" x="0" y="0"></rect>
</svg>
<canvas id="graph" width="550" height="220"></canvas>
<input id="files-upload" type="file" multiple />

실무 체크리스트와 트레이드오프

미디어는 다중 소스를 제공하고 코덱/DRM/MSE 호환성 매트릭스를 유지하며, CORS와 적응형 비트레이트를 구성한다. 트레이드오프는 품질 대 지연, DRM 대 범용성이다.

저장소는 빈번하거나 대용량인 경우 IndexedDB를 쓰고, WebStorage는 소량·설정 값에 한정한다. 트레이드오프는 복잡성 대 일관성/트랜잭션이다.

워커는 CPU 집약 작업만 위임하고, 메시지 데이터는 구조화 복제 비용을 고려해 Transferable을 사용한다. 트레이드오프는 설계 복잡성 대 UI 응답성이다.

위치 정보는 최소 권한·정확도를 유지하고 캐싱·타임아웃을 설정한다. 트레이드오프는 정확도 대 배터리다.

접근성과 시멘틱은 시멘틱 태그와 ARIA 속성을 활용하고, 키보드 내비게이션과 자막을 제공한다.

표준 API와 도구 체인을 활용하면 개발·배포 속도가 오르고 벤더 종속도 줄어든다. 신규 프로젝트는 HTML5 네이티브를 우선 도입하고, 레거시는 단계적으로 전환하는 편이 안전하다.

HTML5웹 표준CanvasWebStorageWebWorker