【工作记录】Threejs学习笔记-引入OrbitControls

2024-03-06 06:52

本文主要是介绍【工作记录】Threejs学习笔记-引入OrbitControls,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

前一篇文章我们介绍了three.js中的基础概念,并给出了展示整体流程的一个简单示例, 本文我们继续研究。

问题

我们在很多3d效果图上都能看到鼠标移动或者缩进实现旋转或者放大缩小的效果,这个在three.js中是通过OrbitControls这个组件实现的。

早上在使用threejs引入OrbitControls的时候发现新版本(大约r159以后)的引入方式都是通过import来引入的,而我想在纯html中做简单测试,

然而如果在html中直接使用import又会提示出各种各样的错误,经过反复的踩坑和实验以及百度,终于解决了这个模块化导入的问题。

所以本文姑且算是一篇踩坑记录吧。

期望目标

本文我们还是通过简单示例来展示要描述的问题和解决方案。

最终实现的效果是两个虚线圆环,自动转动,也可以根据鼠标拖拽或者缩放来触发旋转和缩放效果。

关于虚线圆环的实现方式和参数设置不是本文重点,可参考本文后面的代码进行实现和测试。

开始

前面提到要实现鼠标控制旋转和缩放需要用到OrbitControls这个组件,这个组件在three.js的源码中可以找到。

下载three.js源码

下载地址: https://gitee.com/mirrors/three.js

可以在标签中选择版本进行下载,截止发文时的最新版本是r162

源码下载

新建目录

目录结构如下图:
目录结构
可以直接把下载下来的源码文件中的对应文件夹直接搬过来,也可以只在对应目录放置需要的文件(本文中主要是用到了three.js或者three.module.js/OrbitControls.js这两个文件)。

引入js

这里需要说明一下,在r159版本以前是提供纯js版本的OrbitControls的代码的,位置在examples/js/controls文件夹中,但是后面新出的版本(r160+)删除了js目录,新增了jsm目录,存放的是以模块化的方式实现的组件代码,这个模块化代码直接在html中引用的话控制台会报错。

这里直接说解决方案:

  • 使用旧版本的OrbitControls.js文件,可以直接在html中引入。

    <script src="static/r159/build/three.min.js"></script>
    <script src="static/r159/examples/js/OrbitControls.js"></script>
    <script>
    ....
    // 创建并初始化OrbitControls
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    </script>
    
  • 使用新版本的模块化方式引入,需要调整引入方式

    <script type="importmap">{"imports": {"three": "../static/latest/build/three.module.js","three/addons/": "../static/latest/examples/jsm/"}}</script><script type="module">import * as THREE from 'three';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';....// 创建并初始化OrbitControlsconst controls = new OrbitControls(camera, renderer.domElement);....
    </script>
    

PS: 注意以上两种方案在实例化OrbitControls对象时是有区别的。

OrbitControls的介绍和属性配置

THREE.OrbitControls是Three.js库中一个非常实用的相机控制器,它允许用户通过鼠标或触摸设备以直观的方式旋转、缩放和平移3D场景。这个控件主要是为了让用户能够交互式地查看3D模型或场景,模拟类似3D建模软件中的轨道摄像机操作。

以下是关键参数配置和示例:

用途代码示例
新建对象const controls = new OrbitControls(camera, renderer.domElement);
设置相机围绕的目标点(焦点)controls.target.set(x, y, z); // 默认为(0, 0, 0)
是否启用控件controls.enabled = true //默认为true
控制功能开关controls.enableRotate = true; // 是否允许旋转,默认为true
controls.enableZoom = true; // 是否允许缩放,默认为true
controls.enablePan = true; // 是否允许平移,默认为true
限制范围controls.minDistance = 1; // 相机离目标的最小距离,默认值通常取决于场景大小
controls.maxDistance = 1000; // 相机离目标的最大距离
controls.minZoom = 0.1; // 最小缩放比例(如果支持)
controls.maxZoom = 100; // 最大缩放比例(如果支持)
旋转速度与灵敏度controls.rotateSpeed = 1.0; // 旋转速度,默认值为1
controls.zoomSpeed = 1.2; // 缩放速度,默认值为1
controls.panSpeed = 0.8; // 平移速度,默认值为0.8
旋转限制controls.minAzimuthAngle = -Math.PI / 2; // 最小左旋角度
controls.maxAzimuthAngle = Math.PI / 2; // 最大右旋角度
controls.minPolarAngle = 0; // 最小抬头角度(防止相机倒置)
controls.maxPolarAngle = Math.PI / 2; // 最大低头角度(比如只允许鸟瞰视角)

