Three.js三屏特效

在这里插入图片描述

<template>
  <div class="page page0">
    <h1>Three.js</h1>
    <h3>投射光线实现三维物体交互</h3>
  </div>
  <div class="page page1">
    <h1>Three.js</h1>
    <h3>炫酷三角形</h3>
  </div>
  <div class="page page2">
    <h1>Three.js</h1>
    <h3>点光源围绕照亮小球</h3>
  </div>
</template>

<script setup>
import * as THREE from 'three'
import gsap from 'gsap'
//导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 导入 dat.gui
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

// const gui = new GUI();
//1.创建场景
const scene = new THREE.Scene()
//2.创建相机 角度  宽高比  近端  远端
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 30)
// 设置相机位置  x y z 
camera.position.set(0, 0, 20)
// 把相机添加到场景中
scene.add(camera)

// 弹跳小球
let sphereGroup = new THREE.Group()
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20)
const material = new THREE.MeshStandardMaterial()
const sphere = new THREE.Mesh(sphereGeometry, material)
// 投射阴影
sphere.castShadow = true
sphereGroup.add(sphere)
// 创建平面
const planeGeometry = new THREE.PlaneGeometry(50, 50)
const plane = new THREE.Mesh(planeGeometry, material)
plane.position.set(0, -1, 0)
plane.rotation.x = -Math.PI / 2 // 基于x轴旋转90°
// 接收阴影
plane.receiveShadow = true
sphereGroup.add(plane)
// 红色小球
const smallBall = new THREE.Mesh(
  new THREE.SphereGeometry(0.1, 20, 20),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
smallBall.position.set(2, 2, 2)
// 点光源
const pointLight = new THREE.PointLight(0xff0000, 3);
// pointLight.position.set(2, 2, 2)
pointLight.castShadow = true
// 设置阴影贴图模糊度
pointLight.shadow.radius = 20
// 设置阴影贴图的分辨率
pointLight.shadow.mapSize.set(512, 512);
smallBall.add(pointLight)
sphereGroup.add(smallBall);
sphereGroup.position.set(0, -60, 0)
scene.add(sphereGroup)

// 三角形
let sjxGroup = new THREE.Group()
for (let i = 0; i < 50; i++) {
  // 每一个三角形,需要3个顶点,每个顶点需要3个值
  const geometry = new THREE.BufferGeometry()
  const vertices = new Float32Array(9)
  for (let j = 0; j < 9; j++) {
    vertices[j] = Math.random() * 10 - 5 // -5到5之间的数
  }
  geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
  const color = new THREE.Color(Math.random(), Math.random(), Math.random())
  const cubeMaterial = new THREE.MeshBasicMaterial({
    color,
    transparent: true,
    opacity: 0.8,
    side: THREE.DoubleSide // 背面也渲染
  });
  const sjxMesh = new THREE.Mesh(geometry, cubeMaterial);
  sjxGroup.add(sjxMesh);
}
sjxGroup.position.set(0, -30, 0)
scene.add(sjxGroup)

// 投射光线实现三维物体交互
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2)
const meterial = new THREE.MeshBasicMaterial({
  wireframe: true // 线框
})
const redMaterial = new THREE.MeshBasicMaterial({
  color: '#ff0000'
})
let cubeArr = []
let cubeGroup = new THREE.Group()
// 1000个立方体
for (let i = 0; i < 5; i++) {
  for (let j = 0; j < 5; j++) {
    for (let z = 0; z < 5; z++) {
      const cube = new THREE.Mesh(cubeGeometry, meterial)
      cube.position.set(i * 2 - 4, j * 2 - 4, z * 2 - 4)
      cubeGroup.add(cube)
      cubeArr.push(cube)
    }
  }
}
scene.add(cubeGroup)
// 创建投射光线对象
const raycaster = new THREE.Raycaster()
// 鼠标的位置对象
const mouse = new THREE.Vector2()
// 监听鼠标的位置
window.addEventListener('click', (event) => {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1 // -1 到 1

  mouse.y = -((event.clientY / window.innerHeight) * 2 - 1)
  raycaster.setFromCamera(mouse, camera) // 鼠标的二维坐标,相机坐标
  let result = raycaster.intersectObjects(cubeArr)// 检测物体
  // console.log(result);
  result.forEach(item => {
    item.object.material = redMaterial
  })
})

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ alpha: true })
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 将webgel渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement)

