<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D电梯模拟系统</title>
<link rel="stylesheet" href="elevator.css">
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script src="elevator.js"></script>
</head>
<body>
<div id="canvas-container">
<div id="loading">系统初始化中...</div>
<div id="ui-overlay">
<div id="control-panel">
<h2>电梯控制系统</h2>
<div class="floor-selector">
<div class="floor-btn" data-floor="8">8F</div>
<div class="floor-btn" data-floor="7">7F</div>
<div class="floor-btn" data-floor="6">6F</div>
<div class="floor-btn" data-floor="5">5F</div>
<div class="floor-btn" data-floor="4">4F</div>
<div class="floor-btn" data-floor="3">3F</div>
<div class="floor-btn" data-floor="2">2F</div>
<div class="floor-btn" data-floor="1">1F</div>
</div>
<div class="controls">
<button id="open-door">开门</button>
<button id="close-door">关门</button>
<button id="emergency-stop">紧急停止</button>
</div>
<div class="status">
<p>当前楼层: <span id="current-floor">1</span> F</p>
<p>门状态: <span id="door-status">已关闭</span></p>
<p>电梯状态: <span id="elevator-status">待机</span></p>
</div>
<div class="safety-info">
<h3>安全系统状态</h3>
<p>门传感器: <span id="sensor-status">正常</span></p>
<p>超速保护: <span id="overspeed-status">未激活</span></p>
<p>缓冲装置: <span id="buffer-status">待机</span></p>
</div>
</div>
</div>
</div>
</body>
</html>
index.html
elevator.js
package.json
elevator.css
现在支持上传本地图片了!
document.addEventListener('DOMContentLoaded', initElevatorSystem);
function initElevatorSystem() {
// 移除加载提示
const loading = document.getElementById('loading');
if (loading) loading.remove();
// 初始化Three.js场景
initThreeJS();
// 初始化UI事件
setupUIEvents();
}
function initThreeJS() {
// 场景设置
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
scene.fog = new THREE.Fog(0x1a1a2e, 20, 100);
// 摄像机设置
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);
// 渲染器设置
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById('canvas-container').appendChild(renderer.domElement);
// 添加轨道控制
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 添加光源
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 20, 15);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 电梯系统参数
const elevatorState = {
currentFloor: 1,
targetFloor: 1,
isMoving: false,
doorsOpen: false,
emergencyStop: false,
doorSensorTriggered: false,
speed: 0,
maxSpeed: 0.05,
acceleration: 0.001,
doorPosition: 0 // 0=closed, 1=open
};
// 创建电梯模型
createElevatorSystem(scene);
// 创建安全装置
createSafetyDevices(scene);
// 电梯动画函数
function animateElevator() {
requestAnimationFrame(animateElevator);
if (!elevatorState.emergencyStop) {
// 控制电梯运行
if (elevatorState.currentFloor !== elevatorState.targetFloor &&
!elevatorState.doorsOpen && !elevatorState.doorSensorTriggered) {
// 电梯运动逻辑
elevatorState.isMoving = true;
document.getElementById('elevator-status').textContent = '运行中';
// 加速
if (elevatorState.speed < elevatorState.maxSpeed) {
elevatorState.speed += elevatorState.acceleration;
}
// 向上移动
if (elevatorState.targetFloor > elevatorState.currentFloor) {
scene.getObjectByName('elevatorCar').position.y += elevatorState.speed;
// 检查是否到达目标楼层
if (scene.getObjectByName('elevatorCar').position.y >= elevatorState.targetFloor) {
elevatorState.currentFloor = elevatorState.targetFloor;
elevatorState.isMoving = false;
elevatorState.speed = 0;
openDoors();
}
}
// 向下移动
else if (elevatorState.targetFloor < elevatorState.currentFloor) {
scene.getObjectByName('elevatorCar').position.y -= elevatorState.speed;
// 检查是否到达目标楼层
if (scene.getObjectByName('elevatorCar').position.y <= elevatorState.targetFloor) {
elevatorState.currentFloor = elevatorState.targetFloor;
elevatorState.isMoving = false;
elevatorState.speed = 0;
openDoors();
}
}
// 更新UI
document.getElementById('current-floor').textContent =
Math.round(scene.getObjectByName('elevatorCar').position.y);
}
// 处理电梯门操作
else if (elevatorState.doorsOpen) {
animateDoors();
}
}
// 更新控制系统状态
controls.update();
// 渲染场景
renderer.render(scene, camera);
}
// 电梯门动画
function animateDoors() {
const doorSpeed = 0.02;
const doorsOpen = elevatorState.doorsOpen;
if (doorsOpen && elevatorState.doorPosition < 1) {
elevatorState.doorPosition += doorSpeed;
document.getElementById('door-status').textContent = '正在打开';
}
else if (!doorsOpen && elevatorState.doorPosition > 0) {
elevatorState.doorPosition -= doorSpeed;
document.getElementById('door-status').textContent = '正在关闭';
}
else {
document.getElementById('door-status').textContent =
elevatorState.doorsOpen ? '已打开' : '已关闭';
document.getElementById('elevator-status').textContent =
elevatorState.doorsOpen ? '开门停靠' : '待机';
// 随机触发门传感器
if (!elevatorState.doorsOpen && Math.random() < 0.01) {
triggerDoorSensor();
}
}
// 应用门位置到模型
scene.traverse((object) => {
if (object.name && object.name.includes('Door')) {
if (object.name.includes('Left')) {
object.position.x = -1.5 * elevatorState.doorPosition;
}
else if (object.name.includes('Right')) {
object.position.x = 1.5 * elevatorState.doorPosition;
}
}
});
}
// 开门函数
function openDoors() {
if (!elevatorState.isMoving && !elevatorState.emergencyStop) {
elevatorState.doorsOpen = true;
}
}
// 关门函数
function closeDoors() {
if (!elevatorState.isMoving && !elevatorState.doorSensorTriggered && !elevatorState.emergencyStop) {
elevatorState.doorsOpen = false;
}
}
// 触发门传感器
function triggerDoorSensor() {
if (elevatorState.doorsOpen) return;
elevatorState.doorSensorTriggered = true;
document.getElementById('sensor-status').textContent = '异物检测!';
document.getElementById('sensor-status').style.color = '#e74c3c';
// 重新开门
openDoors();
// 重置传感器状态
setTimeout(() => {
elevatorState.doorSensorTriggered = false;
document.getElementById('sensor-status').textContent = '正常';
document.getElementById('sensor-status').style.color = '';
}, 3000);
}
// 开始动画
animateElevator();
// 创建电梯系统模型
function createElevatorSystem(scene) {
// 创建电梯井
const shaftGroup = new THREE.Group();
shaftGroup.name = 'elevatorShaft';
// 电梯井框架
const shaftGeometry = new THREE.BoxGeometry(5, 40, 5);
const shaftMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc,
wireframe: true,
transparent: true,
opacity: 0.3
});
const shaft = new THREE.Mesh(shaftGeometry, shaftMaterial);
shaft.position.y = 20;
shaftGroup.add(shaft);
// 添加楼层标记
for (let i = 0; i < 8; i++) {
const floorGeometry = new THREE.BoxGeometry(5, 0.1, 5);
const floorMaterial = new THREE.MeshStandardMaterial({
color: i === 0 ? 0x3498db : 0x7f8c8d
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.y = i;
floor.position.z = -2.5;
shaftGroup.add(floor);
// 楼层数字
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 64;
canvas.height = 64;
ctx.fillStyle = '#ffffff';
ctx.font = '48px Arial';
ctx.fillText(`${i+1}F`, 5, 50);
const texture = new THREE.CanvasTexture(canvas);
const signMaterial = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
side: THREE.DoubleSide
});
const signGeometry = new THREE.PlaneGeometry(0.8, 0.8);
const sign = new THREE.Mesh(signGeometry, signMaterial);
sign.position.set(1.5, i, -2.48);
sign.rotation.x = -Math.PI / 2;
shaftGroup.add(sign);
}
scene.add(shaftGroup);
// 创建电梯轿厢
const elevatorGroup = new THREE.Group();
elevatorGroup.name = 'elevatorCar';
elevatorGroup.position.y = elevatorState.currentFloor;
// 轿厢主体
const carGeometry = new THREE.BoxGeometry(3, 2.5, 3);
const carMaterial = new THREE.MeshStandardMaterial({
color: 0x3498db,
metalness: 0.4,
roughness: 0.6
});
const car = new THREE.Mesh(carGeometry, carMaterial);
car.castShadow = true;
car.receiveShadow = true;
car.position.y = 1.25;
elevatorGroup.add(car);
// 轿厢门
createElevatorDoors(elevatorGroup);
// 天花板
const ceilingGeometry = new THREE.PlaneGeometry(3, 3);
const ceilingMaterial = new THREE.MeshBasicMaterial({
color: 0xecf0f1
});
const ceiling = new THREE.Mesh(ceilingGeometry, ceilingMaterial);
ceiling.position.y = 2.5;
ceiling.rotation.x = Math.PI / 2;
elevatorGroup.add(ceiling);
// 地板
const floorGeometry = new THREE.PlaneGeometry(2.8, 2.8);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0x34495e
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.receiveShadow = true;
floor.position.y = 0.01;
floor.rotation.x = -Math.PI / 2;
elevatorGroup.add(floor);
scene.add(elevatorGroup);
}
// 创建电梯门
function createElevatorDoors(parentGroup) {
const doorMaterial = new THREE.MeshStandardMaterial({
color: 0xe74c3c,
metalness: 0.5
});
// 左门
const leftDoorGeometry = new THREE.BoxGeometry(0.2, 2, 1.5);
const leftDoor = new THREE.Mesh(leftDoorGeometry, doorMaterial);
leftDoor.name = 'LeftDoor';
leftDoor.position.set(-0.9, 1.25, 0);
leftDoor.castShadow = true;
parentGroup.add(leftDoor);
// 右门
const rightDoorGeometry = new THREE.BoxGeometry(0.2, 2, 1.5);
const rightDoor = new THREE.Mesh(rightDoorGeometry, doorMaterial);
rightDoor.name = 'RightDoor';
rightDoor.position.set(0.9, 1.25, 0);
rightDoor.castShadow = true;
parentGroup.add(rightDoor);
// 门框
const frameMaterial = new THREE.MeshStandardMaterial({
color: 0x2c3e50
});
const frameGeometry = new THREE.BoxGeometry(0.1, 2.2, 0.1);
// 左侧门框
const leftFrame = new THREE.Mesh(frameGeometry, frameMaterial);
leftFrame.position.set(-1.5, 1.3, 0);
parentGroup.add(leftFrame);
// 右侧门框
const rightFrame = new THREE.Mesh(frameGeometry, frameMaterial);
rightFrame.position.set(1.5, 1.3, 0);
parentGroup.add(rightFrame);
// 顶部门框
const topFrameGeometry = new THREE.BoxGeometry(3.2, 0.1, 0.1);
const topFrame = new THREE.Mesh(topFrameGeometry, frameMaterial);
topFrame.position.set(0, 2.3, 0);
parentGroup.add(topFrame);
}
// 创建安全装置
function createSafetyDevices(scene) {
// 超速保护装置
const overspeedGeometry = new THREE.SphereGeometry(0.5, 16, 16);
const overspeedMaterial = new THREE.MeshBasicMaterial({
color: 0xe74c3c,
wireframe: true
});
const overspeedDevice = new THREE.Mesh(overspeedGeometry, overspeedMaterial);
overspeedDevice.position.set(0, 25, 0);
overspeedDevice.visible = false;
scene.add(overspeedDevice);
// 电梯井底部缓冲装置
const bufferGeometry = new THREE.CylinderGeometry(1, 1, 0.3, 32);
const bufferMaterial = new THREE.MeshStandardMaterial({
color: 0xf1c40f
});
const bufferDevice = new THREE.Mesh(bufferGeometry, bufferMaterial);
bufferDevice.position.set(0, 0.15, 0);
bufferDevice.name = 'bufferDevice';
scene.add(bufferDevice);
// 安全钳装置
const safetyClampGeometry = new THREE.BoxGeometry(0.3, 0.3, 1);
const safetyClampMaterial = new THREE.MeshStandardMaterial({
color: 0xe74c3c
});
const clampLeft = new THREE.Mesh(safetyClampGeometry, safetyClampMaterial);
clampLeft.position.set(-1.5, 3, 0);
clampLeft.name = 'safetyClampLeft';
scene.add(clampLeft);
const clampRight = new THREE.Mesh(safetyClampGeometry, safetyClampMaterial);
clampRight.position.set(1.5, 3, 0);
clampRight.name = 'safetyClampRight';
scene.add(clampRight);
}
// 设置UI事件
function setupUIEvents() {
// 楼层选择按钮
document.querySelectorAll('.floor-btn').forEach(btn => {
btn.addEventListener('click', () => {
if (!elevatorState.isMoving && !elevatorState.doorsOpen) {
const target = parseInt(btn.getAttribute('data-floor'));
if (target !== elevatorState.currentFloor) {
elevatorState.targetFloor = target;
document.getElementById('elevator-status').textContent = '待运行';
}
}
});
});
// 开门按钮
document.getElementById('open-door').addEventListener('click', () => {
if (!elevatorState.isMoving && !elevatorState.emergencyStop) {
openDoors();
}
});
// 关门按钮
document.getElementById('close-door').addEventListener('click', () => {
if (!elevatorState.isMoving && !elevatorState.emergencyStop) {
closeDoors();
}
});
// 紧急停止按钮
document.getElementById('emergency-stop').addEventListener('click', () => {
elevatorState.emergencyStop = !elevatorState.emergencyStop;
if (elevatorState.emergencyStop) {
document.getElementById('emergency-stop').style.background = '#f39c12';
document.getElementById('emergency-stop').textContent = '解除紧急';
document.getElementById('elevator-status').textContent = '紧急停止';
document.getElementById('overspeed-status').textContent = '激活';
} else {
document.getElementById('emergency-stop').style.background = '#e74c3c';
document.getElementById('emergency-stop').textContent = '紧急停止';
document.getElementById('elevator-status').textContent = '待机';
document.getElementById('overspeed-status').textContent = '未激活';
}
});
// 窗口大小调整
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
}
}
{
"dependencies": {
"three": "0.173.0"
}
}
body {
margin: 0;
overflow: hidden;
font-family: 'Microsoft YaHei', sans-serif;
background: #2c3e50;
color: #ecf0f1;
}
#canvas-container {
position: relative;
width: 100vw;
height: 100vh;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: #3498db;
}
#ui-overlay {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
}
#control-panel {
background: rgba(44, 62, 80, 0.8);
padding: 20px;
border-radius: 10px;
width: 300px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.floor-selector {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin: 20px 0;
}
.floor-btn {
background: #3498db;
color: white;
border-radius: 5px;
padding: 10px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.floor-btn:hover {
background: #2980b9;
transform: translateY(-2px);
}
.controls {
display: flex;
justify-content: space-between;
margin: 20px 0;
}
.controls button {
background: #27ae60;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: bold;
}
.controls button:hover {
background: #2ecc71;
transform: translateY(-2px);
}
.controls #emergency-stop {
background: #e74c3c;
}
.controls #emergency-stop:hover {
background: #c0392b;
}
.status, .safety-info {
padding: 15px;
background: rgba(0, 0, 0, 0.3);
border-radius: 5px;
margin: 10px 0;
}
.safety-info {
border: 2px solid #e74c3c;
}
h2, h3 {
text-align: center;
margin-bottom: 15px;
color: #3498db;
}
预览