THREE.js 可以实现很多web 3D的效果,最近想实现一个拖拽生成场景的功能,主要用到拖拽API,draggable 的几个事件来实现,拖拽模型时记录模型属性,释放到画布时生成当前屏幕坐标对应的焦点上,最后生成模型。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drag and Drop 3D Models</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#tools {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 100px;
background-color: #f2f2f2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1;
}
#scene {
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 100px;
background-color: #ddd;
}
#resetBtn {
position: absolute;
right: 20px;
bottom: 20px;
z-index: 2;
padding: 8px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: #fff;
font-size: 16px;
cursor: pointer;
}
img {
width: 50px;
height: 50px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="tools">
<img id="box" src="images/box.svg" draggable="true" />
<img id="cylinder" src="images/cylinder.svg" draggable="true" />
</div>
<div id="scene"></div>
<button id="resetBtn">Reset</button>
<script src="threejs.js"></script> <script src="OrbitControls.js"></script>
<script>
// 场景
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x002244);
// 渲染器
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth - 100, window.innerHeight);
document.getElementById("scene").appendChild(renderer.domElement);
// 相机
var camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
500
);
camera.near = 0.1;
camera.far = 10000;
camera.updateProjectionMatrix();
camera.position.set(0, 200, 500);
camera.lookAt(scene.position);
// 地面网格
// 地面网格
// 创建地面网格
//var geometry = new THREE.PlaneGeometry(10, 10);
//var material = new THREE.MeshBasicMaterial({ color: 0x888888, wireframe: true });
//var ground = new THREE.Mesh(geometry, material);
//ground.rotation.x = -Math.PI / 2;
//.receiveShadow = true; // 让地面网格接收阴影
//scene.add(ground);
var groundSize = 1000;
var ground = createGround(groundSize);
scene.add(ground);
// 创建两个球体,一个在地面上方,另一个在地面下方
var sphereRadius = 50;
var sphereTop = createSphere(sphereRadius);
sphereTop.position.set(0, sphereRadius, 0);
scene.add(sphereTop);
var sphereBottom = createSphere(sphereRadius);
sphereBottom.position.set(0, -sphereRadius, 0);
scene.add(sphereBottom);
// 创建环境光
var ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
// 创建点光源
var pointLight = new THREE.PointLight(0xffffff, 1, 1000);
pointLight.position.set(-100, 200, 100);
scene.add(pointLight);
function createGround(size) {
var geometry = new THREE.PlaneGeometry(size, size, 10, 10);
var material = new THREE.MeshLambertMaterial({ color: 0x888888 , wireframe: true});
var mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
mesh.receiveShadow = true;
return mesh;
}
function createSphere(radius) {
var geometry = new THREE.SphereGeometry(radius, 32, 32);
var material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
var mesh = new THREE.Mesh(geometry, material);
return mesh;
}
// 模型列表
var models = {
box: new THREE.BoxGeometry(50, 50, 50),
cylinder: new THREE.CylinderGeometry(25, 25, 50, 20),
};
// 记录被选中的模型和交叉点
var selectedModel = null;
var intersects = null;
// 工具栏按钮被拖动时,将其ID存储在dataTransfer中方便后续使用
document.querySelectorAll("#tools img").forEach((img) => {
img.addEventListener("dragstart", function (event) {
selectedModel = models[this.id].clone();
var currentTarget = event.currentTarget;
event.dataTransfer.setData("text", currentTarget.id);
});
});
// 场景可拖放,将模型放置在与地面网格相交的位置
document
.getElementById("scene")
.addEventListener("dragover", function (event) {
event.preventDefault();
});
document
.getElementById("scene")
.addEventListener("drop", function (event) {
event.preventDefault();
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
mouse.x = (event.offsetX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -(event.offsetY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
console.log(event.offsetX,event.offsetY);
intersects = raycaster.intersectObject(ground);
if (selectedModel && intersects) {
var mesh = new THREE.Mesh(
selectedModel,
new THREE.MeshPhongMaterial({
color: "#" + ((Math.random() * 0xffffff) << 0).toString(16),
})
);
mesh.position.copy(intersects[0].point);
mesh.position.y += selectedModel.parameters.height / 2;
scene.add(mesh);
}
});
// 重置场景
document.getElementById("resetBtn").addEventListener("click", function () {
while (scene.children.length > 1) {
scene.remove(scene.children[1]);
}
});
// 渲染场景
function render() {
renderer.render(scene, camera);
}
render();
// 监听窗口大小变化
window.addEventListener("resize", function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth - 100, window.innerHeight);
render();
});
// 鼠标移动事件,稍后更新交叉点
document.addEventListener("mousemove", function (event) {
});
// FPS控件
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 50, 0);
controls.update();
// 动画循环
function animate() {
requestAnimationFrame(animate);
render();
}
animate();
</script>
</body>
</html>