// 创建轨道控制器
// const controls = new OrbitControls(camera, renderer.domElement)
// // 设置控制器的阻尼 更真实 惯性
// controls.enableDamping = true

// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)

// 根据鼠标位置,摇晃相机
window.addEventListener('mousemove', (event) => {
  mouse.x = (event.clientX / window.innerWidth) - 0.5 // -0.5 到 0.5
  mouse.y = (event.clientY / window.innerHeight) - 0.5 // -0.5 到 0.5
})

gsap.to(cubeGroup.rotation, {
  x: "+=" + Math.PI, // 原来的地方 + Math.PI, 而不是替换
  y: "+=" + Math.PI,
  duration: 5,
  ease: "power2.inOut",
  repeat: -1
})
gsap.to(sjxGroup.rotation, {
  x: "-=" + Math.PI, // 动画结束时, 元素x会相加
  z: "+=" + Math.PI,
  duration: 6,
  ease: "power2.inOut",
  repeat: -1
})
gsap.to(smallBall.position, {
  x: -3, // 动画结束时, 元素x会相加
  duration: 6,
  ease: "power2.inOut",
  repeat: -1,
  yoyo: true
})
gsap.to(smallBall.position, {
  y: 0, // 动画结束时, 元素x会相加
  duration: 0.5,
  ease: "power2.inOut",
  repeat: -1,
  yoyo: true
})
// 设置时钟
const clock = new THREE.Clock()
function render() {
  // let time = clock.getElapsedTime() // 自时钟启动后的秒数
  let deltaTime = clock.getDelta() // 2帧之间的时间差

  // cubeGroup.rotation.x = time * 0.5
  // cubeGroup.rotation.y = time * 0.5

  // sjxGroup.rotation.x = time * 0.4
  // sjxGroup.rotation.z = time * 0.3

  // smallBall.position.x = Math.sin(time) * 3 // 返回值在 -1.0 到 1.0 之间 * 3
  // smallBall.position.z = Math.cos(time) * 3
  // smallBall.position.y = 2 + Math.sin(time * 10) / 2
  // sphereGroup.rotation.z = Math.sin(time) * 0.05
  // sphereGroup.rotation.x = Math.sin(time) * 0.05

  // 根据当前滚动的scrolly,去设置相机移动的位置
  camera.position.y = -(window.scrollY / window.innerHeight) * 30
  // 实现平滑的相机移动或跟踪效果
  camera.position.x += (mouse.x * 10 - camera.position.x) * deltaTime * 5

  // controls.update()
  // 使用渲染器,通过相机将场景渲染进来
  renderer.render(scene, camera);
  // 渲染下一帧的时候 调用 render函数
  requestAnimationFrame(render)
}
render()

// 监听窗口尺寸变化,更新渲染画面
window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight
  // 更新摄像机的投影矩阵
  camera.updateProjectionMatrix()

  // 更新渲染器
  renderer.setSize(window.innerWidth, window.innerHeight)
  // 设置渲染器的像素比
  renderer.setPixelRatio(window.devicePixelRatio)
})



// 设置当前页
let currentPage = 0
let arrGroup = [cubeGroup, sjxGroup, sphereGroup]
window.addEventListener("scroll", () => {
  // 滚动条距离顶部的高度 / 屏幕的高度
  const newPage = Math.round(window.scrollY / window.innerHeight)
  if (newPage !== currentPage) {
    currentPage = newPage
    gsap.to(arrGroup[currentPage].rotation, {
      z: "+=" + Math.PI * 2,
      x: "+=" + Math.PI * 2,
      duration: 2,
      onComplete: () => {
        console.log('旋转完成');
      }
    })

    // gsap.to(`.page${currentPage} h1`, {
    //   rotate: '+=360', // 不给+=只会执行一次
    //   duration: 1
    // })
    gsap.fromTo(
      `.page${currentPage} h1`,
      { x: -300 },
      { x: 0, rotate: '+=360', duration: 1 }
    )
  }
})
</script>

<style>
* {
  margin: 0;
  padding: 0;
}

body {
  background-color: rgb(36, 58, 66);
}

canvas {
  position: fixed;
  left: 0;
  top: 0;
}

::-webkit-scrollbar{
  display: none;
}
</style>

<style scoped>
.page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  color: #fff;
  position: relative;
  z-index: 999;
}

.page h1 {
  margin: 60px;
  font-size: 40px;
}

.page h3 {
  font-size: 30px;
}
</style>
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值