自从大二初次接触前端以来,一直都有记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 手写板之类的东西
首先是
-
初始化基础变量:
const ctx = cvs.getContext('2d');
:获取Canvas的2D绘图上下文,这是实际进行绘图操作的对象 。let isDraw = false;
:标志变量,用于表示是否处于绘图状态。let startPos = null;
和let endPos = null;
:记录绘图的起始点和结束点的变量。 -
添加落笔事件:
cvs.addEventListener('mousedown', (e) => {...});
:给Canvas添加鼠标按下事件监听器。当鼠标按下时,将触发这个事件处理函数,其中:isDraw = true;
:将isDraw
设置为true
,表示开始绘图。ctx.moveTo(e.pageX, e.pageY)
:将绘图的起始点设置为鼠标按下的位置。dataArray.push([e.pageX, e.pageY])
:将起始点的坐标保存到名为dataArray
的数组中。
-
添加笔触移动事件:
cvs.addEventListener('mousemove', (e) => {...});
:给Canvas添加鼠标移动事件监听器。当鼠标移动时,将触发这个事件处理函数,其中:-
如果
isDraw
为true
,表示处于绘图状态,就会执行:ctx.lineTo(e.pageX, e.pageY);
:画一条直线到当前鼠标位置。ctx.stroke();
:绘制路径。dataArray.push([e.pageX, e.pageY])
:将当前坐标保存到dataArray
数组中。
-
-
添加提笔事件:
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
中
-
构造函数:
- 调用了类内部的几个初始化方法:
initScene()
、initLight()
、initObject()
和initCamera()
。
- 调用了类内部的几个初始化方法:
-
initObject
方法:- 接受一个名为
position
的参数,这个参数似乎是二维坐标的数组。 - 创建了一个颜色为
0x05c5cf4
的MeshLambertMaterial
。 - 创建了一个几何体
geometry2
,表示一个长方体。 - 通过遍历传入的坐标数组,在每个坐标位置创建了一个长方体网格模型,然后将其添加到场景中。
- 接受一个名为
-
initScene
方法:- 创建了一个Three.js场景对象
this.scene
。 - 创建了一个包含网格帮助器(GridHelper)的组(Group),并将组添加到场景中。
- 网格帮助器是一个在场景中显示网格的辅助工具。
- 创建了一个Three.js场景对象
-
initLight
方法:- 创建了一个白色的点光源
pointLight
,并设置其位置。 - 创建了一个白色的环境光
ambient
。 - 将光源添加到场景中。
- 创建了一个白色的点光源
-
initCamera
方法:- 设置了画布的宽度和高度,然后计算了宽高比。
- 创建了透视相机对象
this.camera
,设置了位置和方向。
-
initRender
方法:- 创建了一个Three.js渲染器对象
renderer
,设置了渲染区域尺寸。 - 创建了一个轨道控制器(OrbitControls),并将其绑定到相机和渲染器的DOM元素上。
- 定义了一个渲染函数,使用
requestAnimationFrame
递归调用,实现动画渲染。 - 在渲染函数中,更新了控制器和动画,然后执行渲染操作。
- 最后,返回了渲染器对象。
- 创建了一个Three.js渲染器对象
抽象类
我们在使用的时候只需要 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/...