效果

实现过程

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);

完整代码

3D旋转球体

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: