无图不欢,先上图
使用方法(以vue3为例)
<template>
<div class="net" ref="net"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import NetAnimation from '@/utils/netAnimation.js'
let net = ref(null)
onMounted(() => {
new NetAnimation({
dom: net.value,
pointLightsAttr: [{},{},{}],
axesHelperAttr: {
show: true,
length: 100
},
controlAttr: {
show: true
}
})
})
</script>
<style scoped lang="scss">
.net{
width: 100%;
height: 100%;
background-color: #02112e;
}
</style>
netAnimation.js源码
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
class NetAnimation {
constructor(opt) {
this.dom = opt.dom
this.w = null
this.h = null
this.netMaxY = opt.netMaxY
this.scene = null
this.renderer = null
this.camera = null
this.cameraAttr = opt.cameraAttr
this.ambientLight = null
this.ambientLightAttr = opt.ambientLightAttr
this.pointLights = []
this.pointLightsAttr = opt.pointLightsAttr
this.axesHelper = null
this.axesHelperAttr = opt.axesHelperAttr
this.control = null
this.controlAttr = opt.controlAttr
this.plane = null
this.planeAttr = opt.planeAttr
this.animationIndex = 0
this.requestAnimationFrame = null
this.init()
}
init = () => {
if(!this.dom){
return
}
this.w = this.dom.clientWidth * (window.devicePixelRatio || 1)
this.h = this.dom.clientHeight * (window.devicePixelRatio || 1)
// 创建场景
this.scene = this.createScene()
// 创建render
this.renderer = this.createWebGLRenderer({dom: this.dom});
// 创建相机
const cameraAttr = {
style: {
fov: 45,
aspect: this.w / this.h,
near: 0.01,
far: 10000
},
position: {
x: this.w / 2,
y: this.w / 2,
z: this.h * 2
}
}
if(this.cameraAttr){
this.cameraAttr = Object.assign(cameraAttr, this.cameraAttr)
}else{
this.cameraAttr = cameraAttr
}
this.camera = this.createPerspectiveCamera(this.cameraAttr.style)
this.camera.position.set(this.cameraAttr.position.x, this.cameraAttr.position.y, this.cameraAttr.position.z);
this.camera.lookAt(this.scene.position);
// 创建环境光
const ambientLightAttr = {
show: true,
style: {
color: "#fff",
intensity: 0.1
}
}
if(this.ambientLightAttr){
this.ambientLightAttr = Object.assign(ambientLightAttr, this.ambientLightAttr)
}else{
this.ambientLightAttr = ambientLightAttr
}
if(this.ambientLightAttr.show){
this.ambientLight = this.createAmbientLight(this.ambientLightAttr.style);
this.scene.add(this.ambientLight);
}
// 创建点光源
if(!this.netMaxY){
this.netMaxY = 60
}
const pointLightAttr = [{
style: {
color: '#fff',
intensity: 0.8,
distance: this.w
},
position: {
x: this.w * 0.1,
y: this.netMaxY * 4,
z: 0
}
},{
style: {
color: '#fff',
intensity: 1,
distance: this.w
},
position: {
x: - this.w * 0.2,
y: this.netMaxY * 4,
z: - this.netMaxY * 4
}
},{
style: {
color: '#fff',
intensity: 1,
distance: this.w
},
position: {
x: this.w,
y: this.netMaxY * 4,
z: this.netMaxY * 4
}
}]
if(this.pointLightsAttr?.length){
this.pointLightsAttr.forEach((pointLightItem, pointLightIndex) => {
pointLightItem = Object.assign(pointLightAttr[pointLightIndex % pointLightAttr.length], pointLightItem)
const pointLight = this.createPointLight(pointLightItem.style);
pointLight.position.set(pointLightItem.position.x, pointLightItem.position.y, pointLightItem.position.z);
this.pointLights.push(pointLight)
})
this.scene.add(...this.pointLights);
}
// 创建辅助线
const axesHelperAttr = {
show: false,
length: 100
}
if(this.axesHelperAttr){
this.axesHelperAttr = Object.assign(axesHelperAttr, this.axesHelperAttr)
}else{
this.axesHelperAttr = axesHelperAttr
}
if(this.axesHelperAttr.show){
this.axesHelper = this.createAxesHelper(this.axesHelperAttr.length)
this.scene.add(this.axesHelper);
}
// 创建轨道控制
const controlAttr = {
show: false
}
if(this.controlAttr){
this.controlAttr = Object.assign(controlAttr, this.controlAttr)
}else{
this.controlAttr = controlAttr
}
if(this.controlAttr.show){
this.createControl(this.camera, this.dom);
}
let planeAttr = {
width: this.w,
height: this.h,
widthSegments: Math.floor(this.w / 20),
heightSegments: Math.floor(this.h / 60)
}
if(this.planeAttr){
this.planeAttr = Object.assign(planeAttr, this.planeAttr)
}else{
this.planeAttr = planeAttr
}
const geometry = this.createPlaneGeometry(this.planeAttr)
const material = this.createMeshPlaneMaterial({
color: "#ffffff",
wireframe: true,
});
this.plane = this.createMesh({geometry, materialBasic: material});
this.plane.rotation.x = Math.PI * -0.5;
// this.plane.rotation.z = 45 * (Math.PI / 180);
// this.plane.position.z = 100;
this.scene.add( this.plane )
// 渲染
this.render()
}
render = () => {
//循环调用
this.requestAnimationFrame = requestAnimationFrame(this.render);
this.animation()
this.renderer.render(this.scene, this.camera);
}
unmount = () => {
cancelAnimationFrame(this.requestAnimationFrame)
}
animation = () => {
let animationSpeed = 10
let sinXNum = this.planeAttr.widthSegments
let sinYNum = this.planeAttr.heightSegments
const geometry = this.plane.geometry
const att_p = geometry.getAttribute('position');
let i = 0;
let xi = 0
let yi = 0
while(i < att_p.count){
let x = att_p.getX(i)
let y = att_p.getY(i)
xi = Math.floor(i / sinXNum)
yi = i - xi * sinXNum
let z = (Math.sin(((xi + this.animationIndex / animationSpeed) % sinXNum / sinXNum * Math.PI * 2)) + Math.sin(((yi + xi + this.animationIndex / animationSpeed) % sinYNum / sinYNum * Math.PI * 2))) * (this.netMaxY / 2)
att_p.setXYZ( i, x, y, z );
i += 1;
}
att_p.needsUpdate = true;
geometry.computeVertexNormals();
this.animationIndex++
this.animationIndex %= sinXNum * sinYNum * animationSpeed
}
// 以下皆为实体创建方法
createScene = () => {
return new THREE.Scene();
}
createPerspectiveCamera = ({ fov, aspect, near, far }) => {
// fov — 摄像机视锥体垂直视野角度
// aspect — 摄像机视锥体长宽比
// near — 摄像机视锥体近端面
// far — 摄像机视锥体远端面
return new THREE.PerspectiveCamera(fov, aspect, near, far);
}
createWebGLRenderer = ({ dom, width, height }) => {
// renderDom — dom
// width — 渲染宽度 一般取domclientWidth
// height — 渲染高度 一般取clientHeight
if (width === undefined) {
width = dom.clientWidth;
}
if (height === undefined) {
height = dom.clientHeight;
}
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio || 1);
renderer.setClearColor('#fff', 0); //设置背景颜色和透明度
renderer.setSize(width, height);
dom.appendChild(renderer.domElement);
return renderer;
}
createAmbientLight = ({ color, intensity }) => {
// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
// intensity - (可选参数) 光照强度。 缺省值 1。
return new THREE.AmbientLight(color, intensity);
}
createPointLight = ({ color, intensity, distance, decay }) => {
// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
// intensity - (可选参数) 光照强度。 缺省值 1。
// distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.
// decay - 沿着光照距离的衰退量。缺省值 2。
return new THREE.PointLight(color, intensity, distance, decay);
}
createPlaneGeometry = ({width, height, widthSegments, heightSegments}) => {
return new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
}
createMeshPlaneMaterial = (data) => {
return new THREE.MeshLambertMaterial(data);
}
createMesh = ({geometry, materialBasic}) => {
return new THREE.Mesh(geometry, materialBasic);
}
createAxesHelper = (length) => {
return new THREE.AxesHelper(length)
}
createControl = (camera, dom) => {
return new OrbitControls(camera, dom);
};
}
export default NetAnimation
如果电脑性能不错,可以考虑使用以下netAnimation2.js源码
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
class NetAnimation {
constructor(opt) {
this.dom = opt.dom
this.w = null
this.h = null
this.netMaxY = opt.netMaxY
this.scene = null
this.renderer = null
this.camera = null
this.cameraAttr = opt.cameraAttr
this.ambientLight = null
this.ambientLightAttr = opt.ambientLightAttr
this.pointLights = []
this.pointLightsAttr = opt.pointLightsAttr
this.axesHelper = null
this.axesHelperAttr = opt.axesHelperAttr
this.control = null
this.controlAttr = opt.controlAttr
this.points = []
this.lines = []
this.isSameNormalStyle = opt.isSameNormalStyle
this.pointAttr = opt.pointAttr
this.lineAttr = opt.lineAttr
this.pointsAttr = []
this.linesArrt = []
this.animationIndex = 0
this.requestAnimationFrame = null
this.init()
}
init = () => {
if(!this.dom){
return
}
this.w = this.dom.clientWidth * (window.devicePixelRatio || 1)
this.h = this.dom.clientHeight * (window.devicePixelRatio || 1)
// 创建场景
this.scene = this.createScene()
// 创建render
this.renderer = this.createWebGLRenderer({dom: this.dom});
// 创建相机
const cameraAttr = {
style: {
fov: 45,
aspect: this.w / this.h,
near: 0.01,
far: 10000
},
position: {
x: this.w / 2,
y: this.w / 2,
z: this.h * 2
}
}
if(this.cameraAttr){
this.cameraAttr = Object.assign(cameraAttr, this.cameraAttr)
}else{
this.cameraAttr = cameraAttr
}
this.camera = this.createPerspectiveCamera(this.cameraAttr.style)
this.camera.position.set(this.cameraAttr.position.x, this.cameraAttr.position.y, this.cameraAttr.position.z);
this.camera.lookAt(this.scene.position);
// 创建环境光
const ambientLightAttr = {
show: true,
style: {
color: "#fff",
intensity: 0.1
}
}
if(this.ambientLightAttr){
this.ambientLightAttr = Object.assign(ambientLightAttr, this.ambientLightAttr)
}else{
this.ambientLightAttr = ambientLightAttr
}
if(this.ambientLightAttr.show){
this.ambientLight = this.createAmbientLight(this.ambientLightAttr.style);
// this.scene.add(this.ambientLight);
}
// 创建点光源
if(!this.netMaxY){
this.netMaxY = 100
}
const pointLightAttr = [{
style: {
color: '#fff',
intensity: 0.8,
distance: this.w
},
position: {
x: this.w * 0.1,
y: this.netMaxY * 4,
z: 0
}
},{
style: {
color: '#fff',
intensity: 1,
distance: this.w
},
position: {
x: - this.w * 0.2,
y: this.netMaxY * 4,
z: - this.netMaxY * 4
}
},{
style: {
color: '#fff',
intensity: 1,
distance: this.w
},
position: {
x: this.w,
y: this.netMaxY * 4,
z: this.netMaxY * 4
}
}]
if(this.pointLightsAttr?.length){
this.pointLightsAttr.forEach((pointLightItem, pointLightIndex) => {
pointLightItem = Object.assign(pointLightAttr[pointLightIndex % pointLightAttr.length], pointLightItem)
const pointLight = this.createPointLight(pointLightItem.style);
pointLight.position.set(pointLightItem.position.x, pointLightItem.position.y, pointLightItem.position.z);
this.pointLights.push(pointLight)
})
this.scene.add(...this.pointLights);
}
// 创建辅助线
const axesHelperAttr = {
show: false,
length: 100
}
if(this.axesHelperAttr){
this.axesHelperAttr = Object.assign(axesHelperAttr, this.axesHelperAttr)
}else{
this.axesHelperAttr = axesHelperAttr
}
if(this.axesHelperAttr.show){
this.axesHelper = this.createAxesHelper(this.axesHelperAttr.length)
this.scene.add(this.axesHelper);
}
// 创建轨道控制
const controlAttr = {
show: false
}
if(this.controlAttr){
this.controlAttr = Object.assign(controlAttr, this.controlAttr)
}else{
this.controlAttr = controlAttr
}
if(this.controlAttr.show){
this.createControl(this.camera, this.dom);
}
// 创建点、线
console.time('a')
this.initPointLineData()
if(this.pointsAttr?.length){
// 点geometry、material
// let pointGeometry = null
// let pointMaterial = null
// let pointmMesh = null
// 线geometry、material
let lineMaterial = null
if(this.isSameNormalStyle === undefined){
this.isSameNormalStyle = true
// pointGeometry = this.createSphereGeometry(this.pointAttr.style.normal.geometry);
// pointMaterial = this.createMeshLambertMaterial(this.pointAttr.style.normal.material);
// pointmMesh = this.createMesh({geometry: pointGeometry, materialBasic: pointMaterial});
lineMaterial = this.createMeshLineMaterial(this.lineAttr.style.normal.material);
}
// this.pointsAttr.forEach(pointAttrItem => {
// // 创建点Mesh
// let mesh = null
// if(!this.isSameNormalStyle){
// pointGeometry = this.createSphereGeometry(pointAttrItem.style.normal.geometry);
// pointMaterial = this.createMeshLambertMaterial(pointAttrItem.style.normal.material);
// mesh = this.createMesh({geometry: pointGeometry, materialBasic: pointMaterial});
// }else{
// mesh = pointmMesh.clone();
// }
// mesh.position.set(pointAttrItem.position.x, pointAttrItem.position.y, pointAttrItem.position.z);
// this.points.push(mesh)
// })
this.linesArrt.forEach(lineAttrItem => {
// 创建线Mesh
let linePositions = []
lineAttrItem.forEach(linePoint => {
let i = (linePoint.row * this.pointAttr.col) + linePoint.col
linePositions.push({
...this.pointsAttr[i].position
})
})
const lineGeometry = this.createLineGeometry(linePositions);
// if(!this.isSameNormalStyle){
// lineMaterial = this.createMeshLineMaterial(lineAttrItem.style.normal.material);
// }
const lineMesh = this.createMesh({geometry: lineGeometry, materialBasic: lineMaterial});
this.lines.push(lineMesh)
})
// this.scene.add(...this.points);
this.scene.add(...this.lines);
}
console.timeEnd('a')
// 渲染
this.render()
}
initPointLineData() {
const pointAttr = {
width: this.w,
height: Math.floor(this.w / 2),
row: Math.floor(this.w / 40),
col: Math.floor(this.w / 20),
// width: this.w * 2,
// height: this.w,
// row: this.w / 20,
// col: this.w / 10,
// row: 10,
// col: 10,
style: {
normal: {
geometry: {
radius: 3,
widthSegments: 320,
heightSegments: 160,
},
material: {
color: "#ffffff",
wireframe: false, //是否将几何体渲染为线框,默认值为false(即渲染为平面多边形)
},
},
light: {
geometry: {
radius: 1,
widthSegments: 320,
heightSegments: 160,
},
material: {
color: "#ffffff",
wireframe: false,
},
}
}
}
if(this.pointAttr){
this.pointAttr = Object.assign(pointAttr, this.pointAttr)
}else{
this.pointAttr = pointAttr
}
const lineAttr = {
style: {
normal: {
material: {
color: "#fff",
// color: "#3587C7",
linewidth: 1,
},
},
light: {
material: {
color: "#ffffff",
linewidth: 1,
},
}
}
};
if(this.lineAttr){
this.lineAttr = Object.assign(lineAttr, this.lineAttr)
}else{
this.lineAttr = lineAttr
}
const startX = -this.pointAttr.width / 2
const startZ = -this.pointAttr.height / 2
const stepX = this.pointAttr.width / this.pointAttr.col
const stepZ = this.pointAttr.height / this.pointAttr.row
const sinXNum = this.pointAttr.row / 4
const sinZNum = this.pointAttr.col / 4
for(let i = 0 ; i < this.pointAttr.row; i++){
for(let j = 0 ; j < this.pointAttr.col; j++){
const x = startX + j * stepX
const z = startZ + i * stepZ
const y = (Math.sin((i % sinXNum / sinXNum * Math.PI * 2)) + Math.sin(((j + i) % sinZNum / sinZNum * Math.PI * 2))) * (this.netMaxY / 2)
this.pointsAttr.push({
row: i,
col: j,
position: {
x, y, z
},
style: {...this.pointAttr.style}
})
if(!this.linesArrt[i]){
this.linesArrt[i] = []
}
this.linesArrt[i][j] = {
row: i,
col: j
}
if(!this.linesArrt[this.pointAttr.row + j]){
this.linesArrt[this.pointAttr.row + j] = []
}
this.linesArrt[this.pointAttr.row + j][i] = {
row: i,
col: j
}
}
}
}
render = () => {
//循环调用
this.requestAnimationFrame = requestAnimationFrame(this.render);
this.animation()
this.renderer.render(this.scene, this.camera);
}
unmount = () => {
cancelAnimationFrame(this.requestAnimationFrame)
}
animation = () => {
const sinXNum = this.pointAttr.row / 4
const sinZNum = this.pointAttr.col / 4
const count = this.pointAttr.row * this.pointAttr.col
const animationSpeed = 10 //值越大越慢
console.time('b')
// if(this.animationIndex % 10 === 0){
this.pointsAttr.forEach((pointAttrItem, pointAttrIndex) => {
const i = pointAttrItem.row
const j = pointAttrItem.col
pointAttrItem.position.y = (Math.sin(((i + this.animationIndex / animationSpeed) % sinXNum / sinXNum * Math.PI * 2)) + Math.sin(((j + i + this.animationIndex / animationSpeed) % sinZNum / sinZNum * Math.PI * 2))) * 30
})
this.linesArrt.forEach((lineAttrItem, lineAttrIndex) => {
let linePositions = []
lineAttrItem.forEach(linePoint => {
let i = (linePoint.row * this.pointAttr.col) + linePoint.col
let point = this.pointsAttr[i]
linePositions.push({
...point.position
})
})
const lineGeometry = this.createLineGeometry(linePositions);
this.updateGeometry(this.lines[lineAttrIndex].geometry, lineGeometry)
})
// }
console.timeEnd('b')
this.animationIndex++
this.animationIndex %= count * animationSpeed
}
updateGeometry = (geometry, geometry_source) => {
const att_p = geometry.getAttribute('position');
const att_ps = geometry_source.getAttribute('position');
let i = 0;
while(i < att_p.count){
att_p.setXYZ( i, att_ps.getX(i), att_ps.getY(i),att_ps.getZ(i) );
i += 1;
}
att_p.needsUpdate = true;
geometry.computeVertexNormals();
};
rand = (n,m) => {
var c = m - n + 1;
return Math.floor(Math.random() * c + n);
}
// 以下皆为实体创建方法
createScene = () => {
return new THREE.Scene();
}
createPerspectiveCamera = ({ fov, aspect, near, far }) => {
// fov — 摄像机视锥体垂直视野角度
// aspect — 摄像机视锥体长宽比
// near — 摄像机视锥体近端面
// far — 摄像机视锥体远端面
return new THREE.PerspectiveCamera(fov, aspect, near, far);
}
createWebGLRenderer = ({ dom, width, height }) => {
// renderDom — dom
// width — 渲染宽度 一般取domclientWidth
// height — 渲染高度 一般取clientHeight
if (width === undefined) {
width = dom.clientWidth;
}
if (height === undefined) {
height = dom.clientHeight;
}
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio || 1);
renderer.setClearColor('#fff', 0); //设置背景颜色和透明度
renderer.setSize(width, height);
dom.appendChild(renderer.domElement);
return renderer;
}
createAmbientLight = ({ color, intensity }) => {
// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
// intensity - (可选参数) 光照强度。 缺省值 1。
return new THREE.AmbientLight(color, intensity);
}
createPointLight = ({ color, intensity, distance, decay }) => {
// color - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
// intensity - (可选参数) 光照强度。 缺省值 1。
// distance - 这个距离表示从光源到光照强度为0的位置。 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.
// decay - 沿着光照距离的衰退量。缺省值 2。
return new THREE.PointLight(color, intensity, distance, decay);
}
createSphereGeometry = ({
radius,
widthSegments,
heightSegments,
phiStart,
phiLength,
thetaStart,
thetaLength
}) => {
/*
radius — 球体半径,默认为1。
widthSegments — 水平分段数(沿着经线分段),最小值为3,默认值为32。
heightSegments — 垂直分段数(沿着纬线分段),最小值为2,默认值为16。
phiStart — 指定水平(经线)起始角度,默认值为0。。
phiLength — 指定水平(经线)扫描角度的大小,默认值为 Math.PI * 2。
thetaStart — 指定垂直(纬线)起始角度,默认值为0。
thetaLength — 指定垂直(纬线)扫描角度大小,默认值为 Math.PI。
*/
return new THREE.SphereGeometry(
radius,
widthSegments,
heightSegments,
phiStart,
phiLength,
thetaStart,
thetaLength
);
}
createMeshLambertMaterial = (data) => {
return new THREE.MeshLambertMaterial(data);
}
createLineGeometry = (points) => {
// const pointsVector3 = [];
// for (let i = 0; i < points.length; i++) {
// pointsVector3.push(new THREE.Vector3(points[i].x, points[i].y, points[i].z));
// }
// const geometry = new THREE.BufferGeometry().setFromPoints(pointsVector3);
// const line = new MeshLine();
// line.setGeometry(geometry);
// return line
let pointsVector3 = []
points.forEach(point => {
pointsVector3.push(new THREE.Vector3(point.x, point.y, point.z))
})
let curve = new THREE.CatmullRomCurve3(pointsVector3);
var CurvePath = new THREE.CurvePath();// 创建CurvePath对象
// CurvePath.curves.push(line1, curve, line2);// 插入多段线条
CurvePath.curves.push(curve);
return new THREE.TubeGeometry(CurvePath, points.length * 10, 1, 25, false);
}
createMeshLineMaterial = (data) => {
// return new MeshLineMaterial({
// lineWidth: data.linewidth,
// color: data.color || "white",
// dashArray: data.dashArray || 0,
// transparent: true,
// })
return new THREE.MeshLambertMaterial({
color: data.color || "white"
});
}
createMesh = ({geometry, materialBasic}) => {
return new THREE.Mesh(geometry, materialBasic);
}
createAxesHelper = (length) => {
return new THREE.AxesHelper(length)
}
createControl = (camera, dom) => {
return new OrbitControls(camera, dom);
};
}
export default NetAnimation