Scannable assets. Grab another device to scan and experience the magic. Long press (or right click) any QR code to download.
Tap any QR code to isolate it for scanning.
Showcase
The core capabilities.
Rich Cards
JSON-driven layout mapping.
Custom HTML Elements
Mapping hidden DOM nodes in 3D.
Smart Text & Data
Regex parsed native integrations.
3D Models
WebGL .glb loaders.
Video Players
Liquid morphing media.
Image Frames
Static spatial assets.
Audio Players
Interactive widgets.
Custom Generator
Instantly create and download your own custom styled QR anchors. Right-click/long-press to download. Hint: Try appending ?scale=1.5 to your payload to test dynamic AR overrides!
Live Configuration
Tracking & Performance
Engine & Advanced Setup
Changing these requires a full hardware/DOM reload.
Omni SpatialQR
Zero-Friction WebAR Framework. A blazing-fast Vanilla JS library for mapping rich HTML interfaces and 3D models perfectly onto physical QR codes.
The physical world is static. The digital world is limitless. Omni SpatialQR is the bridge between them.
Until now, Augmented Reality required massive friction: forcing users to download heavy apps, scanning weird proprietary markers, or waiting for bloated 3D engines to load. Omni SpatialQR changes the paradigm. It turns standard, everyday QR codes—on business cards, restaurant menus, retail packaging, and real estate signs—into instant, zero-friction spatial interfaces directly in the mobile browser.
Make your print media play video. Turn a coaster into a 3D showroom. Turn a poster into a glassmorphic checkout interface. It’s lightweight, instantly familiar to users, and feels like native magic.
README.md
A vanilla JavaScript library that calculates camera pose homography from the native BarcodeDetector API to project HTML DOM elements and Three.js WebGL models onto physical QR codes via mathematically precise CSS matrix3d transforms.
Installation
<script src="omni-spatial-qr.js"></script>
Minimal Quick Start
const visionAR = new OmniSpatialQR({
container: '#ar-wrapper',
type: 'dynamic'
});
visionAR.start();
// Essential for SPA (React/Vue) routing to prevent memory leaks
visionAR.stop();
Use Cases & Data Routing
Physical QR codes should be as simple as possible (low data density) so they scan instantly from far away. Omni SpatialQR provides two powerful architectures to keep your codes microscopic while rendering massive data payloads.
Use Case A: The Universal Billboard
Use this if your QR codes are standard URLs designed to be scanned by native iPhone/Android cameras to open your website. The library automatically extracts your query parameters (e.g. https://mysite.com?promo=vip).
If users scan codes inside your webapp, you don't need full URLs. Use custom URIs (e.g., omni:vip). This makes the physical QR code smaller, allowing the camera to track it significantly faster and from much further away.
const visionAR = new OmniSpatialQR({
type: 'dynamic',
uriPrefix: 'omni', // Intercepts "omni:vip"
assetResolver: async (parsedData, rawData) => {
// parsedData is the clean string: "vip"
// rawData is the untouched original: "omni:vip?scale=2"
const dbItem = await fetch(`/api/items/${parsedData}`);
return dbItem.json();
}
});
Use Case C: The 3D Model Viewer
Ideal for e-commerce or museums. Map physical items to their 3D digital twins automatically.
If you need an AR element (like a 3D arrow or a machine part overlay) to point in the exact physical direction encoded by the printed QR code ink, you must force the engine to track the absolute physical orientation.
You can temporarily override your core configuration for specific assets simply by appending parameters directly to the physical QR code URL. The library applies them instantly and safely strips them out so file inference remains untouched.
?omni=type (Forces the specific UI layout, e.g. ?omni=audio)
?scale=2.5 (Scales the overlay dynamically for this scan)
?pos=top (Overrides the anchor layout position)
?mm=50 (Triggers trueScale3D and sets the physical QR size in millimeters)
?yaw=90 (Rotates a 3D model on the Y-axis to correct export orientation)
Omni URI Scheme: You can also prefix strings with type:payload. Example: video:https://mysite.com/vid.mp4 or 3d:duck.glb. Furthermore, Omni URIs fully support URL parameter overrides natively. For example, scanning 3d:duck.glb?scale=2&yaw=90 will cleanly extract the parameters, rotate and scale the model, and will load duck.glb to the type preset.
AssetResolver Overrides & Error Handling
If your backend returns a URL without an extension (e.g. https://api.com/file_77), dynamic mode will fail to infer the media type. Your resolver can return a config object to force the engine type. You can also pass AR overrides directly from your database!
Furthermore, if your database lookup fails, simply return null. The engine will gracefully abort the visual update and fire the onError hook.
CSS Resolution Nuance: The library uses a strict CSS reset (all: initial) to protect your layout, but explicitly inherits your font-family. Due to the default overlayResolution: 100, 1 unit equals 1 physical pixel relative to the QR code size. Scale your CSS padding and fonts accordingly!
Advanced 3D Animation Control
The on3DLoad hook gives you total control over the Three.js scene. The library provides a constantly ticking AnimationMixer in the background, allowing you to trigger baked animations flawlessly without writing your own render loops.
let myMixer = null;
let myAnimations = null;
const visionAR = new OmniSpatialQR({
type: '3d',
autoPlay3DAnimations: false, // Prevents track 0 from auto-playing
on3DLoad: (gltf, scene, mixer) => {
// Save the library's ticking mixer globally
myMixer = mixer;
myAnimations = gltf.animations;
// Or use GSAP to manually animate the raw mesh!
gsap.to(gltf.scene.position, { y: 2, duration: 1 });
}
});
// Trigger a baked animation later via an HTML button click
document.getElementById('play-btn').onclick = () => {
if (myMixer && myAnimations[2]) {
myMixer.stopAllAction();
myMixer.clipAction(myAnimations[2]).play();
}
};
Full Configuration API Reference
container (String): DOM selector for the AR viewport. (Default: '#omni-qr')
scale (Number): Base scale multiplier. (Default: 1.0)
position (String): 'center', 'top', 'bottom', 'left', 'right'. (Default: 'center')
gap (Number): Unit offset if position is not center, scales proportionally with the overlay. (Default: 10)
overlayResolution (Number): Base render matrix. Higher numbers increase clarity but can reduce performance. Use 100 for optimal performance, 200 for big scales and 400 only for computers. (Default: 100)
spawnAnimation (Boolean): Triggers an Apple-style pop-up animation for all DOM overlays and 3D models when they spawn. (Default: true)
autoPlay (Boolean): Auto-plays injected media loops. (Default: true)
showControls (Boolean): Toggles custom media HUD overlays. (Default: true)
textTruncate (Boolean): Truncates strings vs wrapping to paragraph block. (Default: true)
cameraFacing (String): 'environment' or 'user'. (Default: 'environment')
yawOffset (Number): Adds rotational offset (in degrees) to correct 3D models exported with the wrong forward axis. (Default: 0)
show3DSpinner (Boolean): Shows a frosted silicone loading sphere while heavy 3D models download. (Default: true)
dracoDecoderPath (String): URL path to Google's Draco WASM decoders. Required to load highly compressed 3D models. (Default: 'https://www.gstatic.com/draco/v1/decoders/')
parseParam (Array/String): e.g. ['id', 'user']. Extracts parameters natively to use in assetResolver. (Default: null)
uriPrefix (Array/String): e.g. 'brand'. Enables custom brand:payload extraction. (Default: null)
assetResolver (Async Function): (parsedData, rawData) => {}. Receives the cleanly parsed payload and the untouched raw string. Returns mapped URLs, JSON Cards, or JSON Config Overrides. Supported override keys: type, data, html, scale, position, yaw, mm. (Default: null)
templates (Object): Maps IDs to hidden DOM elements for custom HTML rendering. (Default: {})
Lifecycle Events
onDetect(parsedData, rawData): Fires the instant a payload is decoded and extracted, before the UI builds. Perfect for firing a background analytics pixel or triggering an auto-download.
onTrackingRestored(): Fires when AR physically locks onto the square.
onTrackingLost(): Fires when AR loses physical tracking.
onClick(resolvedData, rawData): Fires when the user taps the AR overlay. Passes the final resolved data (the output of your assetResolver) and the original raw string. Great for navigating to external product pages.
on3DLoad(gltf, scene, mixer): Fires instantly when a 3D model finishes downloading and parsing, but before it appears on screen. Provides raw access to the Three.js objects and the animation mixer for custom modifications.
onError(err): Captures Camera, WebWorker, and Resolver failures gracefully.
Pro-Tips & Gotchas
HTTPS Requirement: Modern browsers strictly require https:// (or localhost) to access the getUserMedia camera API.
CORS for 3D: If you are loading .glb files from an external server (like AWS S3 or a custom CDN), ensure your server is configured with Access-Control-Allow-Origin: *, otherwise the WebGL canvas will block the download for security reasons.
Live Engine Methods
.setScale(number): Smoothly animates the scale dynamically. Snaps instantly if called while tracking is lost.
.set3DRotation(bool): Instantly starts or stops the 3D turntable animation.
.freezeTracking(bool): Freezes the AR overlay in place on screen while the camera keeps playing, drastically improving UX for viewing media.
.pause(): Synchronously stops the camera hardware to save battery for modal/background views.
await .resume(): Asynchronously requests camera hardware access to spin the AR engine back up.
Physical QR Code Generator API
Don't use a third-party generator. Our static helper creates beautiful, branded physical anchors instantly. With maximum error correction (Level H) when adding an embedded logos.
const qr = await OmniSpatialQR.generate({
data: 'omni:promo1',
type: 'soft', // standard, soft, dots
size: 300,
color: '#18181b', // Dot color
bgColor: '#ffffff',// Background color
logo: '/logo.png' // Drops your brand into the center
});
document.body.appendChild(qr);