Three.js--》实现3d圣诞贺卡展示模型

目录

项目搭建

初始化three.js基础代码

加载环境模型

设置环境纹理

添加水面并设置阴影效果

实现幽灵小球的运动

实现相机切换和文字切屏

实现漫天星星和爱心样式


今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。

项目搭建

本案例还是借助框架书写three项目,借用vite构建工具搭建vue项目,vite这个构建工具如果有不了解的朋友,可以参考我之前对其讲解的文章:vite脚手架的搭建与使用 。搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖,安装完成之后终端在安装 npm i three 即可。

因为我搭建的是vue3项目,为了便于代码的可读性,所以我将three.js代码单独抽离放在一个组件当中,在App根组件中进入引入该组件。具体如下:

<template>
  <!-- 圣诞3d贺卡 -->
  <christmasCard></christmasCard>
</template>

<script setup>
import christmasCard from './components/christmasCard.vue';
</script>

<style lang="less">
  *{
    margin: 0;
    padding: 0;
  }
</style>

初始化three.js基础代码

three.js开启必须用到的基础代码如下:

导入three库

import * as THREE from 'three'

初始化场景

const scene = new THREE.Scene()

初始化相机

// 初始化相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.set(-3.23,2.98,4.06)
camera.updateProjectionMatrix()

初始化渲染器

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true // 设置抗锯齿
})
renderer.setSize(window.innerWidth,window.innerHeight)
document.body.appendChild(renderer.domElement)

监听屏幕大小的改变,修改渲染器的宽高和相机的比例

window.addEventListener("resize",()=>{ 
  renderer.setSize(window.innerWidth,window.innerHeight)
  camera.aspect = window.innerWidth/window.innerHeight
  camera.updateProjectionMatrix()
})

导入控制器

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
// 初始化控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true // 设置控制阻尼

设置渲染函数

const render = () =>{ 
  requestAnimationFrame(render)
  renderer.render(scene,camera)
  controls.update()
}

进行挂载

import { onMounted } from "vue";
onMounted(()=>{
  render()
})

ok,写完基础代码之后,接下来开始具体的Demo实操。 

加载环境模型

经过前几篇对three.js小demo的训练,相信大家对加载模型可谓是得心应手了吧,无非就四步嘛:

第一步引入加载GLTF模型和压缩模型的第三方库:

// 加载GLTF模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 解压GLTF模型
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';

第二步初始化loader:

const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/")
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)

第三步就是加载gltf模型:

gltfLoader.load("./model/scene.glb",(gltf)=>{
  const model = gltf.scene
  model.scale.set(0.3,0.3,0.3)
  scene.add(model)
})

第四步就是根据具体情况添加光源:

// 添加平行光
const light = new THREE.DirectionalLight(0xffffff,1)
light.position.set(0,50,0)
scene.add(light)

设置环境纹理

这里的话通过RGBELoader将HDR(高动态范围)格式的图片数据加载到Three.js中,并将其转换为Cubemap格式的文本形式,以用于创建更高质量、更真实的3D场景和物体。

// 解析 HDR 纹理数据
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'

let rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/sky.hdr',(texture)=>{
  texture.mapping = THREE.EquirectangularReflectionMapping
  scene.background = texture
  scene.environment = texture
})

当然我们也可以设置一下环境纹理的色调映射,如下:

// 设置色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping // 色调映射技术,是在电影和电视行业中广泛使用的一种技术
renderer.toneMappingExposure = 0.3 // 色调亮光程度

这样可让环境稍微暗一点,更具有朦胧感:

添加水面并设置阴影效果

接下来通过three给我们提供的库进行创建一个水面,如下:

// 导入水面
import { Water } from 'three/examples/jsm/objects/Water2'
// 创建水面
const waterGeometry = new THREE.CircleGeometry(100,100)
const water = new Water(waterGeometry,{
  textureWidth: 1024,
  textureHeight: 1024,
  color: 0xeeeeff,
  flowDirection: new THREE.Vector2(1,1),
  scale: 100
})
water.rotation.x = -Math.PI /2
water.position.y = -0.1
scene.add(water)

接下来通过设置一个点光源,然后给模型添加接受阴影效果,如下:

// 添加点光源
const pointLight = new THREE.PointLight(0xffffff,2,10)
pointLight.position.set(-0.05,0.8,0.1)
pointLight.castShadow = true
scene.add(pointLight)