OrbitControls本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围。

controls.addEventListener('change', function () {// 浏览器控制台查看相机位置变化console.log('camera.position',camera.position);
});

补充知识

1. importMap的使用场景

import map 是一种Web平台的原生模块加载映射机制,它允许开发者在浏览器中自定义模块导入路径与实际加载地址之间的映射关系。通过 <script type="importmap"> 标签在HTML文档中定义一个 import map,可以解决以下使用场景:

  1. 模块路径重定向
    • 当项目依赖了多个库,而这些库可能因为版本更新、CDN地址变化或者内部模块结构调整等原因需要修改其导入路径时,import map 可以集中管理这些映射关系,无需更改代码中的 import 语句。
  2. 命名空间或包结构支持
    • 在Node.js环境和一些构建工具中,开发者习惯于使用类似 npm 的包管理和导入方式(如 import { someModule } from 'package-name')。import map 提供了一种在浏览器环境中模拟这种行为的方法,使得大型项目能够更好地组织和维护模块间的依赖关系。
  3. 多版本共存与按需加载
    • 同一项目中可能需要同时使用不同版本的库,import map 可以将不同的模块版本映射到不同的URL上,实现多个版本的同时加载与使用,避免版本冲突。
  4. 优化加载策略
    • 开发者可以通过 import map 将模块的源码映射到经过编译、压缩或缓存优化后的URL上,从而提升加载速度和用户体验。
  5. 本地开发与生产环境切换
    • 在开发阶段,可能需要从本地文件系统加载模块;而在部署上线后,则需要从CDN或其他远程服务器加载。import map 可以灵活配置这些差异化的加载路径。

以下为示例代码

<script type="importmap">
{"imports": {"module-a": "/path/to/module-a.js","module-b": "//cdn.example.com/module-b.js","package-name": "/local/path/to/package-name/index.js"}
}
</script>

然后在JavaScript模块中就可以按照映射关系来导入模块:

import { someFunction } from 'module-a';
import * as packageApi from 'package-name';

2. type=“module”的使用场景

