Atlaslog
Atlaslog / Examples / three.js / Controls Pointerlock
three.js · Runnable Example

Controls Pointerlock

Runnable three.js example: Controls Pointerlock. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.

Open standalone → Chinese notes All three.js examples
/demos/three-controls-pointerlock.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/three-controls-pointerlock.html
<!--
  说明:点击画布锁定指针,用 WASD 在街区里第一人称行走,ESC 释放鼠标
  核心逻辑:PointerLockControls 只管理朝向,位移由 moveForward / moveRight 合成
  依赖:three.js 0.160.0、PointerLockControls · jsDelivr CDN(ESM + importmap)
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>第一人称漫游</title>
<style>
  html,body{height:100%;margin:0;background:#0a1016}
  body{display:flex;flex-direction:column;overflow:hidden;
       font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#e6e8ec}
  .bar{flex:0 0 auto;padding:8px 12px;background:#161b22;border-bottom:1px solid #252c36;
       display:flex;gap:14px;align-items:center;font-size:12px;color:#8b93a1;flex-wrap:wrap}
  .bar b{color:#58a6ff;font-weight:600;font-variant-numeric:tabular-nums}
  .bar input[type=range]{width:140px;accent-color:#4f9ef8}
  .bar .sp{flex:1 1 auto}
  #stage{flex:1 1 auto;min-height:0;position:relative;cursor:pointer}
  #stage canvas{display:block}
  #cross{position:absolute;left:50%;top:50%;width:14px;height:14px;margin:-7px 0 0 -7px;
       pointer-events:none;opacity:.7}
  #cross::before,#cross::after{content:'';position:absolute;background:#dfe9f5}
  #cross::before{left:6px;top:0;width:2px;height:14px}
  #cross::after{top:6px;left:0;height:2px;width:14px}
  #veil{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
       background:rgba(8,13,18,.78);color:#cfe0f2;font-size:15px;letter-spacing:.05em;
       transition:opacity .2s}
  #veil.hide{opacity:0;pointer-events:none}
</style>
</head>
<body>
<div class="bar">
  <span>坐标 <b id="posv">—</b></span>
  <span>速度 <b id="spdv">6.0</b> 单位/秒</span>
  <label>调速 <input type="range" id="spd" min="2" max="16" step="0.5" value="6"></label>
  <span class="sp"></span>
  <span>WASD 行走 · Shift 加速 · ESC 退出</span>
</div>
<div id="stage">
  <div id="cross"></div>
  <div id="veil">点击进入第一人称视角</div>
</div>

<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
  }
}
</script>
<script type="module">
import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';

const stage = document.getElementById('stage');
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(stage.clientWidth, stage.clientHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
stage.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a1016);
scene.fog = new THREE.Fog(0x0a1016, 30, 90);

const camera = new THREE.PerspectiveCamera(70, stage.clientWidth / stage.clientHeight, 0.1, 300);
camera.position.set(0, 1.7, 26);

const controls = new PointerLockControls(camera, renderer.domElement);
scene.add(controls.getObject());

scene.add(new THREE.HemisphereLight(0xc2d9f0, 0x161d24, 1.5));
const sun = new THREE.DirectionalLight(0xffe9c8, 2.0);
sun.position.set(-16, 26, 10);
scene.add(sun);

const ground = new THREE.Mesh(
  new THREE.PlaneGeometry(200, 200),
  new THREE.MeshStandardMaterial({ color: 0x1b242d, roughness: 0.95 })
);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);

/* 街道:沿 X 轴一条主街,两侧排满楼 */
const roadMat = new THREE.MeshStandardMaterial({ color: 0x2b3742, roughness: 0.9 });
const road = new THREE.Mesh(new THREE.BoxGeometry(200, 0.05, 8), roadMat);
road.position.set(0, 0.03, 0);
scene.add(road);

const lineMat = new THREE.MeshBasicMaterial({ color: 0x8b9aa8 });
for (let x = -90; x <= 90; x += 6) {
  const dash = new THREE.Mesh(new THREE.BoxGeometry(2.4, 0.02, 0.22), lineMat);
  dash.position.set(x, 0.07, 0);
  scene.add(dash);
}

function hash(n) {
  const x = Math.sin(n * 33.7 + 11.3) * 43758.5453;
  return x - Math.floor(x);
}

for (let i = 0; i < 64; i++) {
  const side = i % 2 === 0 ? 1 : -1;
  const x = -88 + (i / 2 | 0) * 5.6;
  const h = 4 + Math.pow(hash(i), 1.8) * 26;
  const w = 3.4 + hash(i + 4) * 1.6;
  const d = 3.4 + hash(i + 8) * 1.6;

  const b = new THREE.Mesh(
    new THREE.BoxGeometry(w, h, d),
    new THREE.MeshStandardMaterial({
      color: new THREE.Color().setHSL(0.58 - hash(i + 2) * 0.12, 0.2, 0.3 + hash(i + 6) * 0.22),
      roughness: 0.7,
      metalness: 0.08
    })
  );
  b.position.set(x, h / 2, side * (6 + d / 2));
  scene.add(b);
}

const keys = Object.create(null);
document.addEventListener('keydown', function (e) { keys[e.code] = true; });
document.addEventListener('keyup', function (e) { keys[e.code] = false; });

const veil = document.getElementById('veil');
veil.addEventListener('click', function () { controls.lock(); });
renderer.domElement.addEventListener('click', function () { controls.lock(); });

controls.addEventListener('lock', function () { veil.classList.add('hide'); });
controls.addEventListener('unlock', function () { veil.classList.remove('hide'); });

let speed = 6;
const spdEl = document.getElementById('spd');
const spdvEl = document.getElementById('spdv');
spdEl.addEventListener('input', function () {
  speed = Number(spdEl.value);
  spdvEl.textContent = speed.toFixed(1);
});

const posvEl = document.getElementById('posv');
const clock = new THREE.Clock();

renderer.setAnimationLoop(function () {
  const dt = Math.min(clock.getDelta(), 0.1);
  const boost = keys['ShiftLeft'] || keys['ShiftRight'] ? 2.2 : 1;
  const step = speed * boost * dt;

  if (controls.isLocked) {
    if (keys['KeyW']) controls.moveForward(step);
    if (keys['KeyS']) controls.moveForward(-step);
    if (keys['KeyA']) controls.moveRight(-step);
    if (keys['KeyD']) controls.moveRight(step);

    /* 保持行走高度,避免镜头被抬到楼顶或钻进地面 */
    controls.getObject().position.y = 1.7;
  }

  posvEl.textContent =
    controls.getObject().position.x.toFixed(1) + ', ' +
    controls.getObject().position.z.toFixed(1);

  renderer.render(scene, camera);
});

window.addEventListener('resize', function () {
  const w = stage.clientWidth;
  const h = stage.clientHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
});
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.

More three.js examples

three.js · EXAMPLECss2d Label three.js · EXAMPLECube three.js · EXAMPLEGeometry Colors