实现幽灵小球的运动

接下来我们通过创建一个点光源组来实现三个发光的幽灵小球:

// 创建点光源组
const pointLightGroup = new THREE.Group();
pointLightGroup.position.set(-8, 2.5, -1.5);
let radius = 3;
let pointLightArr = [];
for (let i = 0; i < 3; i++) {
  // 创建球体当灯泡
  const sphereGeometry = new THREE.SphereGeometry(0.2, 32, 32);
  const sphereMaterial = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0xffffff,
    emissiveIntensity: 10,
  });
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  pointLightArr.push(sphere);
  sphere.position.set(
    radius * Math.cos((i * 2 * Math.PI) / 3),
    Math.cos((i * 2 * Math.PI) / 3),
    radius * Math.sin((i * 2 * Math.PI) / 3)
  );

  let pointLight = new THREE.PointLight(0xffffff, 50);
  sphere.add(pointLight);

  pointLightGroup.add(sphere);
}
scene.add(pointLightGroup);

因为要想实现动画效果,这里需要安装一个动画库,看参考我之前的文章: Gsap动画库基本使用与原理 ,这里就不再赘述。

通过使用补间函数,从0到2π,使灯泡旋转:

let options = {
  angle: 0,
};
gsap.to(options, {
  angle: Math.PI * 2,
  duration: 10,
  repeat: -1,
  ease: "linear",
  onUpdate: () => {
    pointLightGroup.rotation.y = options.angle;
    pointLightArr.forEach((item, index) => {
      item.position.set(
        radius * Math.cos((index * 2 * Math.PI) / 3),
        Math.cos((index * 2 * Math.PI) / 3 + options.angle * 5),
        radius * Math.sin((index * 2 * Math.PI) / 3)
      );
    });
  },
});

最后实现的效果如下:

实现相机切换和文字切屏

首先先定义一个移动函数,如下:

// 使用补间动画移动相机
let timeLine1 = gsap.timeline()
let timeLine2 = gsap.timeline()
// 定义相机移动函数
const translateCamera = (position,target) =>{ 
  timeLine1.to(camera.position, {
    x: position.x,
    y: position.y,
    z: position.z,
    duration: 1,
    ease: "power2.inOut",
  });

  timeLine2.to(controls.target, {
    x: target.x,
    y: target.y,
    z: target.z,
    duration: 1,
    ease: "power2.inOut",
  });
}

接下来定义文字切屏的场景:

// 添加文字场景
let scenes = [
  {
    text:'圣诞快乐',
    callback:()=>{
      // 执行函数切换位置
      translateCamera(
        new THREE.Vector3(-3.23, 3, 4.06),
        new THREE.Vector3(-8, 2, 0)
      );
    }
  },
  {
    text:'感谢世界这么大还能遇见你',
    callback:()=>{
      translateCamera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0))
    }
  },
  {
    text:'愿与你探寻整个世界',
    callback:()=>{
      translateCamera(new THREE.Vector3(10, 3, 0), new THREE.Vector3(5, 2, 0))
    }
  },
  {
    text:'愿将天上的星星送给你',
    callback:()=>{
      translateCamera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0))
    }
  },
  {
    text:'愿永远和你在一起',
    callback:()=>{
      translateCamera(
        new THREE.Vector3(-20, 1.3, 6.6),
        new THREE.Vector3(5, 2, 0)
      );
    }
  },
]

 接下来通过监听鼠标滚轮事件:

let index = ref(0)
let isAnimate = false;
// 监听鼠标滚轮事件
window.addEventListener(
  "wheel",
  (e) => {
    if (isAnimate) return;
    isAnimate = true;
    if (e.deltaY > 0) {
      index.value++;
      if (index.value > scenes.length - 1) {
        index.value = 0;
      }
    }
    scenes[index.value].callback();
    setTimeout(() => {
      isAnimate = false;
    }, 1000);
  },
  false
);

接下来设置文字展示的样式:

<template>
  <div 
    class="scenes"
    style="
      position: fixed;
      left: 0;
      top: 0;
      z-index: 10;
      pointer-events: none;
      transition: all 1s;
    "
    :style="{
      transform: `translate3d(0, ${-index * 100}vh, 0)`,
    }"
  >
    <div v-for="item in scenes" style="width: 100vw;height: 100vh;">
      <h1 style="padding: 100px 50px; font-size: 50px; color: #fff">{{ item.text }}</h1>
    </div>
  </div>
</template>

实现漫天星星和爱心样式

接下来通过InstancedMesh网格渲染优化方式,允许在渲染多个相同的模型时,只需要创建一个几何体和材质,然后通过实例化渲染多个对象,从而大大提高渲染性能。

// 实例化创建漫天星星
let starsInstance = new THREE.InstancedMesh(
  new THREE.SphereGeometry(0.1, 32, 32),
  new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0xffffff, //模拟自发光材质,并且不会受到光照的影响。
    emissiveIntensity: 10, // 控制发光效果强度
  }),
  100 // 创建100个
);

接下来将信息渲染到环境当中:

// 星星随机到天上
let starsArr = [];
let endArr = [];
for (let i = 0; i < 100; i++) {
  let x = Math.random() * 100 - 50;
  let y = Math.random() * 100 - 50;
  let z = Math.random() * 100 - 50;
  starsArr.push(new THREE.Vector3(x, y, z));

  let matrix = new THREE.Matrix4();
  matrix.setPosition(x, y, z);
  starsInstance.setMatrixAt(i, matrix);
}
scene.add(starsInstance);

接下来实现爱心的效果:

// 创建爱心路径
let heartShape = new THREE.Shape();
heartShape.moveTo(25, 25);
heartShape.bezierCurveTo(25, 25, 20, 0, 0, 0);
heartShape.bezierCurveTo(-30, 0, -30, 35, -30, 35);
heartShape.bezierCurveTo(-30, 55, -10, 77, 25, 95);
heartShape.bezierCurveTo(60, 77, 80, 55, 80, 35);
heartShape.bezierCurveTo(80, 35, 80, 0, 50, 0);
heartShape.bezierCurveTo(35, 0, 25, 25, 25, 25);

// 根据爱心路径获取点
let center = new THREE.Vector3(0, 2, 10);
for (let i = 0; i < 100; i++) {
  let point = heartShape.getPoint(i / 100);
  endArr.push(
    new THREE.Vector3(
      point.x * 0.1 + center.x,
      point.y * 0.1 + center.y,
      center.z
    )
  );
}

接下啦创建爱心动画:

// 创建爱心动画
function makeHeart() {
  let params = {
    time: 0,
  };

  gsap.to(params, {
    time: 1,
    duration: 1,
    onUpdate: () => {
      for (let i = 0; i < 100; i++) {
        let x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.time;
        let y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.time;
        let z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.time;
        let matrix = new THREE.Matrix4();
        matrix.setPosition(x, y, z);
        starsInstance.setMatrixAt(i, matrix);
      }
      starsInstance.instanceMatrix.needsUpdate = true;
    },
  });
}

function restoreHeart() {
  let params = {
    time: 0,
  };

  gsap.to(params, {
    time: 1,
    duration: 1,
    onUpdate: () => {
      for (let i = 0; i < 100; i++) {
        let x = endArr[i].x + (starsArr[i].x - endArr[i].x) * params.time;
        let y = endArr[i].y + (starsArr[i].y - endArr[i].y) * params.time;
        let z = endArr[i].z + (starsArr[i].z - endArr[i].z) * params.time;
        let matrix = new THREE.Matrix4();
        matrix.setPosition(x, y, z);
        starsInstance.setMatrixAt(i, matrix);
      }
      starsInstance.instanceMatrix.needsUpdate = true;
    },
  });
}

在场景中进行调用函数即可:

在定义一个定时器,让页面在刚加载的时候就开始调用,在设置一个监听函数,当用户点击屏幕的时候,定时器清除,然后用户可以自行手动去切换场景:

var timer = setInterval(function() {
  var scrollEvent = new WheelEvent('wheel', { deltaY: 100 });
  window.dispatchEvent(scrollEvent);
}, 3000);

document.addEventListener('click', function() {
  clearInterval(timer);
});

相关效果图如下:

demo做完,给出本案例的完整代码:(获取素材也可以私信博主)

<template>
  <div 
    class="scenes"
    style="
      position: fixed;
      left: 0;
      top: 0;
      z-index: 10;
      pointer-events: none;
      transition: all 1s;
    "
    :style="{
      transform: `translate3d(0, ${-index * 100}vh, 0)`,
    }"
  >
    <div v-for="item in scenes" style="width: 100vw;height: 100vh;">
      <h1 style="padding: 100px 50px; font-size: 50px; color: #fff">{{ item.text }}</h1>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import * as THREE from 'three'
// 导入动画库
import gsap from 'gsap';
import { onMounted } from "vue";
// 加载GLTF模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 解压GLTF模型
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
// 导入控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// 解析 HDR 纹理数据
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'
// 导入水面
import { Water } from 'three/examples/jsm/objects/Water2'

// 初始化场景
const scene = new THREE.Scene()
// 初始化相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.set(-3.23,2.98,4.06)
camera.updateProjectionMatrix()

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
  antialias: true // 设置抗锯齿
})
renderer.setSize(window.innerWidth,window.innerHeight)
document.body.appendChild(renderer.domElement)

// 监听页面变化
window.addEventListener("resize",()=>{  
  renderer.setSize(window.innerWidth,window.innerHeight)
  camera.aspect = window.innerWidth/window.innerHeight
  camera.updateProjectionMatrix()
})

// 设置色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping // 色调映射技术,是在电影和电视行业中广泛使用的一种技术
renderer.toneMappingExposure = 0.3 // 色调亮光程度
// 设置渲染器允许阴影效果
renderer.shadowMap.enabled = true
renderer.physicallyCorrectLights = true

// 初始化控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.target.set(-8,2,0)
controls.enableDamping = true // 设置控制阻尼

const render = () =>{ 
  requestAnimationFrame(render)
  renderer.render(scene,camera)
  controls.update()
}

onMounted(()=>{
  render()
})

// 初始化loader
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/")
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
// 加载模型
gltfLoader.load("./model/scene.glb",(gltf)=>{
  const model = gltf.scene
  model.traverse((child)=>{
    if(child.name === 'Plane'){
      child.visible = false
    }
    // 设置物体是允许接收和投射阴影的
    if(child.isMesh){
      child.castShadow = true
      child.receiveShadow = true
    }
  })
  scene.add(model)
})

// 创建水面
const waterGeometry = new THREE.CircleGeometry(100,100)
const water = new Water(waterGeometry,{
  textureWidth: 1024,
  textureHeight: 1024,
  color: 0xeeeeff,
  flowDirection: new THREE.Vector2(1,1),
  scale: 100
})
water.rotation.x = -Math.PI /2
water.position.y = -0.1
scene.add(water)

// 添加平行光
const light = new THREE.DirectionalLight(0xffffff,1)
light.position.set(0,50,0)
scene.add(light)
// 添加点光源
const pointLight = new THREE.PointLight(0xffffff,50,10)
pointLight.position.set(0.1, 2.4, 0)
pointLight.castShadow = true
scene.add(pointLight)

// 创建点光源组
const pointLightGroup = new THREE.Group();
pointLightGroup.position.set(-8, 2.5, -1.5);
let radius = 3;
let pointLightArr = [];
for (let i = 0; i < 3; i++) {
  // 创建球体当灯泡
  const sphereGeometry = new THREE.SphereGeometry(0.2, 32, 32);
  const sphereMaterial = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0xffffff,
    emissiveIntensity: 10,
  });
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  pointLightArr.push(sphere);
  sphere.position.set(
    radius * Math.cos((i * 2 * Math.PI) / 3),
    Math.cos((i * 2 * Math.PI) / 3),
    radius * Math.sin((i * 2 * Math.PI) / 3)
  );

  let pointLight = new THREE.PointLight(0xffffff, 50);
  sphere.add(pointLight);

  pointLightGroup.add(sphere);
}
scene.add(pointLightGroup);

// 使用补间函数,从0到2π,使灯泡旋转
let options = {
  angle: 0,
};
gsap.to(options, {
  angle: Math.PI * 2,
  duration: 10,
  repeat: -1,
  ease: "linear",
  onUpdate: () => {
    pointLightGroup.rotation.y = options.angle;
    pointLightArr.forEach((item, index) => {
      item.position.set(
        radius * Math.cos((index * 2 * Math.PI) / 3),
        Math.cos((index * 2 * Math.PI) / 3 + options.angle * 5),
        radius * Math.sin((index * 2 * Math.PI) / 3)
      );
    });
  },
});

// 加载纹理贴图
let rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/sky.hdr',(texture)=>{
  texture.mapping = THREE.EquirectangularReflectionMapping
  scene.background = texture
  scene.environment = texture
})

// 使用补间动画移动相机
let timeLine1 = gsap.timeline()
let timeLine2 = gsap.timeline()
// 定义相机移动函数
const translateCamera = (position,target) =>{ 
  timeLine1.to(camera.position, {
    x: position.x,
    y: position.y,
    z: position.z,
    duration: 1,
    ease: "power2.inOut",
  });

  timeLine2.to(controls.target, {
    x: target.x,
    y: target.y,
    z: target.z,
    duration: 1,
    ease: "power2.inOut",
  });
}

// 添加文字场景
let scenes = [
  {
    text:'圣诞快乐',
    callback:()=>{
      // 执行函数切换位置
      translateCamera(
        new THREE.Vector3(-3.23, 3, 4.06),
        new THREE.Vector3(-8, 2, 0)
      );
      restoreHeart()
    }
  },
  {
    text:'感谢世界这么大还能遇见你',
    callback:()=>{
      translateCamera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0))
    }
  },
  {
    text:'愿与你探寻整个世界',
    callback:()=>{
      translateCamera(new THREE.Vector3(10, 3, 0), new THREE.Vector3(5, 2, 0))
    }
  },
  {
    text:'愿将天上的星星送给你',
    callback:()=>{
      translateCamera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0))
      makeHeart()
    }
  },
  {
    text:'愿永远和你在一起',
    callback:()=>{
      translateCamera(
        new THREE.Vector3(-20, 1.3, 6.6),
        new THREE.Vector3(5, 2, 0)
      );
    }
  },
]

let index = ref(0)
let isAnimate = false;
// 监听鼠标滚轮事件
window.addEventListener(
  "wheel",
  (e) => {
    if (isAnimate) return;
    isAnimate = true;
    if (e.deltaY > 0) {
      index.value++;
      if (index.value > scenes.length - 1) {
        index.value = 0;
      }
    }
    scenes[index.value].callback();
    setTimeout(() => {
      isAnimate = false;
    }, 1000);
  },
  false
);

// 实例化创建漫天星星
let starsInstance = new THREE.InstancedMesh(
  new THREE.SphereGeometry(0.1, 32, 32),
  new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0xffffff, //模拟自发光材质,并且不会受到光照的影响。
    emissiveIntensity: 10, // 控制发光效果强度
  }),
  100 // 创建100个
);
// 星星随机到天上
let starsArr = [];
let endArr = [];
for (let i = 0; i < 100; i++) {
  let x = Math.random() * 100 - 50;
  let y = Math.random() * 100 - 50;
  let z = Math.random() * 100 - 50;
  starsArr.push(new THREE.Vector3(x, y, z));

  let matrix = new THREE.Matrix4();
  matrix.setPosition(x, y, z);
  starsInstance.setMatrixAt(i, matrix);
}
scene.add(starsInstance);

// 创建爱心路径
let heartShape = new THREE.Shape();
heartShape.moveTo(25, 25);
heartShape.bezierCurveTo(25, 25, 20, 0, 0, 0);
heartShape.bezierCurveTo(-30, 0, -30, 35, -30, 35);
heartShape.bezierCurveTo(-30, 55, -10, 77, 25, 95);
heartShape.bezierCurveTo(60, 77, 80, 55, 80, 35);
heartShape.bezierCurveTo(80, 35, 80, 0, 50, 0);
heartShape.bezierCurveTo(35, 0, 25, 25, 25, 25);

// 根据爱心路径获取点
let center = new THREE.Vector3(0, 2, 10);
for (let i = 0; i < 100; i++) {
  let point = heartShape.getPoint(i / 100);
  endArr.push(
    new THREE.Vector3(
      point.x * 0.1 + center.x,
      point.y * 0.1 + center.y,
      center.z
    )
  );
}

// 创建爱心动画
function makeHeart() {
  let params = {
    time: 0,
  };

  gsap.to(params, {
    time: 1,
    duration: 1,
    onUpdate: () => {
      for (let i = 0; i < 100; i++) {
        let x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.time;
        let y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.time;
        let z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.time;
        let matrix = new THREE.Matrix4();
        matrix.setPosition(x, y, z);
        starsInstance.setMatrixAt(i, matrix);
      }
      starsInstance.instanceMatrix.needsUpdate = true;
    },
  });
}

