【前端笔记】面试官:canvas和three.js怎么相互转化 | 入门canvas和three

自从大二初次接触前端以来,一直都有记markdown笔记的习惯.又因为掘金最近有一个活动.所以开了这个专栏。我会写一些业务相关的小技巧和前端知识中的重点内容之类的整理一下发上来。难免有点地方不够细致。欢迎大家指教

本篇文章是来源于一道国内做vr,ar的企业的笔试题(算是小独角兽那种)。题目是这样的.

css 复制代码
采用h5的canvas
1. 2d部分,画墙,注意测量部分;
2. 3d部分,根据2d部分传过来的数据,显示3d墙。建议使用:three.js引擎

最后花了一个小时搞定了。感觉还是挺有意思的。分享一下

最后的效果,跟下图差不多

废话少说,我们直接开始。

在线演示地址:electrolux.gitee.io/front-css-p...

代码完整地址:gitee.com/Electrolux/...

总体思路

  • 首先我们需要实现canvas的绘图逻辑,并且保存canvas移动过后的点(dataArray)
  • 然后我们需要实现three的初始化类逻辑(threeInit)。
  • 最后改造three类的object添加逻辑(initObject)。需要在一段canvas绘图结束后,我们将第一步保存的数据dataArray 传递给 调用第二步的initObject实现动态 渲染点

核心

css 复制代码
原来的3维坐标系中 根据canvas的
轨迹 新建 3d 的 1 * 1 的方块
并且根据 canvas 的位置进行放置

1.canvas 部分

思路

先讲一下基础思路,我们首先需要实现一个可写的canvas画布,即是canvas 手写板之类的东西

首先是

  1. 初始化基础变量:const ctx = cvs.getContext('2d');:获取Canvas的2D绘图上下文,这是实际进行绘图操作的对象 。 let isDraw = false;:标志变量,用于表示是否处于绘图状态。 let startPos = null;let endPos = null;:记录绘图的起始点和结束点的变量。

  2. 添加落笔事件:cvs.addEventListener('mousedown', (e) => {...});:给Canvas添加鼠标按下事件监听器。当鼠标按下时,将触发这个事件处理函数,其中:

    • isDraw = true;:将isDraw设置为true,表示开始绘图。
    • ctx.moveTo(e.pageX, e.pageY):将绘图的起始点设置为鼠标按下的位置。
    • dataArray.push([e.pageX, e.pageY]):将起始点的坐标保存到名为dataArray的数组中。
  3. 添加笔触移动事件: cvs.addEventListener('mousemove', (e) => {...});:给Canvas添加鼠标移动事件监听器。当鼠标移动时,将触发这个事件处理函数,其中:

    • 如果isDrawtrue,表示处于绘图状态,就会执行:

      • ctx.lineTo(e.pageX, e.pageY);:画一条直线到当前鼠标位置。
      • ctx.stroke();:绘制路径。
      • dataArray.push([e.pageX, e.pageY]):将当前坐标保存到dataArray数组中。
  4. 添加提笔事件:cvs.addEventListener('mouseup', (e) => {...});:给Canvas添加鼠标释放事件监听器。当鼠标释放时,将触发这个事件处理函数,其中:

    • isDraw = false;:将isDraw设置为false,表示结束绘图。
    • dataArray.push([e.pageX, e.pageY]):将释放点的坐标保存到dataArray数组中。
    • console.log(dataArray):在控制台输出绘制的路径坐标数组。

抽象函数

经过上面的分析,我们就可以很轻松抽象出一个函数

可以看到,我在下面的代码中有一个 wirte函数,里面传入 你的Canvas元素就可以让这块区域变成一个手绘板并且保存变量

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
  
    <canvas id="test" width="600" height="200"></canvas>


    <script>
         function write(dom) {
            const cvs = dom;
            const ctx = cvs.getContext('2d');
            let isDraw = false;
            let startPos = null
            let endPos = null
            cvs.addEventListener('mousedown', (e) => {
                dataArray  = []
                e = e || window.event;
                isDraw = true;
                ctx.moveTo(e.pageX, e.pageY)
                // console.log("start:",e.pageX, e.pageY)
                dataArray.push([e.pageX, e.pageY])
            })
            cvs.addEventListener('mousemove', (e) => {
                e = e || window.event;
       
                if (isDraw) {
                    ctx.lineTo(e.pageX, e.pageY);
                    ctx.stroke();
                    dataArray.push([e.pageX, e.pageY])
                }
                
            })
            cvs.addEventListener('mouseup', (e) => {
                isDraw = false;
                // console.log("end:",e.pageX, e.pageY)
                console.log(dataArray)
               
                dataArray.push([e.pageX, e.pageY])
                // three.initObject(dataArray)
            })
        }
        write(document.querySelector("#test"))
    </script>
</body>
</html>

2.threejs

思路

