赞
踩
1. 获取Canvas元素和设置初始参数
// 获取Canvas元素 const canvas = document.querySelector('#scene'); canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; const ctx = canvas.getContext('2d'); // 针对高DPI屏幕进行缩放 if (window.devicePixelRatio > 1) { canvas.width = canvas.clientWidth * 2; canvas.height = canvas.clientHeight * 2; ctx.scale(2, 2); } // Canvas的宽度和高度 let width = canvas.clientWidth; let height = canvas.clientHeight; // 球体旋转的角度 let rotation = 0; // 存储所有点的数组 let dots = [];
这部分代码主要是获取Canvas元素,并根据设备的DPI进行缩放。然后,定义了一些全局变量,包括Canvas的宽度、高度、球体旋转的角度和存储所有点的数组。
2. 定义一些常量
// 一些常量
const DOTS_AMOUNT = 1000; // 点的数量
const DOT_RADIUS = 2; // 点的半径
let GLOBE_RADIUS = width * 0.7; // 球半径
let GLOBE_CENTER_Z = -GLOBE_RADIUS; // 球心的Z坐标
let PROJECTION_CENTER_X = width / 2; // 画布HTML的X中心
let PROJECTION_CENTER_Y = height / 2; // 画布HTML的Y中心
let FIELD_OF_VIEW = width * 0.8;
这部分代码定义了一些常量,如点的数量、点的半径、球半径等。
3.定义点(Dot)类
// Dot类表示每个点的属性 class Dot { constructor(x, y, z, color) { this.x = x; this.y = y; this.z = z; this.color = color; // 添加颜色属性 this.xProject = 0; this.yProject = 0; this.sizeProjection = 0; } // 将三维坐标映射到二维画布上 project(sin, cos) { const rotX = cos * this.x + sin * (this.z - GLOBE_CENTER_Z); const rotZ = -sin * this.x + cos * (this.z - GLOBE_CENTER_Z) + GLOBE_CENTER_Z; this.sizeProjection = FIELD_OF_VIEW / (FIELD_OF_VIEW - rotZ); this.xProject = (rotX * this.sizeProjection) + PROJECTION_CENTER_X; this.yProject = (this.y * this.sizeProjection) + PROJECTION_CENTER_Y; } // 在画布上绘制点 draw(sin, cos) { this.project(sin, cos); ctx.beginPath(); ctx.arc(this.xProject, this.yProject, DOT_RADIUS * this.sizeProjection, 0, Math.PI * 2); ctx.fillStyle = this.color; // 使用粒子的颜色属性 ctx.closePath(); ctx.fill(); } }
这部分代码定义了一个Dot类,表示球体上的每个点的属性和方法。包括构造函数、将三维坐标映射到二维画布上的project方法,以及在画布上绘制点的draw方法
4.创建和渲染球体的函数
// 创建所有点的函数 function createDots() { // 清空粒子数组 dots.length = 0; // 创建一半蓝色粒子 for (let i = 0; i < DOTS_AMOUNT / 2; i++) { const theta = Math.random() * 2 * Math.PI; const phi = Math.acos((Math.random() * 2) - 1); const x = GLOBE_RADIUS * Math.sin(phi) * Math.cos(theta); const y = GLOBE_RADIUS * Math.sin(phi) * Math.sin(theta); const z = (GLOBE_RADIUS * Math.cos(phi)) + GLOBE_CENTER_Z; dots.push(new Dot(x, y, z, '#981898')); } // 创建一半红色粒子 for (let i = 0; i < DOTS_AMOUNT / 2; i++) { const theta = Math.random() * 2 * Math.PI; const phi = Math.acos((Math.random() * 2) - 1); const x = GLOBE_RADIUS * Math.sin(phi) * Math.cos(theta); const y = GLOBE_RADIUS * Math.sin(phi) * Math.sin(theta); const z = (GLOBE_RADIUS * Math.cos(phi)) + GLOBE_CENTER_Z; dots.push(new Dot(x, y, z, '#E71751')); } }
这部分代码定义了createDots函数,用于生成一定数量的点,并分别将一半标记为蓝色,一半标记为红色。这里可以根据需求改动,
// 渲染函数,不断更新球体的旋转
function render(a) {
ctx.clearRect(0, 0, width, height);
rotation = a * 0.0004;
const sineRotation = Math.sin(rotation);
const cosineRotation = Math.cos(rotation);
// 遍历所有点并绘制
for (var i = 0; i < dots.length; i++) {
dots[i].draw(sineRotation, cosineRotation);
}
window.requestAnimationFrame(render);
}
这部分代码定义了render函数,用于在每一帧更新球体的旋转角度,并循环绘制所有的点,产生连续的旋转效果。
5.监听窗口大小变化事件
// 当用户调整窗口大小时重新计算参数 function afterResize() { width = canvas.offsetWidth; height = canvas.offsetHeight; if (window.devicePixelRatio > 1) { canvas.width = canvas.clientWidth * 2; canvas.height = canvas.clientHeight * 2; ctx.scale(2, 2); } else { canvas.width = width; canvas.height = height; } GLOBE_RADIUS = width * 0.7; GLOBE_CENTER_Z = -GLOBE_RADIUS; PROJECTION_CENTER_X = width / 2; PROJECTION_CENTER_Y = height / 2; FIELD_OF_VIEW = width * 0.8; createDots(); // 重新生成所有点 } // 变量用于存储用户调整窗口大小时的超时 let resizeTimeout; // 当用户调整窗口大小时触发的函数 function onResize() { resizeTimeout = window.clearTimeout(resizeTimeout); resizeTimeout = window.setTimeout(afterResize, 500); } // 监听窗口大小变化事件 window.addEventListener('resize', onResize);
这部分代码用于监听窗口大小变化事件,并在用户调整窗口大小时重新计算参数,以保持效果的稳定性。
最后初始化一下就好了
// 初始化点数组
createDots();
// 渲染场景
window.requestAnimationFrame(render);
完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>3D旋转球体</title> <style> body { height: 100vh; display: flex; justify-content: center; align-items: center; background: #000; margin: 0; } canvas { width: 500px; height: 500px; } </style> </head> <body> <canvas id="scene"></canvas> <script> // 获取Canvas元素 const canvas = document.querySelector('#scene'); canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; const ctx = canvas.getContext('2d'); // 针对高DPI屏幕进行缩放 if (window.devicePixelRatio > 1) { canvas.width = canvas.clientWidth * 2; canvas.height = canvas.clientHeight * 2; ctx.scale(2, 2); } // Canvas的宽度和高度 let width = canvas.clientWidth; let height = canvas.clientHeight; // 球体旋转的角度 let rotation = 0; // 存储所有点的数组 let dots = []; // 一些常量 const DOTS_AMOUNT = 1000; // 点的数量 const DOT_RADIUS = 2; // 点的半径 let GLOBE_RADIUS = width * 0.7; // 球半径 let GLOBE_CENTER_Z = -GLOBE_RADIUS; // 球心的Z坐标 let PROJECTION_CENTER_X = width / 2; // 画布HTML的X中心 let PROJECTION_CENTER_Y = height / 2; // 画布HTML的Y中心 let FIELD_OF_VIEW = width * 0.8; // Dot类表示每个点的属性 class Dot { constructor(x, y, z, color) { this.x = x; this.y = y; this.z = z; this.color = color; // 添加颜色属性 this.xProject = 0; this.yProject = 0; this.sizeProjection = 0; } // 将三维坐标映射到二维画布上 project(sin, cos) { const rotX = cos * this.x + sin * (this.z - GLOBE_CENTER_Z); const rotZ = -sin * this.x + cos * (this.z - GLOBE_CENTER_Z) + GLOBE_CENTER_Z; this.sizeProjection = FIELD_OF_VIEW / (FIELD_OF_VIEW - rotZ); this.xProject = (rotX * this.sizeProjection) + PROJECTION_CENTER_X; this.yProject = (this.y * this.sizeProjection) + PROJECTION_CENTER_Y; } // 在画布上绘制点 draw(sin, cos) { this.project(sin, cos); ctx.beginPath(); ctx.arc(this.xProject, this.yProject, DOT_RADIUS * this.sizeProjection, 0, Math.PI * 2); ctx.fillStyle = this.color; // 使用粒子的颜色属性 ctx.closePath(); ctx.fill(); } } // 创建所有点的函数 function createDots() { // 清空粒子数组 dots.length = 0; // 创建一半蓝色粒子 for (let i = 0; i < DOTS_AMOUNT / 2; i++) { const theta = Math.random() * 2 * Math.PI; const phi = Math.acos((Math.random() * 2) - 1); const x = GLOBE_RADIUS * Math.sin(phi) * Math.cos(theta); const y = GLOBE_RADIUS * Math.sin(phi) * Math.sin(theta); const z = (GLOBE_RADIUS * Math.cos(phi)) + GLOBE_CENTER_Z; dots.push(new Dot(x, y, z, '#981898')); } // 创建一半红色粒子 for (let i = 0; i < DOTS_AMOUNT / 2; i++) { const theta = Math.random() * 2 * Math.PI; const phi = Math.acos((Math.random() * 2) - 1); const x = GLOBE_RADIUS * Math.sin(phi) * Math.cos(theta); const y = GLOBE_RADIUS * Math.sin(phi) * Math.sin(theta); const z = (GLOBE_RADIUS * Math.cos(phi)) + GLOBE_CENTER_Z; dots.push(new Dot(x, y, z, '#E71751')); } } // 渲染函数,不断更新球体的旋转 function render(a) { ctx.clearRect(0, 0, width, height); rotation = a * 0.0004; const sineRotation = Math.sin(rotation); const cosineRotation = Math.cos(rotation); // 遍历所有点并绘制 for (var i = 0; i < dots.length; i++) { dots[i].draw(sineRotation, cosineRotation); } window.requestAnimationFrame(render); } // 当用户调整窗口大小时重新计算参数 function afterResize() { width = canvas.offsetWidth; height = canvas.offsetHeight; if (window.devicePixelRatio > 1) { canvas.width = canvas.clientWidth * 2; canvas.height = canvas.clientHeight * 2; ctx.scale(2, 2); } else { canvas.width = width; canvas.height = height; } GLOBE_RADIUS = width * 0.7; GLOBE_CENTER_Z = -GLOBE_RADIUS; PROJECTION_CENTER_X = width / 2; PROJECTION_CENTER_Y = height / 2; FIELD_OF_VIEW = width * 0.8; createDots(); // 重新生成所有点 } // 变量用于存储用户调整窗口大小时的超时 let resizeTimeout; // 当用户调整窗口大小时触发的函数 function onResize() { resizeTimeout = window.clearTimeout(resizeTimeout); resizeTimeout = window.setTimeout(afterResize, 500); } // 监听窗口大小变化事件 window.addEventListener('resize', onResize); // 初始化点数组 createDots(); // 渲染场景 window.requestAnimationFrame(render); </script> </body> </html>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。