【工作记录】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';
        ....
        // 创建并初始化OrbitControls
        const 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);
        //创建渲染器 设置抗锯齿属性为true
        const 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);
        
         // 创建并初始化OrbitControls
        const 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)/100
        controls.minAzimuthAngle = (0*Math.PI)/100;
        controls.maxPolarAngle = (90*Math.PI)/100
        controls.minPolarAngle = (0*Math.PI)/100
        controls.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 defined
    at 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的引入过程,解决了模块化组件引入的问题,希望能帮助到需要的朋友。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/434868.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机系统缺少cv100.dll文件解决方法(最新)

cv100.dll 是一个Windows操作系统中的动态链接库&#xff08;DLL&#xff09;文件。DLL文件是包含可由多个程序共享的代码和数据的模块&#xff0c;以减少磁盘空间占用并提高系统性能。根据收集到的信息&#xff0c;cv100.dll 文件可能与图像处理、计算机视觉相关的功能有关。 …

springcloud:3.8测试限流

服务提供者【test-provider8001】 Openfeign远程调用服务提供者搭建 文章地址http://t.csdnimg.cn/06iz8 相关接口 测试远程调用&#xff1a;http://localhost:8001/payment/index 服务消费者【test-consumer-resilience4j8004】 Openfeign远程调用消费者搭建 文章地址http://t…

功能安全概念梳理二

什么是SEooC&#xff1f;SEooC和element有什么不一样&#xff1f; 参考链接&#xff1a;车规级 | ISO26262中对独立安全要素&#xff08;SEooC&#xff09;的开发要求 汽车功能安全(ISO 26262)系列: 到底什么是SEooC开发 安全措施(Safety measure)和安全机制(Safety mechanis…

[Redis]——缓存击穿和缓存穿透及解决方案(图解+代码+解释)

目录 一、缓存击穿&#xff08;热点Key问题&#xff09; 1.1 问题描述 1.2 解决方案及逻辑图 1.2.1 互斥锁 1.2.2 逻辑过期 二、缓存穿透 2.1 问题描述 2.2 解决方案逻辑图 2.2.1 缓存空对象 2.2.2 布隆过滤器 一、缓存击穿&#xff08;热点Key问题&#xff09; 个人理…

鸿蒙岗位大反攻:几十家头部应用加入鸿蒙后,鸿蒙工程师薪酬水涨船高

华为原生鸿蒙生态开始百花齐放&#xff1a;已经有几十家国内应用软件&#xff0c;宣布适配华为原生鸿蒙APP&#xff0c;其中就包括支付宝、美团、新浪微博、小红书、B站、高德地图、58同城等多家头部应用。 因为华为已经公开宣布&#xff1a;2024年的鸿蒙5.0系统将不再兼容安卓…

Redis(5.0)

1、什么是Redis Redis是一种开源的、基于内存、支持持久化的高性能Key-Value的NoSQL数据库&#xff0c;它同时也提供了多种数据结构来满足不同场景下的数据存储需求。 2、安装Redis&#xff08;Linux&#xff09; 2.1、去官网&#xff08;http://www.redis.cn/&#xff09;下…

Hololens 2应用开发系列(3)——MRTK基础知识及配置文件配置(中)

Hololens 2应用开发系列&#xff08;3&#xff09;——MRTK基础知识及配置文件配置&#xff08;中&#xff09; 一、前言二、输入系统2.1 MRTK输入系统介绍2.2 输入数据提供者&#xff08;Input Data Providers&#xff09;2.3 输入动作&#xff08;Input Actions&#xff09;2…

云服务器迁移--天翼云篇(最省钱解决官方未解问题)

0x00 背景 距离上次从华为云迁移到天翼云过了一年之久&#xff0c;之前买的一年期天翼云服务器快到期了&#xff0c;这次需要把这个池子(内蒙)的天翼云服务器迁移到新买的另一个池子的天翼云里。上一篇文章华为云迁移到天翼云教程在 云服务器迁移 (全网最省钱最详细攻略)_服务…

二分以及练习题目

二分模板 判断是否可以二分 &#xff08;1&#xff09;单调性 备选答案集是有序的 &#xff08;2&#xff09;二段性 在检查了mid是否符合要求之和&#xff0c;我可以舍弃左右某一边的答案 两个模板 关键词&#xff1a;满足条件的最小值&#xff0c;最大值最小&#xff0…