构造一个3d场景需要 object,scene,light,camera,render 这些基本对象,我们可以把他们封装到一个class 类 threeInit

  1. 构造函数:

    • 调用了类内部的几个初始化方法:initScene()initLight()initObject()initCamera()
  2. initObject方法

    • 接受一个名为position的参数,这个参数似乎是二维坐标的数组。
    • 创建了一个颜色为0x05c5cf4MeshLambertMaterial
    • 创建了一个几何体geometry2,表示一个长方体。
    • 通过遍历传入的坐标数组,在每个坐标位置创建了一个长方体网格模型,然后将其添加到场景中。
  3. initScene方法

    • 创建了一个Three.js场景对象this.scene
    • 创建了一个包含网格帮助器(GridHelper)的组(Group),并将组添加到场景中。
    • 网格帮助器是一个在场景中显示网格的辅助工具。
  4. initLight方法

    • 创建了一个白色的点光源pointLight,并设置其位置。
    • 创建了一个白色的环境光ambient
    • 将光源添加到场景中。
  5. initCamera方法

    • 设置了画布的宽度和高度,然后计算了宽高比。
    • 创建了透视相机对象this.camera,设置了位置和方向。
  6. initRender方法

    • 创建了一个Three.js渲染器对象renderer,设置了渲染区域尺寸。
    • 创建了一个轨道控制器(OrbitControls),并将其绑定到相机和渲染器的DOM元素上。
    • 定义了一个渲染函数,使用requestAnimationFrame递归调用,实现动画渲染。
    • 在渲染函数中,更新了控制器和动画,然后执行渲染操作。
    • 最后,返回了渲染器对象。

抽象类

我们在使用的时候只需要 new threeInit() 就好了

ini 复制代码
  class threeInit {
        constructor(param) {
            this.param = {
                x: 0,
                y: 0,
                z: 0
            }  
            this.initScene()
            this.initLight()
            this.initObject()
            this.initCamera()
        }
        // 传入二维坐标,
        initObject() {
            //材质
            let material = new THREE.MeshLambertMaterial({
                color: "rgb(197, 81, 81)"
            });
            let geometry1 = new THREE.BoxGeometry(10, 10, 100); //创建一个立方体几何对象Geometry
          
        }
        initScene() {
            this.scene = new THREE.Scene()
            const group = new THREE.Group();
                            this.scene.add( group );
            const helper = new THREE.GridHelper( 160, 100 );
                            helper.rotation.x = Math.PI / 2;
                            group.add( helper );
        }
        initLight() {
            let pointLight = new THREE.PointLight(0xffffff); //创建一个白色的点光源
            pointLight.position.set(0, 0, 150);
            this.scene.add(pointLight);
            let ambient = new THREE.AmbientLight(0xffffff, 1);
            this.scene.add(ambient); 
        }
        initCamera() {
            this.width = 1280; //canvas画布宽度
            this.height = 600; //canvas画布高度
            let k = this.width / this.height; //canvas画布宽高比
            //创建相机对象
            this.camera = new THREE.PerspectiveCamera(30, k, 20, 1000);
            this.camera.position.set(0, 10, 60); //设置相机位置
            let v1 = new THREE.Vector3(0, 0, 0)
            this.camera.lookAt(v1); //设置相机方向(指向的场景对象)
        }

        initRender() {

            let v1 = new THREE.Vector3(0, 0, 0)
            // let renderer = new THREE.WebGLRenderer();
            let renderer = new THREE.WebGLRenderer({ alpha: true });
            renderer.setSize(this.width, this.height);//设置渲染区域尺寸

            // 添加控制器
            let control = new OrbitControls(this.camera, renderer.domElement);
            control.target = v1 //设置相机方向(指向的场景对象)
            control.update();

            // 渲染函数
            let render = () => {
                // actions[0].play();
                const delta = clock.getDelta();
                if ( mixer ) mixer.update( delta );
                control.update();
                renderer.render(this.scene, this.camera);//执行渲染操作
                requestAnimationFrame(render);//请求再次执行渲染函数render,渲染下一帧
            }
            render();
            return renderer
        }


    }

3.threejs 和 canvas 联动

我们第一步我们拿到了 canvas 的 移动轨迹数据。我们只需要把 第二步的 initObject 改造成动态添加的就可以了。就是如下代码

csharp 复制代码
initObject(position) {
    //材质
    let material = new THREE.MeshLambertMaterial({
        color: "rgb(197, 81, 81)"
    });
   //创建一个立方体几何对象Geometry 
    let geometry2 = new THREE.BoxGeometry(1, 1, 10);   
    for(let i in position){
        let mesh =  new THREE.Mesh(geometry2, material); //网格模型对象Mesh
        mesh.position.set(position[i][0]/10, -position[i][1]/10, 0)
        this.scene.add(mesh)
    }
}

总结

总体来说,这段东西应该是入门难度的canvas 和 three。做一个这种demo应该还是能够学到不少东西的

在线演示地址:electrolux.gitee.io/front-css-p...

代码完整地址:gitee.com/Electrolux/...

相关推荐
陆仟6 分钟前
关闭小广告【JavaScript】
javascript
程序员大金38 分钟前
基于SpringBoot+Vue+MySQL的特色旅游网站系统
java·前端·vue.js·spring boot·后端·mysql·tomcat
陈住气^-^39 分钟前
面试题:react、vue中的key有什么作用?(key的内部原理)
javascript·vue.js·react.js
游仙好梦41 分钟前
Vitepress 自定义主题开发教程
前端·开源·vitepress
getaxiosluo1 小时前
详解Vite创建Vue3项目router-less-scss-pinia-持久化
前端·vue.js·chrome·typescript·less·scss
用你的胜利博我一笑吧1 小时前
supermap iclient3d for cesium中entity使用
前端·javascript·vue.js·3d·cesium·supermap
初心魏2 小时前
Uniapp 跨域
前端·数据库·uni-app
学会沉淀。2 小时前
Vue3快速入门+axios的异步请求(基础使用)
前端·javascript·vue.js
机器人迈克猫2 小时前
Django_Vue3_ElementUI_Release_003_前端Vue3项目初始化
前端·elementui·django
小于负无穷2 小时前
Webpack:现代前端项目的强大打包工具
前端·javascript·webpack·typescript·node.js