function restoreHeart() {
  let params = {
    time: 0,
  };

  gsap.to(params, {
    time: 1,
    duration: 1,
    onUpdate: () => {
      for (let i = 0; i < 100; i++) {
        let x = endArr[i].x + (starsArr[i].x - endArr[i].x) * params.time;
        let y = endArr[i].y + (starsArr[i].y - endArr[i].y) * params.time;
        let z = endArr[i].z + (starsArr[i].z - endArr[i].z) * params.time;
        let matrix = new THREE.Matrix4();
        matrix.setPosition(x, y, z);
        starsInstance.setMatrixAt(i, matrix);
      }
      starsInstance.instanceMatrix.needsUpdate = true;
    },
  });
}

var timer = setInterval(function() {
  var scrollEvent = new WheelEvent('wheel', { deltaY: 100 });
  window.dispatchEvent(scrollEvent);
}, 3000);

document.addEventListener('click', function() {
  clearInterval(timer);
});

</script>
<style lang="less" scoped></style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/26466.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

笔试强训8

作者&#xff1a;爱塔居 专栏&#xff1a;笔试强训 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步 day13 一. 单选 1.下列关于视图的说法错误的是&#xff1a; A 视图是从一个或多个基本表导出的表&#xff0c;它是虚表B 视图一经定义就可以和基本表一样被查询…

SSM 如何使用 Redis 实现缓存?

SSM 如何使用 Redis 实现缓存&#xff1f; Redis 是一个高性能的非关系型数据库&#xff0c;它支持多种数据结构和多种操作&#xff0c;可以用于缓存、队列、计数器等场景。在 SSM&#xff08;Spring Spring MVC MyBatis&#xff09;开发中&#xff0c;Redis 可以用来实现数…

皮卡丘CSRF

1.CSRF&#xff08;get&#xff09; 首先看提示&#xff0c;我们选择用户kobe&#xff0c;密码123456登录 点击修改个人信息&#xff0c;假如用户要把住址改为shanxi 再点击submit&#xff0c;同时用bp抓包&#xff0c;我们可以看到是get请求&#xff0c;数据包含在URL之中 将…

NCI架构-1

1、NFCC和DH通过物理连线相连&#xff0c;物理连线对应为Transport Layer&#xff08;传输层&#xff09;&#xff0c;支持SPI、I2C、UART、USB等&#xff1b; 2、DH中所有和NFC相关的应用程序都可视为DH-NFCEE(EE:Execution Enviroment)&#xff0c;图左的NFCEE模块可运行一些…

Jetson nano之ROS入门 - - 机器人建模与仿真

文章目录 前言一、URDF建模1. URDF语法详解a. robotb. linkc. joint 2. URDF机器人建模实操 二、Xacro宏优化1、 Xacro宏语法详解2、 Xacro建模实操 三、Rviz与Gazebo仿真1、Gazebo集成URDF建模语法基础2、Gazebo集成URDF实操 总结 前言 在ROS中&#xff0c;机器人建模和仿真是…

Android AIDL Callback的使用(配源码)

零、示例说明 本示例&#xff0c;完成的功能是&#xff1a;客户端向服务端注册一个回调&#xff0c;服务端是一个商店shop&#xff0c;当商店里的产品 Product 有变化时&#xff0c;调用回调向通知客户端&#xff0c;什么商品更新了。 一、完整源代码 完整源码链接: https:/…

solr快速上手:配置从mysql同步数据(五)

0. 引言 上一节我们已经配置了新的索引&#xff0c;但是数据还是手动添加的&#xff0c;并没有实现自动从数据库同步&#xff0c;所以这一节&#xff0c;继续来实现从mysql同步数据到solr solr快速上手&#xff1a;solr简介及安装&#xff08;一&#xff09; solr快速上手&a…

[java安全]反射

文章目录 [java安全]反射定义反射的运用1、反射获取类对象1.1、Class.forName()1.2、Object.class1.3、obj.getClass() 2、反射获取成员方法2.1、getMethods()2.2、getDeclaredMethods()2.3、getMethod()2.4、getDeclaredMethod() 3、反射获取构造方法4、反射创建对象4.1、通过…

自动化测试selenium环境搭建

