Axes
Runnable three.js example: Axes. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.
single HTML file · CDN dependencies onlyinterface may contain Chinese labels
Source
<!--
说明:在 three 的 Y 向上坐标系里摆出东、北、天三个方向,对照 GIS 的 Z 向上约定
核心逻辑:ArrowHelper 画方向,Canvas 贴图给箭头做文字标签,Grid 提供水平参照
依赖:three.js 0.160.0、OrbitControls · 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:#0d1117}
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:10px;align-items:center;font-size:12px;color:#8b93a1;flex-wrap:wrap}
.bar b{color:#58a6ff;font-weight:600}
.bar button{font:inherit;font-size:12px;padding:4px 10px;border-radius:5px;cursor:pointer;
border:1px solid #303845;background:#1d2531;color:#c8d2e0}
.bar button:hover{background:#253040}
.bar .sp{flex:1 1 auto}
#stage{flex:1 1 auto;min-height:0;position:relative}
#stage canvas{display:block}
</style>
</head>
<body>
<div class="bar">
<span><b>X</b> 东 · <b>Z</b> 南 · <b>Y</b> 天(北向取 −Z)</span>
<button id="gridbtn">隐藏网格</button>
<button id="arrowbtn">隐藏坐标轴</button>
<span class="sp"></span>
<span>要素位置:E=4 m,N=3 m(即 z=−3)</span>
</div>
<div id="stage"></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 { OrbitControls } from 'three/addons/controls/OrbitControls.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(0x0d1117);
const camera = new THREE.PerspectiveCamera(
45, stage.clientWidth / stage.clientHeight, 0.1, 200
);
camera.position.set(9, 7, 12);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.4, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.maxPolarAngle = Math.PI * 0.495;
controls.update();
scene.add(new THREE.HemisphereLight(0xcfe4ff, 0x151d26, 1.15));
const key = new THREE.DirectionalLight(0xffffff, 2.2);
key.position.set(6, 10, 7);
scene.add(key);
/* GridHelper 位于 XZ 平面,等价于水平面 */
const grid = new THREE.GridHelper(20, 20, 0x3a5068, 0x1d2a36);
scene.add(grid);
/* 文字标签用 canvas 生成,不引入图片文件 */
function makeLabel(text, color) {
const c = document.createElement('canvas');
c.width = 128;
c.height = 64;
const g = c.getContext('2d');
g.clearRect(0, 0, 128, 64);
g.fillStyle = color;
g.font = '600 34px "PingFang SC", "Microsoft YaHei", sans-serif';
g.textAlign = 'center';
g.textBaseline = 'middle';
g.fillText(text, 64, 32);
const tex = new THREE.CanvasTexture(c);
tex.colorSpace = THREE.SRGBColorSpace;
const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false });
const sprite = new THREE.Sprite(mat);
sprite.scale.set(1.8, 0.9, 1);
return sprite;
}
const ORIGIN = new THREE.Vector3(0, 0, 0);
const LEN = 5;
const axes = new THREE.Group();
const east = new THREE.ArrowHelper(
new THREE.Vector3(1, 0, 0), ORIGIN, LEN, 0xff6b6b, 0.7, 0.36
);
const north = new THREE.ArrowHelper(
new THREE.Vector3(0, 0, -1), ORIGIN, LEN, 0x63e6be, 0.7, 0.36
);
const up = new THREE.ArrowHelper(
new THREE.Vector3(0, 1, 0), ORIGIN, LEN, 0x8ab4ff, 0.7, 0.36
);
axes.add(east, north, up);
const labelE = makeLabel('东 X', '#ff9b9b');
labelE.position.set(LEN + 1.1, 0.25, 0);
const labelN = makeLabel('北', '#8ff0d4');
labelN.position.set(0, 0.25, -LEN - 1.1);
const labelU = makeLabel('天 Y', '#a9c9ff');
labelU.position.set(0, LEN + 0.9, 0);
axes.add(labelE, labelN, labelU);
scene.add(axes);
/* 一个带高度的小体块,说明要素坐标 (E, N, U) 如何落到 three 的 (x, y, -z) */
const feature = new THREE.Mesh(
new THREE.BoxGeometry(1.2, 2.2, 1.2),
new THREE.MeshStandardMaterial({ color: 0xe0a458, roughness: 0.42, metalness: 0.18 })
);
feature.position.set(4, 1.1, -3);
scene.add(feature);
/* 从地面原点连到要素底部,标出平面偏移 */
const offsetLine = new THREE.Line(
new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(0, 0.01, 0),
new THREE.Vector3(4, 0.01, 0),
new THREE.Vector3(4, 0.01, -3)
]),
new THREE.LineBasicMaterial({ color: 0x6b7f95 })
);
scene.add(offsetLine);
renderer.setAnimationLoop(function () {
controls.update();
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);
});
const gridBtn = document.getElementById('gridbtn');
let gridOn = true;
gridBtn.addEventListener('click', function () {
gridOn = !gridOn;
grid.visible = gridOn;
gridBtn.textContent = gridOn ? '隐藏网格' : '显示网格';
});
const arrowBtn = document.getElementById('arrowbtn');
let axesOn = true;
arrowBtn.addEventListener('click', function () {
axesOn = !axesOn;
axes.visible = axesOn;
arrowBtn.textContent = axesOn ? '隐藏坐标轴' : '显示坐标轴';
});
</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.