Integración programática avanzada
Esta alternativa permite controlar explícitamente el motor, los modelos y el ciclo de vida mediante JavaScript. Para una primera aplicación con imagen y GLB sigue inicio con HTML. No tienes que reproducir este código si utilizas webar-scene.
Ejecutar este repositorio
Requisitos: Node.js 22.18 o posterior, NPM, navegador con WebGL y cámara. Desde la carpeta del proyecto:
npm ci
npm run dev
predev compila la librería, prepara OpenCV, los modelos de ejemplo, Workers, tipos y documentación. La primera ejecución tarda más que las siguientes. Abre http://127.0.0.1:5173/examples/documented-app.html, pulsa Iniciar cámara y concede el permiso. Muestra http://127.0.0.1:5173/marker.png en otra pantalla o imprímelo. No intentes escanear una miniatura demasiado pequeña.
En un computador se utiliza su webcam disponible; en móviles se solicita preferentemente la trasera. El tracking de imágenes no requiere immersive-ar.
Mi propio index.html
Ejecuta npm run build y copia todo el contenido de dist/ a la carpeta de tu sitio. Puedes cambiar su index.html por tu aplicación. No copies únicamente el JS: hacen falta los Workers, OpenCV y, para modelos Draco, sus decoders.
mi-sitio/
index.html
webar-facil.min.js
workers/ # copiar completo desde dist
vision/opencv.js
decoders/draco/ # conservar nombres de archivos
imagen.jpg
modelo.glb
En el HTML completo de abajo las rutas corresponden a examples/documented-app.html de este repositorio. Para usarlo como mi-sitio/index.html, sustituye:
| Ruta del ejemplo | Ruta de tu sitio |
|---|---|
../dist/webar-facil.min.js |
./webar-facil.min.js |
../vision/opencv.js |
./vision/opencv.js |
../decoders/draco/ |
./decoders/draco/ |
../marker.png |
./imagen.jpg |
../models/demo-draco.glb |
./modelo.glb |
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="data:,">
<title>Mi aplicación WebAR</title>
<style>
body{margin:0;background:#15202a;color:white;font:16px system-ui}
header{padding:12px}#view{width:100%;height:75vh;height:75dvh}button{padding:10px}
</style>
</head>
<body>
<header>
<button id="start">Iniciar cámara</button>
<button id="stop" disabled>Detener</button>
<p id="status" role="status">Apunta al marcador de ejemplo.</p>
</header>
<div id="view"></div>
<!-- En dist/examples el empaquetador cambia ../dist/ por ../. -->
<script src="../dist/webar-facil.min.js"></script>
<script>
// 1. El script standalone incluye Three.js y publica WebARFacil.
const {ThreeARScene,ThreeARModel}=WebARFacil;
const start=document.querySelector('#start');
const stop=document.querySelector('#stop');
const status=document.querySelector('#status');
const report=error=>{status.textContent=error instanceof Error?error.message:String(error);};
// 2. La escena crea cámara, fondo, luces, renderer y bucle de animación.
const app=new ThreeARScene({
container:document.querySelector('#view'),
engineOptions:{
opencvURL:new URL('../vision/opencv.js',location.href),
imageLossGraceMs:500,
keepContentOnLost:false,
},
});
// 3. El ID enlaza el contenido con su marcador; no es el nombre del GLB.
const model=new ThreeARModel({
dracoDecoderPath:new URL('../decoders/draco/',location.href),
});
app.addContent('poster',model);
app.engine.onError(report);
app.engine.on('targetFound',event=>{status.textContent=`Visible: ${event.targetId}`;});
app.engine.on('targetLost',event=>{status.textContent=`Fuera de vista: ${event.targetId}`;});
app.engine.on('trackingQuality',event=>{
// Diagnóstico heurístico: registrar o presentar ayuda sin reiniciar el modelo.
if(event.issues.length) console.debug(event.targetId,event.state,event.issues);
});
// 4. Carga bajo demanda. Las operaciones del motor se esperan en orden.
start.onclick=async()=>{
start.disabled=true;stop.disabled=true;
try{
await model.load('./assets/Kai-WebAR-Facil.glb');
await app.engine.startCamera();
// Basta JPG/PNG. El Worker compila los descriptores automáticamente.
const target=await app.engine.addImageTarget('poster','./assets/marcador.jpg',{
physicalWidthMeters:0.2,
});
console.info('Calidad del marcador',target.metadata.quality);
status.textContent='Busca el marcador con la cámara.';
stop.disabled=false;
}catch(error){
await app.engine.stop();report(error);start.disabled=false;
}
};
stop.onclick=async()=>{
stop.disabled=true;
try{await app.engine.stop();status.textContent='Cámara detenida.';}
catch(error){report(error);}
finally{start.disabled=false;}
// stop() elimina los targets: Iniciar vuelve a registrarlos.
};
// 5. El contenedor actualiza las animaciones aunque falte una pose nueva.
// dispose() libera recursos que pertenecen a la escena y sus modelos.
addEventListener('pagehide',()=>{void app.dispose();});
</script>
</body>
</html>
addContent('poster', model) y addImageTarget('poster', imagen) deben utilizar el mismo ID. La imagen se convierte internamente en descriptores: no tienes que cargar .dat ni JSON. La anchura física de 0.2 significa 20 cm; ajustarla da una escala coherente con el marcador real. Si desconoces su tamaño, puedes usarla como referencia de escala, sin atribuir exactitud métrica a la escena.
Qué modificar después
- Sustituye las rutas de imagen y GLB o usa archivos de un formulario: recetas programáticas.
- Usa un MP4 en lugar del modelo: vídeo.
- Añade un segundo marcador con otro ID: multimarcador.
- Activa retención visual con
app.engine.setKeepContentOnLost(true). - Añade SLAM siguiendo la guía espacial; la disponibilidad y la continuidad al cambiar de modo son diferentes del tracking de imágenes.
Para publicar o probar desde un teléfono utiliza HTTPS. localhost de un teléfono es el propio teléfono, no tu computador. Consulta despliegue.