type="module" 属性在HTML <script> 标签中使用,用于指示浏览器按照ECMAScript模块(ES6 Modules)的规范来加载和执行JavaScript代码。以下是 type="module" 使用的主要场景:

  1. 模块化开发
    • 当你的项目采用了ES6模块化机制,通过 importexport 语句导入和导出模块时,需要在引用这些模块的 <script> 标签中设置 type="module"
  2. 异步加载与依赖管理
    • ES6模块支持异步加载,浏览器会并行加载多个模块,然后根据模块间的依赖关系按序执行。
    • 这种方式可以避免传统的脚本阻塞页面渲染,提升页面加载性能。
  3. 代码组织与复用
    • 随着项目规模扩大,将代码拆分成多个模块进行管理和复用是非常必要的。type="module" 允许开发者定义独立的、可维护的模块,并确保每个模块有自己独立的作用域。
  4. 避免全局命名空间污染
    • 在模块内部定义的变量、函数等不会自动添加到全局作用域,这有助于减少不同模块之间的命名冲突问题。
  5. 现代前端框架配合
    • 现代前端框架如Vue.js、React.js等,在构建工具配置下通常默认采用模块化开发,即便在实际应用中不直接写 <script type="module">,但在构建后的产物或运行环境支持模块化的现代浏览器上,依然受益于模块化机制。
  6. 跨域限制
    • 注意,当在本地文件系统(file://)上直接打开带有 type="module" 的HTML文件时,由于浏览器安全策略,默认不允许跨域请求本地文件,因此可能无法正常加载模块。解决办法是使用像VSCode的Live Server插件或者部署到支持HTTP协议的本地服务器环境来预览和调试。

完整代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>3D Map with Three.js</title><style>body {margin: 0;}canvas {display: block;}</style>
</head><body><script type="importmap">{"imports": {"three": "../static/latest/build/three.module.js","three/addons/": "../static/latest/examples/jsm/"}}</script><script type="module">import * as THREE from 'three';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';// //场景const scene = new THREE.Scene();//透视投影相机const camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);//创建渲染器 设置抗锯齿属性为trueconst renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });renderer.setSize(window.innerWidth, window.innerHeight);//作为元素添加到html中document.body.appendChild(renderer.domElement);camera.position.z = 5;// resize 事件window.addEventListener("resize", () => {let width = window.innerWidth;let height = window.innerHeight;renderer.setSize(width, height);camera.aspect = width / height;/*** updateProjectionMatrix() 是 Three.js 中的一个方法,* 通常用于相机(Camera)对象。在Three.js中,当你更改了相机的投影参数(如透视相机的视场角、近裁剪面或远裁剪面等),* 或者更改了相机的位置、朝向等影响其投影矩阵的因素时,需要调用此方法来更新相机的内部投影矩阵。*/camera.updateProjectionMatrix();});// 定义旋转速度const clockwiseRotationSpeed = -0.005; // 顺时针旋转速度const counterclockwiseRotationSpeed = 0.005; // 逆时针旋转速度// 创建虚线圆圈的函数(这里简化为8段虚线)function createDashedCircle(radius, segments, dashSize, gapSize) {const vertices = [];const indices = [];for (let i = 0; i <= segments * 4; i++) { // 每个点分割为dashSize和gapSize两部分const angle = i / (segments * 4) * Math.PI * 2;const x = radius * Math.cos(angle);const y = radius * Math.sin(angle);if (i % 4 === 0) {vertices.push(x, y, 0); // dash起点vertices.push(x + dashSize, y, 0); // dash终点if (i > 0 && i !== segments * 12) { // 添加索引以形成线条indices.push(i - 4, i - 3, i - 2, i - 1);}} else if (i % 4 === 2) {vertices.push(x + dashSize + gapSize, y, 0); // gap终点同时也是下一个dash的起点}}const geometry = new THREE.BufferGeometry();geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));geometry.setIndex(indices);const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });return new THREE.LineSegments(geometry, material);}// 创建并添加两个虚线圆圈到场景const outerDashedCircle = createDashedCircle(3, 120, 0.01, 0.01);outerDashedCircle.position.z = -1;scene.add(outerDashedCircle);const innerDashedCircle = createDashedCircle(2.5, 120, 0.01, 0.01);innerDashedCircle.position.z = -2;scene.add(innerDashedCircle);// 创建并初始化OrbitControlsconst controls = new OrbitControls(camera, renderer.domElement);// 设置OrbitControls的一些参数,例如只允许沿着X轴旋转controls.enableDamping = true; // 使动画更平滑controls.dampingFactor = 0.05;controls.screenSpacePanning = false; // 禁止屏幕空间平移controls.minDistance = 2;controls.maxDistance = 10;//设置横向和纵向可旋转角度范围 可以通过尝试并调整controls.maxAzimuthAngle = (45*Math.PI)/100controls.minAzimuthAngle = (0*Math.PI)/100;controls.maxPolarAngle = (90*Math.PI)/100controls.minPolarAngle = (0*Math.PI)/100controls.update()// 环境光/*** AmbientLight 是 Three.js 中的一种光源类型,* 它模拟环境光的效果,即场景中的每个点都会受到相同强度和颜色的光照。* 在三维场景中添加 AmbientLight 可以提供全局的基础照明。* @type {AmbientLight}*/const ambientLight = new THREE.AmbientLight(0x404040, 0.5);scene.add(ambientLight);// const helper = new THREE.CameraHelper(camera)// scene.add(helper)// 每帧更新场景和控制function animate() {requestAnimationFrame(animate);controls.update(); // 更新OrbitControls的状态outerDashedCircle.rotation.z += clockwiseRotationSpeed;innerDashedCircle.rotation.z += counterclockwiseRotationSpeed;renderer.render(scene, camera);}animate();</script>
</body></html>

整体代码结构跟上篇文章介绍的差不多,中间多了OrbitControls的引入和配置,再就是虚线双环的实现。

最终效果

threejs实现的双圆环效果图

问题记录

  1. 控制台报错: Uncaught SyntaxError: Cannot use import statement outside a module

    这个问题主要是在script中引入了模块化的组件,但是没有配置参数type=“module”

    解决方案就是:

    <script type="module">...
    </script>
    
  2. 控制台报错:

Refused to execute script from 'http://127.0.0.1:5500/demo/static/latest/build/three.module.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
circle_r159_module.html:1  Refused to execute script from 'http://127.0.0.1:5500/demo/static/latest/examples/jsm/controls/OrbitControls.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
circle_r159_module.html:36  Uncaught ReferenceError: THREE is not definedat circle_r159_module.html:36:23

这个问题就是上面提到的模块化实现直接在html中引用导致的问题,解决方案如上文所述,推荐使用importMap解决。

<script type="importmap">{"imports": {"three": "../static/latest/build/three.module.js","three/addons/": "../static/latest/examples/jsm/"}}
</script>

同时也要注意在引用的时候script标签添加type=”module“属性,然后引用的时候使用import关键字即可。

  1. 控制台报错找不到js文件等问题

    从以下几个方向检查:

    • 检查路径是否正确,路径下是否存在目标文件
    • 检查路径是否包含中文字符
    • 检查路径是否包含.等特殊字符

总结

本文记录了在不依赖vue等模块化开发框架的基础上实现OrbitControls.js的引入过程,解决了模块化组件引入的问题,希望能帮助到需要的朋友。

针对以上问题有任何问题或者建议欢迎留言交流。

创作不易欢迎一键三连~~~

这篇关于【工作记录】Threejs学习笔记-引入OrbitControls的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/779191

相关文章

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

一文教你Python引入其他文件夹下的.py文件

《一文教你Python引入其他文件夹下的.py文件》这篇文章主要为大家详细介绍了如何在Python中引入其他文件夹里的.py文件,并探讨几种常见的实现方式,有需要的小伙伴可以根据需求进行选择... 目录1. 使用sys.path动态添加路径2. 使用相对导入(适用于包结构)3. 使用pythonPATH环境

Spring Boot中定时任务Cron表达式的终极指南最佳实践记录

《SpringBoot中定时任务Cron表达式的终极指南最佳实践记录》本文详细介绍了SpringBoot中定时任务的实现方法,特别是Cron表达式的使用技巧和高级用法,从基础语法到复杂场景,从快速启... 目录一、Cron表达式基础1.1 Cron表达式结构1.2 核心语法规则二、Spring Boot中定

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

kotlin中的模块化结构组件及工作原理

《kotlin中的模块化结构组件及工作原理》本文介绍了Kotlin中模块化结构组件,包括ViewModel、LiveData、Room和Navigation的工作原理和基础使用,本文通过实例代码给大家... 目录ViewModel 工作原理LiveData 工作原理Room 工作原理Navigation 工

国内环境搭建私有知识问答库踩坑记录(ollama+deepseek+ragflow)

《国内环境搭建私有知识问答库踩坑记录(ollama+deepseek+ragflow)》本文给大家利用deepseek模型搭建私有知识问答库的详细步骤和遇到的问题及解决办法,感兴趣的朋友一起看看吧... 目录1. 第1步大家在安装完ollama后,需要到系统环境变量中添加两个变量2. 第3步 “在cmd中

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板