three.js基于滚动的动画

本文部分内容为Three.js Journey课程的学习笔记

todo:

  • 学习如何将 Three.js 作为 HTML 页面的背景
  • 让相机跟随滚动而移动
  • 发现一些技巧让3D效果更具沉浸感
  • 基于光标位置添加视差效果,当到达特点区域时触发动画效果

[1]css设置

要将Three.js 作为HTML 页面的背景,首先需要对页面css布局进行一定的设置

<body>
  <canvas class="webgl"></canvas>
  <section class="section">
    <h1>My Portfolio</h1>
  </section>
  <section class="section">
    <h2>My projects</h2>
  </section>
  <section class="section">
    <h2>Contact me</h2>
  </section>
</body>
*
{
margin: 0;
padding: 0;
}

html,
body
{
/* overflow: hidden; */
background-color: #363636;;
}

.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
}


.section
{
display: flex;
align-items: center;
height: 100vh;
position: relative;
font-family: 'Cabin', sans-serif;
color: #ffeded;
text-transform: uppercase;
font-size: 7vmin;

padding-left: 10%;
padding-right: 10%;
}

section:nth-child(odd)
{
    justify-content: flex-end;
}

[2]固定弹性卷轴

在某些浏览器环境中(这里chorome是不会的),您可能会注意到,如果滚动距离太远,当页面超出限制时,您会看到一种弹性动画。
虽然这是一个很酷的功能,但默认情况下,页面背面是白色的,与我们的体验不符。
为了解决这个问题,我们可以将页面的颜色设置background-color为与 的颜色clearColor相同renderer。相反,我们将使其透明并仅在页面上clearColor设置。background-color

const renderer = new THREE.WebGLRenderer({
canvas: canvas,
alpha: true
})

[3]在Three画布中添加多个对象

为了实现鼠标滚动动画效果,通常需要添加多个三维对象

/**
* Objects
*/
// Texture---这里添加了一些渐变纹理
const textureLoader = new THREE.TextureLoader()
const gradientTexture = textureLoader.load('textures/gradients/3.jpg')
gradientTexture.magFilter = THREE.NearestFilter
// Material
const material = new THREE.MeshToonMaterial({
    color: parameters.materialColor,
    gradientMap: gradientTexture
})

// Meshes
const mesh1 = new THREE.Mesh(
    new THREE.TorusGeometry(1, 0.4, 16, 60),
    material
)
const mesh2 = new THREE.Mesh(
    new THREE.ConeGeometry(1, 2, 32),
    material
)
const mesh3 = new THREE.Mesh(
    new THREE.TorusKnotGeometry(0.8, 0.35, 100, 16),
    material
)

这里需要根据自己的喜好添加不同的对象和材质,同时也别忘了添加一些光照效果:

/**
* Lights
*/
const directionalLight = new THREE.DirectionalLight('#ffffff', 1)
directionalLight.position.set(1, 1, 0)
scene.add(directionalLight)

然后就是修改这些三维对象的位置了,为了达到滚动滚动条展示不同的三维物体,需要将他们的y坐标值取一定的距离:

const objectsDistance = 2  //物体之间的距离
mesh1.position.y = - objectsDistance * 0
mesh2.position.y = - objectsDistance * 1
mesh3.position.y = - objectsDistance * 2

然后就是相机设定相机随滚动条滚动而滚动了

/**
* Scroll
*/
let scrollY = window.scrollY //检索滚动值
//监听滚动条的滚动值
window.addEventListener('scroll', () =>
{
scrollY = window.scrollY

console.log(scrollY)
})

然后在tick函数中,用于scrollY使相机移动(在进行渲染之前):

const tick = () =>
{
// ...

// Animate camera
camera.position.y = - scrollY / sizes.height * objectsDistance

// ...
}

后面也可以根据需要对对象的水平位置进行修改:

mesh1.position.x = 2
mesh2.position.x = - 2
mesh3.position.x = 2

[4]视差Parallax

为了让我们的体验更加身临其境,我们将通过使相机根据鼠标移动水平和垂直移动来应用这种视差效果。它将创建自然的交互,并帮助用户感受到深度。

光标

首先,我们需要检索光标位置。
为此,请创建一个具有和属性的cursor对象:xy

/**
* Cursor
*/
const cursor = {}
cursor.x = 0
cursor.y = 0

然后,监听mousemove事件window并更新这些值:

window.addEventListener('mousemove', (event) =>
{
cursor.x = event.clientX / sizes.width - 0.5
cursor.y = event.clientY / sizes.height - 0.5

console.log(cursor)
})

然后就可以在函数tick中使用了

const tick = () =>
{
// ...

// Animate camera
camera.position.y = - scrollY / sizes.height * objectsDistance

const parallaxX = cursor.x
const parallaxY = - cursor.y
camera.position.x = parallaxX
camera.position.y = parallaxY

// ...
}

但是这时就又有一个问题了,就是这里设定了后,相机的滚动就不起作用了,因为问我们更新了camera.position.y两次,第二次将取代第一次
为了解决这个问题,我们将把相机放在一个组three.js docs中,并对组而不是相机本身应用视差。

/**
* Camera
*/
// Group
const cameraGroup = new THREE.Group()
scene.add(cameraGroup)

// Base camera
const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 100)
camera.position.z = 6
cameraGroup.add(camera)

在该tick函数中,不是在相机上应用视差,而是将其应用在cameraGroup:

[5]额外效果