自动化测试工具selenium搭建 1. 自动化和selenium基本概念 1) 什么是自动化?为什么要做自动化&#xff1f; 自动化测试能够代替一部分的手工测试&#xff0c;自动化测试能够提高测试的效率。随着项目功能的增加&#xff0c;版本越来越多&#xff0c;版本的回归测试的压力也…

MySQL架构简介

MySQL是系统架构中最常见的中间件&#xff0c;主要由Server层&#xff08;连接器Connectors、连接池Connection Pool、查询缓存query cache、分析器Parser、优化器Optimizer、执行器、binlog&#xff09;以及存储引擎层组成。 MySQL架构简介 连接器 与客户端建立连接、认证身…

论文笔记:Graph neural networks: A review of methods and applications

1 GNN的设计pipeline 1.1 获取图结构 结构化场景 图结构在应用问题中是已知的 比如分子结构、物理系统非结构化场景 图结构在应用问题中是未知的 需要根据任务人为地建图 1.2 判断图的类型 & 尺寸 图的类型 有向图/无向图//异构图/同构图 图中的点和边类型是不是一样的…

LearnOpenGL-高级OpenGL-8.高级GLSL

本人初学者&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 文章目录 高级GLSLGLSL的内建变量在顶点着色器的内建变量gl_PointSizegl_VertexID 在片段着色器的内建变量gl_FragCoordgl_FrontFacinggl_FragDepth 接口块Uniform缓冲对象Uniform块布局使用Uniform缓冲简…

基于QT的智能家居中控系统的简明设计

文章目录 系统总体说明主板UI设计后续改进与完善 系统总体说明 系统采用 “主从式架构” &#xff0c;即一主多从式&#xff0c;该智能居家控制系统的主要功能包括登录功能、注册功能、音乐播放功能、时间显示、日历显示、温度湿度光照气压海拔数据等环境指标数据显示等。   …

Linux之理解文件系统——文件的管理

文章目录 前言一、磁盘1.磁盘的物理结构2.磁盘的存储结构3.磁盘的逻辑结构 二、文件系统与inode1.文件在磁盘中是如何存储的&#xff1f;2.对文件进行操作 三、软硬链接1.软链接创建软链接&#xff1a;inode删除软链接&#xff1a;软链接的作用&#xff1a; 2.硬链接创建硬链接…

堆(堆排序 模拟堆)

目录 一、堆的数据结构二、堆的操作方法往下调整的示意图往上调整的示意图相关功能的实现思路1.插入一个数2.求最小值3.删除最小值4.删除任意一个元素5.修改任意一个元素 三、堆的实战运用堆排序模拟堆 一、堆的数据结构 堆是一个完全二叉树&#xff1a;除了最后一层结点以外&…

3ds MAX 基本体建模,长方体、圆柱体和球体

3ds MAX基本页面如下&#xff1a; 生成新的几何体在右侧&#xff1a; 选择生成的对象类型即可&#xff0c;以下为例子&#xff1a; 1、长方体建模 选择建立的对象类型为长方形 在 任意一个窗口绘制&#xff0c;鼠标滑动 这里选择左上角的俯视图 松开鼠标后&#xff0c;可以…

第18章 JQuery DataTables初始化渲染显示与排序

1 System.Linq.AsyncIEnumerableExtensions (Data\Extensions\AsyncIEnumerableExtensions.cs) namespace System.Linq { /// <summary> /// 【异步枚举数扩展--类】 /// <remarks> /// 摘要&#xff1a; /// 该类通过对System.Linq.Async中方法的自定义扩展…

C++进阶 —— set

目录 一&#xff0c;set介绍 二&#xff0c;set使用 一&#xff0c;set介绍 set是按照特定次序存储元素的关联式容器&#xff0c;元素不可重复&#xff1b;set中的元素不能在容器中修改(元素总是const)&#xff0c;但是可从容器中插入和删除它们&#xff1b;set中的元素总是按…

【测试报告】个人博客系统自动化测试报告

文章目录 项目背景项目功能测试计划功能测试测试用例执行测试的操作步骤 自动化测试设计的模块、自动化运行的结果、问题定位的结果自动化测试优点 项目背景 对于一个程序员来说&#xff0c;定期整理总结并写博客是不可或缺的步骤&#xff0c;不管是对近期新掌握的技术或者是遇…

代码随想录算法训练营第五十三天 | 力扣 1143.最长公共子序列, 1035.不相交的线, 53. 最大子序和

1143.最长公共子序列 题目 1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符…