Augmented Reality (AR) has traditionally required specialized native frameworks, but modern web tools are changing the game. With AR.js and Three.js, developers can now create marker-based AR experiences that run entirely in a browser — no app downloads required.
This article guides you through setting up a lightweight, mobile-friendly AR project using AR.js with raw Three.js, bypassing the need for A-Frame to give you more control over your 3D scenes.
Technologies Used
- AR.js – A performance-focused AR library for the web.
- Three.js – A flexible and powerful 3D rendering library built on WebGL.
Use Case
We’ll build a web-based AR scene that renders a red 3D cube on top of a Hiro marker when detected via a webcam.
Project Structure
ar-threejs-project/
│
├── index.html
├── app.js
└── assets/
└── data/
└── camera_para.dat
Download camera_para.dat from the AR.js GitHub and place it in the correct folder.
1. HTML Boilerplate (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AR.js with Three.js</title>
<style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ar.js@3.4.2/three.js/build/ar-threex.min.js"></script>
<script src="app.js"></script>
</body>
</html>
This includes:
Three.jsfor 3D rendering.AR.jswith the Three.js build.- A
scripttag to load our main app logic.
2. Main App Logic (app.js)
// Setup basic Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.Camera();
scene.add(camera);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color('black'), 0);
document.body.appendChild(renderer.domElement);
// Setup AR.js source (webcam)
const arSource = new THREEx.ArToolkitSource({ sourceType: 'webcam' });
arSource.init(() => onResize());
window.addEventListener('resize', onResize);
function onResize() {
arSource.onResize();
arSource.copySizeTo(renderer.domElement);
if (arContext.arController) {
arSource.copySizeTo(arContext.arController.canvas);
}
}
// Initialize AR.js context
const arContext = new THREEx.ArToolkitContext({
cameraParametersUrl: 'assets/data/camera_para.dat',
detectionMode: 'mono',
});
arContext.init(() => {
camera.projectionMatrix.copy(arContext.getProjectionMatrix());
});
// Create AR marker controls
const markerRoot = new THREE.Group();
scene.add(markerRoot);
const markerControls = new THREEx.ArMarkerControls(arContext, markerRoot, {
type: 'pattern',
patternUrl: 'https://cdn.jsdelivr.net/npm/ar.js@3.4.2/data/data/patt.hiro',
changeMatrixMode: 'modelViewMatrix',
});
// Add a 3D cube to the marker
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 0.5;
markerRoot.add(cube);
// Main render loop
function render() {
requestAnimationFrame(render);
if (arSource.ready) arContext.update(arSource.domElement);
renderer.render(scene, camera);
}
render();
Result
Once everything is set up, open your index.html in a modern browser (preferably over HTTPS or localhost), and point your webcam at a printed Hiro marker (you can find one here). You should see a 3D cube appear on top of the marker in real time.