上面的基本是已经有一定的效果了,而如果追求更炫酷的效果,则需要之际补充添加,原理可参考上面的,比如粒子、触发旋转、网格动画、对材质等其他属性进行动画处理、对 HTML 文本进行动画处理

import './style.css'
import * as THREE from 'three'
import * as dat from 'lil-gui'
import gsap from 'gsap'

/**
 * Debug
 */
const gui = new dat.GUI()

const parameters = {
  materialColor: '#ffeded',
  materialColor:"#ffeded"
}

/**
 * gui
 */
gui
  .addColor(parameters, 'materialColor')
  .onChange(() =>
    {
      material.color.set(parameters.materialColor)
      particlesMaterial.color.set(parameters.materialColor)
    })

/**
 * Base
 */
// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

/**
 * Test cube
 */
/**
* Objects
*/

// Texture
const textureLoader = new THREE.TextureLoader()
const gradientTexture = textureLoader.load('textures/gradients/3.jpg')
gradientTexture.magFilter = THREE.NearestFilter
// Material
const material = new THREE.MeshToonMaterial({
  color: parameters.materialColor,
  gradientMap: gradientTexture
})

// Meshes
const mesh1 = new THREE.Mesh(
  new THREE.TorusGeometry(1, 0.4, 16, 60),
  material
)
const mesh2 = new THREE.Mesh(
  new THREE.ConeGeometry(1, 2, 32),
  material
)
const mesh3 = new THREE.Mesh(
  new THREE.TorusKnotGeometry(0.8, 0.35, 100, 16),
  material
)

const objectsDistance = 4
mesh1.position.y = - objectsDistance * 0
mesh2.position.y = - objectsDistance * 1
mesh3.position.y = - objectsDistance * 2
const sectionMeshes = [ mesh1, mesh2, mesh3 ]

mesh1.position.x = 2
mesh2.position.x = - 2
mesh3.position.x = 2
scene.add(mesh1, mesh2, mesh3)


/**
* Particles
*/
// Geometry
const particlesCount = 200
const positions = new Float32Array(particlesCount * 3)
//构建随机position
for(let i = 0; i < particlesCount; i++)
{
  positions[i * 3 + 0] = (Math.random() - 0.5) * 10
  positions[i * 3 + 1] = objectsDistance * 0.5 - Math.random() * objectsDistance * sectionMeshes.length
  positions[i * 3 + 2] = (Math.random() - 0.5) * 10
}

const particlesGeometry = new THREE.BufferGeometry()
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))

// Material
const particlesMaterial = new THREE.PointsMaterial({
  color: parameters.materialColor,
  sizeAttenuation: true,
  size: 0.03
})

// Points
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)


/**
* Scroll
*/
let scrollY = window.scrollY
let currentSection = 0 //用于记录触发旋转
window.addEventListener('scroll', () =>
  {
    scrollY = window.scrollY
    const newSection = Math.round(scrollY / sizes.height)

    if(newSection != currentSection)
    {
      currentSection = newSection
      gsap.to(
        sectionMeshes[currentSection].rotation,
        {
          duration: 1.5,
          ease: 'power2.inOut',
          x: '+=6',
          y: '+=3',
          z: '+=1.5'
        }
      )
      console.log('changed', currentSection)
    }

  })


/**
* Cursor
*/
const cursor = {}
cursor.x = 0
cursor.y = 0
window.addEventListener('mousemove', (event) =>
  {
    cursor.x = event.clientX / sizes.width - 0.5
    cursor.y = event.clientY / sizes.height - 0.5

      })

      /**
      * Lights
      */
      const directionalLight = new THREE.DirectionalLight('#ffffff', 1)
      directionalLight.position.set(1, 1, 0)
      scene.add(directionalLight)

      /**
      * Sizes
      */
      const sizes = {
      width: window.innerWidth,
      height: window.innerHeight
      }



      window.addEventListener('resize', () =>
    {
      // Update sizes
      sizes.width = window.innerWidth
      sizes.height = window.innerHeight

      // Update camera
      camera.aspect = sizes.width / sizes.height
      camera.updateProjectionMatrix()

      // Update renderer
      renderer.setSize(sizes.width, sizes.height)
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
      })

      /**
      * Camera
      */

      // Group
      const cameraGroup = new THREE.Group()
      scene.add(cameraGroup)
      // Base camera
      const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 100)
      camera.position.z = 6
      cameraGroup.add(camera)

      /**
      * Renderer
      */
      const renderer = new THREE.WebGLRenderer({
      canvas: canvas,
      alpha: true
      })
      renderer.setSize(sizes.width, sizes.height)
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

      /**
      * Animate
      */
      const clock = new THREE.Clock()
      let previousTime = 0

      const tick = () =>
    {
      const elapsedTime = clock.getElapsedTime()
      const deltaTime = elapsedTime - previousTime
      previousTime = elapsedTime
      // Animate meshes
      for(const mesh of sectionMeshes)
      {
      mesh.rotation.x += deltaTime * 0.1
      mesh.rotation.y += deltaTime * 0.12
      }
      // Animate camera
      camera.position.y = - scrollY / sizes.height * objectsDistance

      const parallaxX = cursor.x * 0.8
      const parallaxY = - cursor.y * 0.8

      cameraGroup.position.x += (parallaxX - cameraGroup.position.x) * 5 * deltaTime
      cameraGroup.position.y += (parallaxY - cameraGroup.position.y) * 5 * deltaTime

      // Render
      renderer.render(scene, camera)

      // Call tick again on the next frame
      window.requestAnimationFrame(tick)
      }

      tick()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

seeooco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值