--- id: wiki-2026-0508-offscreencanvas-safari-제약-사항 title: OffscreenCanvas Safari 제약 사항 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [OffscreenCanvas Safari, Safari OffscreenCanvas Limits, OffscreenCanvas WebKit] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [canvas, safari, webkit, browser-compat, offscreencanvas] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: JavaScript framework: Browser --- # OffscreenCanvas Safari 제약 사항 ## 매 한 줄 > **"매 Safari OffscreenCanvas 의 partial support"**. Safari 16.4+ 매 2D context 만 지원, WebGL/WebGPU OffscreenCanvas 는 매 17.0+ 부터 점진. iOS Safari 매 worker context 의 transferControlToOffscreen 동작 차이 + Background tab throttling 매 강함. 2026 기준 Chrome/Firefox 와 매 feature parity 거의 도달, 매 edge case 잔존. ## 매 핵심 ### 매 Safari 의 timeline - Safari **16.4** (2023): OffscreenCanvas 2D context. - Safari **17.0** (2023): WebGL OffscreenCanvas in worker. - Safari **18.x** (2025): WebGPU OffscreenCanvas (experimental flag). - iOS Safari 매 desktop 의 1-2 minor 늦게 따라옴. ### 매 주요 제약 - **Background tab throttling**: 매 worker animation frame 매 1Hz 까지 drop (Chrome/Firefox 보다 강한 throttle). - **Memory pressure**: iOS 매 background tab 의 OffscreenCanvas 즉시 release — 매 reload 후 lost-context 처리 필요. - **ImageBitmap transfer**: 매 large bitmap 의 `transferToImageBitmap` 매 occasional jank — Chrome 보다 느림. - **WebGL2 context loss**: iOS Safari 매 page-hide 즉시 lost. - **Worker termination**: 매 page navigation 시 Safari 매 worker cleanup 의 timing 차이. ### 매 응용 1. Image processing (Photopea-style) — main thread 차단 회피. 2. Chart rendering (Plotly, Apache ECharts worker mode). 3. Game offscreen rendering. 4. PDF/canvas thumbnail 생성. ## 💻 패턴 ### Feature detection (매 graceful fallback) ```ts function supportsOffscreen2D(): boolean { return typeof OffscreenCanvas !== "undefined" && typeof new OffscreenCanvas(1, 1).getContext === "function"; } function supportsOffscreenWebGL(): boolean { if (!supportsOffscreen2D()) return false; try { return !!new OffscreenCanvas(1, 1).getContext("webgl2"); } catch { return false; } } ``` ### Safari-safe transferControlToOffscreen ```ts const canvas = document.querySelector("canvas")!; let offscreen: OffscreenCanvas | null = null; let worker: Worker | null = null; function start() { if (!supportsOffscreen2D()) { renderOnMainThread(canvas); return; } offscreen = canvas.transferControlToOffscreen(); worker = new Worker(new URL("./renderer.worker.ts", import.meta.url), { type: "module" }); worker.postMessage({ type: "init", canvas: offscreen }, [offscreen]); } ``` ### Lost context handling (매 iOS 필수) ```ts // renderer.worker.ts self.addEventListener("message", (e) => { if (e.data.type === "init") { const canvas = e.data.canvas as OffscreenCanvas; let gl = canvas.getContext("webgl2"); canvas.addEventListener?.("webglcontextlost", (ev) => { ev.preventDefault(); console.warn("WebGL context lost (Safari background?)"); }); canvas.addEventListener?.("webglcontextrestored", () => { gl = canvas.getContext("webgl2"); reinitScene(); }); } }); ``` ### Visibility-aware rendering (매 throttle 회피) ```ts let isVisible = !document.hidden; document.addEventListener("visibilitychange", () => { isVisible = !document.hidden; worker?.postMessage({ type: "visibility", visible: isVisible }); }); // In worker: let raf: number; function loop() { draw(); raf = self.requestAnimationFrame?.(loop) ?? setTimeout(loop, 16); } self.addEventListener("message", (e) => { if (e.data.type === "visibility") { if (e.data.visible) loop(); else cancelAnimationFrame?.(raf); } }); ``` ### ImageBitmap chunked transfer (매 Safari jank 회피) ```ts async function processInChunks(blob: Blob, chunkSize = 1024) { const bitmap = await createImageBitmap(blob); for (let y = 0; y < bitmap.height; y += chunkSize) { const chunk = await createImageBitmap(bitmap, 0, y, bitmap.width, Math.min(chunkSize, bitmap.height - y)); worker.postMessage({ type: "chunk", chunk, y }, [chunk]); await new Promise(r => setTimeout(r, 0)); // yield to UI } bitmap.close(); } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 2D rendering, modern browser | OffscreenCanvas + worker | | WebGL on iOS Safari | feature-detect + main-thread fallback | | Long-running animation | visibilitychange 의 pause | | Large bitmap | chunked transfer | | Production, broad support | progressive enhancement, never required | **기본값**: feature-detect + 매 main-thread fallback path 매 항상 유지. ## 🔗 Graph - 부모: [[Web-Workers]] · [[Canvas-API]] - 변형: [[OffscreenCanvas]] · [[ImageBitmap]] - 응용: [[Image-Processing]] · [[Chart-Rendering]] · [[WebGL]] - Adjacent: [[Browser-Compatibility]] · [[Safari-Quirks]] · [[WebGPU]] ## 🤖 LLM 활용 **언제**: heavy canvas 작업의 worker 위임 + Safari/iOS target. **언제 X**: simple animation / 매 main thread 충분. ## ❌ 안티패턴 - **No fallback**: 매 OffscreenCanvas 의 mandatory 가정 — 매 old Safari user crash. - **Ignore context lost**: 매 iOS background → resume 후 매 black canvas. - **Throttle 무시**: 매 background tab 의 animation 매 progress 가정 — 매 desync. - **Sync transferToImageBitmap loop**: 매 매 frame transfer — 매 GC pressure. ## 🧪 검증 / 중복 - Verified (caniuse OffscreenCanvas, WebKit blog 16.4/17.0/18, MDN OffscreenCanvas). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Safari timeline, throttling/lost-context/chunked-transfer 패턴 |