Django学习记录08——图表及文件上传案例

1.图表Echarts的应用 Apache ECharts 1.1 使用方法 引用echarts.js即可到官方文档中查询使用 1.2 常用图标的使用 图表展示页面的部署&#xff08;主要展示折线图、柱状图、饼图&#xff09; {% block content %}<div class"container"><div class&qu…

李沐动手学习深度学习——4.1练习

1. 计算pReLU激活函数的导数。 pReLU激活函数公式根据课本有如下&#xff1a; p R e L U ( x ) max ⁡ ( 0 , x ) α min ⁡ ( 0 , x ) \mathrm{pReLU}(x) \max(0, x)\alpha \min(0,x) pReLU(x)max(0,x)αmin(0,x) 对应的函数图像为: 对应的导数计算为&#xff1a; d p R…

github双因子认证

最近换了个安卓手机&#xff0c;打算让之前的苹果手机退役了&#xff0c;所以需要重新搞GitHub的Two-factor authentication 步骤如下&#xff1a; 1. 访问安全中心 https://github.com/settings/security 2. 点击Authenticator app右侧按钮 3. 下载腾讯身份验证器&#xff…

解密程序员的“藏宝图”:我的祖传代码大公开

程序员是如何看待“祖传代码”的&#xff1f; 大家好&#xff0c;我是小明&#xff0c;一位充满好奇心和分享热情的程序员。今天&#xff0c;我要为大家揭开我心中的“藏宝图”——那些我认为值得传世的祖传代码。让我们一同踏上这场奇妙的代码冒险之旅吧&#xff01; 宝物一…

WMS仓储管理系统在电子企业中的应用效果如何

一、WMS仓储管理系统的概念与重要性 在当今信息化与自动化的时代&#xff0c;仓储管理不再仅仅是简单的物品存储和分发。仓库作为供应链中的核心环节&#xff0c;其管理效率和准确性直接影响到企业的运营成本和客户满意度。WMS仓储管理系统应运而生&#xff0c;成为电子企业提…

代码随想录算法训练营第三十八天|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

509. 斐波那契数 刷题https://leetcode.cn/problems/fibonacci-number/description/文章讲解https://programmercarl.com/0509.%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE视频讲解https://www.bilibili.com/video/BV…

微服务如何保证对外接口的安全?可以这样做!

如果你的微服务需要向第三方开放接口&#xff0c;如何确保你提供的接口是安全的呢&#xff1f; 1. 什么是安全接口 通常来说&#xff0c;要将暴露在外网的 API 接口视为安全接口&#xff0c;需要实现防篡改和防重放的功能。 1.1 什么是篡改问题&#xff1f; 1.1.1 如何解决篡…

GEE 依照范围裁剪 下载Sentinel-2数据

0. GEE介绍 Google Earth Engine&#xff08;GEE&#xff09; 是由Google开发的一种云端平台&#xff0c;旨在提供强大的地理空间数据处理和分析工具。GEE集成了大量的遥感影像数据和地理空间数据集&#xff0c;以及高性能的计算资源&#xff0c;使用户能够在云端高效地进行大规…

【深度学习笔记】 5_9 含并行连结的网络(GoogLeNet)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 5.9 含并行连结的网络&#xff08;GoogLeNet&#xff09; 在2014年的ImageNet图像识别挑战赛中&#xff0c;一个名叫GoogLeNet的网络结…

Pycharm与Anaconda安装

网址&#xff1a; Pycharm&#xff1a;https://www.jetbrains.com/pycharm/ Anaconda&#xff1a;https://www.anaconda.com/download/ 官网下载速度太慢可以选择到清华源下载&#xff1a;https://repo.anaconda.com/archive/ 一&#xff1a;Anaconda安装 安装&#xff1a; …

Android开发社招面试经验,大佬带你看源码

程序员的劫 最近&#xff0c;又被程序员年龄的事情刷屏了。37岁被公司优化&#xff0c;找工作几个月都没有很好的归属&#xff0c;所谓的小公司还看不上。等等类似的话题变成了程序员的吐槽固定标题&#xff0c;无论是程序员&#xff0c;还是其他行业人员&#xff0c;都可以就…