Rozszerzenie pliku: .svg
Cel: pojedynczy plik SVG zawierający UI (HTML) i logikę (JavaScript) osadzone w <foreignObject>, który w przeglądarce ładuje i renderuje modele 3D (glTF/GLB, OBJ, STL, DAE/Collada itp.) przy użyciu WebGL/Three.js.
-
Plik SVG zawiera element
<foreignObject>z wewnętrznym dokumentem XHTML (np.<body xmlns="http://www.w3.org/1999/xhtml">) — wewnątrz: HTML +<canvas>+<script>. -
JavaScript ładuje biblioteki (np. Three.js, GLTFLoader) i renderuje scenę w
<canvas>. -
Problem, który zgłosiłeś: błąd przetwarzania XML — powodem są niezabezpieczone fragmenty JavaScript (np.
// komentarze,</script>wewnątrz kodu, znaki<&) które łamią reguły XML. Rozwiązaniem jest jedno z poniższych:- umieszczenie kodu JS w sekcji CDATA:
<![CDATA[ ... ]]> - albo trzymanie logiki w zewnętrznym skrypcie i wstawienie tylko
<script src="..."></script>(zalecane dla większych projektów) - albo uciekanie (escaping) znaków problematycznych, ALE to jest uciążliwe i łatwo popełnić błąd.
- umieszczenie kodu JS w sekcji CDATA:
<svg ...>
<defs> ... opcjonalne definicje ... </defs>
<rect .../> <!-- tło etc. -->
<foreignObject x=... y=... width=... height=...>
<body xmlns="http://www.w3.org/1999/xhtml">
<!-- UI: inputs, canvas -->
<canvas id="canvas"></canvas>
<!-- Sposób A: inline script - NAJWAŻNIEJSZE: zawrzyj JS w CDATA -->
<script type="application/javascript"><![CDATA[
// Twój JS (możesz użyć import(...) dynam., albo tworzyć <script type="module"> dynamicznie)
]]></script>
<!-- Sposób B (zalecany) : odwołanie do zewnętrznego pliku JS -->
<script type="module" src="viewer-module.js"></script>
</body>
</foreignObject>
</svg>
XML (w tym plik SVG jako XML) wymaga, aby dokument był well-formed. Wewnątrz XML:
// komentarzw skrypcie sam w sobie nie jest problemem — problem robią znaki</script>w stringach lub niezamknięte nawiasy, a przede wszystkim znaki<i&w treści, które XML interpretuje jako znaczniki/encje.- Jeśli używasz XHTML wewnątrz
<foreignObject>, to zawartość musi być poprawnym XML, więc zwykły JS (zwłaszcza z</script>fragmentami) może złamać parser. - Najpewniejsze rozwiązanie: CDATA lub zewnętrzny skrypt.
-
Dla prostych projektów / single-file: użyj
<![CDATA[ ... ]]>otaczającego cały kod JS (również importów).- Uwaga:
<![CDATA[zabrania interpretacji<i&— więc XML parser nie narzeka.
- Uwaga:
-
Dla większych projektów: trzymaj skrypty w zewnętrznym pliku
.jsi odwołuj się przez<script type="module" src="..."></script>(w obrębie foreignObject).- Jeśli chcesz mieć naprawdę pojedynczy plik (self-contained), możesz dołączyć skrypt zewnętrzny jako base64 w
src(data URL) lub wstawić go jako CDATA.
- Jeśli chcesz mieć naprawdę pojedynczy plik (self-contained), możesz dołączyć skrypt zewnętrzny jako base64 w
-
Unikaj umieszczania znaków
</script>w stringach JS — lepiej trzymać w CDATA. -
Testowanie: otwieraj plik przez prosty server (
python -m http.server) aby uniknąć niektórych ograniczeń przeglądarki przyfile://.
Uwaga: ten przykład pokazuje sposób zabezpieczenia przed błędami XML; to nie pełna wersja Three.js, ale pokazuje strukturę i poprawne umieszczenie skryptu.
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" viewBox="0 0 800 600">
<rect width="100%" height="100%" fill="#111"/>
<foreignObject x="10" y="10" width="780" height="580">
<body xmlns="http://www.w3.org/1999/xhtml" style="margin:0">
<div style="width:100%;height:100%;display:flex;flex-direction:column">
<canvas id="canvas" style="flex:1; width:100%; height:100%"></canvas>
<!-- Inline JS — BEZPIECZNE: wszystko w CDATA -->
<script type="application/javascript"><![CDATA[
// kod JS tutaj — możesz używać "//" i "<" bez łamania XML
(function(){
const canvas = document.getElementById('canvas');
// prosty rysunek testowy
const ctx = canvas.getContext('2d');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
ctx.fillStyle = '#223';
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#fff';
ctx.fillText('SVG-packed viewer (minimal test)', 20, 40);
// W praktyce: tutaj import i inicjacja Three.js (najlepiej jako dynamiczny moduł)
})();
]]></script>
</div>
</body>
</foreignObject>
</svg>Przeglądarki mogą mieć różne zachowanie przy type="module" wewnątrz <foreignObject> i CDATA. Możliwości:
- Zewnętrzny modul:
<script type="module" src="viewer-module.js"></script>— najprostsze i najmniej kłopotliwe. - Inline module w CDATA:
<script type="module"><![CDATA[ import ...; ... ]]></script>— zadziała w większości przeglądarek, ale testuj. - Tworzenie dynamicznego modułu: w prostym
<script>(classic) w CDATA stwórz element<script type="module">dynamicznie i ustaw jegotextContentna treść modułu (to obejście, gdy inline module bezpośrednio nie działa).
Przykład dynamicznego wstrzyknięcia modułu (do użycia w CDATA):
const moduleCode = `import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js';
// ... reszta modułu ...`;
const s = document.createElement('script');
s.type = 'module';
s.textContent = moduleCode;
document.body.appendChild(s);- glTF / GLB (najlepszy wybór dla WebGL; PBR, animacje, compact) — zalecane
.glbdla single-file. - OBJ (geometry + MTL, bez PBR) — prosty, szeroko obsługiwany.
- STL (ASCII/bin) — do geometrii, bez materiałów.
- COLLADA (.dae) — xml-owy; używany dawniej, ale działa.
- USDZ — działa głównie w Safari/AR Quick Look, nie jest bezpośrednio renderowany przez Three.js.
- X3D — możliwy, ale wymaga odrębnego silnika (x3dom, X3DOM).
Aby mieć w pełni self-contained plik z modelem GLB, możesz zembedować model jako data URL:
// przykładowy URL: 'data:model/gltf-binary;base64,AAAA...'
gltfLoader.load('data:model/gltf-binary;base64,....', (gltf) => { ... });Uwaga:
- Plik SVG stanie się bardzo duży (rozmiar base64 ≈ 4/3 rzeczywistego rozmiaru binarnego).
- Niektóre przeglądarki mogą mieć limit długości URL — testuj przy dużych plikach.
Alternatywa: wstawić binarny model jako <script type="application/octet-stream" id="model-data"> albo jako element <metadata> z CDATA i potem parsować. To działa, lecz jest mniej standardowe.
- Jeśli ładujesz model z innego hosta przez URL (
fetchlub loader), serwer musi zwracać nagłówekAccess-Control-Allow-Origin: *lub origin twojej strony. Brak tego headera → błędy w przeglądarce. - Przy lokalnym testowaniu używaj prostego serwera (
python -m http.serveralbonpx http-server) zamiastfile://, by uniknąć problemów z modułami i CORS.
-
Plik SVG może zawierać skrypt — uruchamiaj tylko zaufane pliki. Nie otwieraj SVG z nieznanego źródła (może zawierać złośliwy JS).
-
Jeśli dystrybuujesz taki viewer publicznie, rozważ:
- ograniczenie funkcji (np. brak dostępu do sieci lokalnej),
- Content Security Policy (CSP) na serwerze hostującym (jeśli używasz HTML wrappera, nie samego SVG),
- nie używaj inline scriptów w środowisku High-Security bez audytu.
- „Błąd przetwarzania XML: nieprawidłowo sformowany” → zwykle brak CDATA lub obecność
</script>w tekście. Rozwiązanie: użyj CDATA lub przenieś JS do pliku. - CORS error on load → upewnij się, że serwer modelu zwraca
Access-Control-Allow-Origin. - Module not found / failed to fetch → CDN może być zablokowany lub
file://nie pozwala na moduły; użyj serwera. - Brak widoku / czarny canvas → upewnij się, że Three.js inicjuje renderer z prawidłowym rozmiarem; wywołaj
renderer.setSize()icamera.updateProjectionMatrix().
Przykład jest skrótem (pobieranie Three.js z CDN). W praktyce lepiej mieć zewnętrzny viewer-module.js. Ten fragment pokazuje w jaki sposób zabezpieczyć JS przed XML parserem.
<foreignObject x="0" y="0" width="800" height="600">
<body xmlns="http://www.w3.org/1999/xhtml">
<canvas id="c" style="width:100%;height:100%"></canvas>
<script type="application/javascript"><![CDATA[
(function(){
// dynamiczne załadowanie modułu three.js jako modułu (tworzymy <script type="module"> dynamicznie)
const moduleCode = `
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/controls/OrbitControls.js';
export function start() {
const canvas = document.getElementById('c');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
const camera = new THREE.PerspectiveCamera(45, canvas.clientWidth / canvas.clientHeight, 0.1, 1000);
camera.position.set(0,1.5,3);
const controls = new OrbitControls(camera, renderer.domElement);
const light = new THREE.HemisphereLight(0xffffff,0x444444,1.0);
scene.add(light);
renderer.render(scene, camera);
// ... dalsza logika ładowania modeli
}
`;
const s = document.createElement('script');
s.type = 'module';
s.textContent = moduleCode + '\nimport.meta && (window.__viewer_module__ = (await (new Function(moduleCode + "\\nreturn {start};"))()).start());';
document.body.appendChild(s);
})();
]]></script>
</body>
</foreignObject>(Uwaga: powyższy fragment pokazuje zasadę; przy produkcji napisz moduł w pliku zewnętrznym.)
- DRACO decompression dla skompresowanych glTF (DRACOLoader).
- Kompresja tekstur (KTX2 / Basis).
- Obsługa animacji i skinned meshes.
- Eksport/zip: możliwość pobrania modelu lub eksportu sceny.
- Tryb AR: stream do USDZ lub WebXR support.
- Fallback: jeśli Three.js nie jest dostępne, pokaż przyjazny komunikat.
- Jeżeli chcesz prosty, self-contained viewer: użyj CDATA dla inline skryptów albo wbuduj model jako base64 data URL (uwaga: rozmiar).
- Jeśli zależy ci na stabilności i czytelności: trzymaj JS jako zewnętrzny moduł i odwołuj się przez
src. - Testuj w docelowych przeglądarkach i hostuj przez HTTP(S).
- Dla produkcji: preferuj glTF/GLB jako format wymiany i rozglądaj się za DRACO/texture compression. yć wykonana teraz?