아래의 효과를 background에 넣어보려한다.
점들이 파도처럼 꿀렁이는? 효과이다.
Three.js 배경 적용
위에 언급한 효과는 스크립트가 굉장해엄청나 복잡하므로 ,,,(용량 장난없음;)
아래의 코드펜 예제를 참고하였다
https://codepen.io/mweslander/pen/JreNPj
똑같은 효과인데 커스텀에 따라 느낌이 확 달라지므로
이쁘게 커스텀해보자~
HTML 구조
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three.js 배경 테스트</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"
/>
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<canvas id="background"></canvas>
<header id="header">
<div class="logo_area">
<h1>Three.js</h1>
</div>
<div class="github_area">
<a href="https://github.com/YeoDaSeul4355" target="_blank"
><i class="fa-brands fa-github-alt"></i
></a>
</div>
</header>
<main id="main">
<section id="content01">
<h2>section <span>one</span></h2>
</section>
<section id="content02">
<h2>section <span>two</span></h2>
</section>
<section id="content03">
<h2>section <span>three</span></h2>
</section>
<section id="content04">
<h2>section <span>four</span></h2>
</section>
<section id="content05">
<h2>section <span>five</span></h2>
</section>
</main>
<!-- fontAwesome -->
<script
src="https://kit.fontawesome.com/f8a51b75db.js"
crossorigin="anonymous"
></script>
<!-- 필요한 cdn -->
<script src="https://unpkg.com/@studio-freight/lenis@1.0.42/dist/lenis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
<script src="https://codepen.io/mweslander/pen/OxamJj.js"></script>
<script src="https://codepen.io/mweslander/pen/LzXWOo.js"></script>
<script src="https://codepen.io/mweslander/pen/JreWMV.js"></script>
<!-- 스크립트 -->
<script src="./js/script.js"></script>
</body>
</html>
구조는 간단히 섹션만 나눠서 스크롤 느낌이 나게끔 설정하였다.
CSS
@import url("https://fonts.googleapis.com/css2?family=Shrikhand&display=swap");
body {
margin: 0;
overflow-x: hidden;
font-family: "Shrikhand", serif;
}
canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
section {
width: 100%;
height: 100vh;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
header {
width: 90vw;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.github_area i {
font-size: 3rem;
color: #000;
transition: all 0.3s ease-in-out;
}
.github_area i:hover {
color: #ff84a9;
transition: all 0.3s ease-in-out;
}
section h2 {
margin-bottom: 10%;
font-size: 3rem;
color: #222;
text-transform: uppercase;
}
section h2 span {
color: #ccc;
}
나머지 스타일링은 별 거 없고 canvas태그만 position: fixed를 통해 고정시켜준다. 그리고 배경으로 깔아줄 것이기 때문에 z-index를 -1로 설정해 뒤에 깔리게끔 설정시켜주기!
스크립트
const lenis = new Lenis({
duration: 2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
});
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
// particle 입자와 간격, 수
const SEPARATION = 100,
AMOUNTX = 70,
AMOUNTY = 70;
// Three.js의 카메라, 씬, 렌더러
let camera, scene, renderer;
// 입자들을 저장할 배열과 각각의 입자, 애니메이션 카운터
let particles,
particle,
count = 0;
// 마우스의 현재 위치
let mouseX = 0,
mouseY = 0;
// 브라우저 창의 가로와 세로 크기의 / 2
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
// 초기화 함수로 카메라, 입자들, 렌더러 등을 생성하고 설정
function init() {
camera = new THREE.PerspectiveCamera(
100, // 시야각
window.innerWidth / window.innerHeight, // 종횡비
2, // 전방 절단면
10000 // 후방 절단면
);
camera.position.z = 2000; // z축 방향으로 2000의 거리
scene = new THREE.Scene();
// 입자들을 저장할 배열
particles = new Array();
var PI2 = Math.PI * 2; // 원주율의 두 배를 계산하여 PI2에 저장
var geometry = new THREE.Geometry(); // Three.js의 Geometry 객체를 생성
// 입자의 머티리얼을 설정
var material = new THREE.SpriteCanvasMaterial({
color: "#FF7384", // 입자 색상
program: function (context) {
context.beginPath();
context.arc(0, 0, 0.2, 0, PI2, true); // 입자 크기
context.fill();
},
});
// 반복문
var i = 0;
for (var ix = 0; ix < AMOUNTX; ix++) {
for (var iy = 0; iy < AMOUNTY; iy++) {
particle = particles[i++] = new THREE.Sprite(material);
particle.position.x = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2;
particle.position.z = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2;
scene.add(particle);
if (i > 0) {
geometry.vertices.push(particle.position);
}
}
}
// Three.js의 CanvasRenderer를 생성
renderer = new THREE.CanvasRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
document.addEventListener("mousemove", onDocumentMouseMove, false);
document.addEventListener("touchstart", onDocumentTouchStart, false);
document.addEventListener("touchmove", onDocumentTouchMove, false);
window.addEventListener("resize", onWindowResize, false);
// 스크롤 이벤트 리스너 추가
window.addEventListener("scroll", function () {
// 스크롤 위치 가져오기
let scrollPosition = window.scrollY || window.pageYOffset;
let content03 = document.querySelector("#content03").offsetTop;
let content05 = document.querySelector("#content05").offsetTop;
if (scrollPosition > content03 - 700 && scrollPosition < content05 - 500) {
material.color.set("#ffffff");
renderer.render(scene, camera);
} else {
material.color.set("#FF7384");
renderer.render(scene, camera);
}
});
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
}
function onDocumentTouchStart(event) {
if (event.touches.length === 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - windowHalfX;
mouseY = event.touches[0].pageY - windowHalfY;
}
}
function onDocumentTouchMove(event) {
if (event.touches.length === 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - windowHalfX;
mouseY = event.touches[0].pageY - windowHalfY;
}
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
// 현재 색상
let currentColor = new THREE.Color(renderer.getClearColor());
// 목표 색상
let targetColor = new THREE.Color();
let scrollPosition = window.scrollY || window.pageYOffset;
let content03 = document.querySelector("#content03").offsetTop;
let content05 = document.querySelector("#content05").offsetTop;
// 조건문
if (scrollPosition > content03 - 700 && scrollPosition < content05 - 500) {
targetColor.set("#000000");
} else {
targetColor.set("#ffffff");
}
// 색상 보간을 위한 보간 계수
let interpolationFactor = 0.1;
// 보간된 색상 계산
let newColor = new THREE.Color()
.copy(currentColor)
.lerp(targetColor, interpolationFactor);
// 부드럽게 변경된 색상 설정
renderer.setClearColor(newColor, 1);
// 나머지 렌더링 코드는 그대로 유지됨
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
for (let i = 0; i < particles.length; i++) {
let ix = i % AMOUNTX;
let iy = Math.floor(i / AMOUNTX);
let particle = particles[i];
particle.position.y =
Math.sin((ix + count) * 0.2) * 50 + Math.sin((iy + count) * 0.2) * 50;
particle.scale.x = particle.scale.y =
(Math.sin((ix + count) * 0.3) + 1) * 4 +
(Math.sin((iy + count) * 0.5) + 1) * 4;
}
renderer.render(scene, camera);
count += 0.1;
}
init();
animate();
lenis부분은 부드러운 스크롤 효과를 위해 넣은 것이므로 참고!
https://duektmf34.tistory.com/207
1. Three.js 객체 및 변수 초기화
const SEPARATION = 100,
AMOUNTX = 70,
AMOUNTY = 70;
// Three.js의 카메라, 씬, 렌더러
let camera, scene, renderer;
// 입자들을 저장할 배열과 각각의 입자, 애니메이션 카운터
let particles,
particle,
count = 0;
// 마우스의 현재 위치
let mouseX = 0,
mouseY = 0;
// 브라우저 창의 가로와 세로 크기의 / 2
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
- 상수 선언:
- const SEPARATION = 100: 입자 사이의 간격을 나타내는 상수입니다. 입자 사이의 간격이 100인 것으로 정의됩니다.
- const AMOUNTX = 70: x축 방향으로 입자의 수를 나타내는 상수입니다. 즉, x축에 70개의 입자가 배치됩니다.
- const AMOUNTY = 70: y축 방향으로 입자의 수를 나타내는 상수입니다. 즉, y축에 70개의 입자가 배치됩니다.
- Three.js 객체 및 변수 초기화:
- let camera, scene, renderer;: Three.js에서 사용할 카메라, 씬, 렌더러를 선언합니다.
- let particles, particle, count = 0;: 입자들을 저장할 배열과 각각의 입자, 그리고 애니메이션 카운터 변수를 선언합니다.
- let mouseX = 0, mouseY = 0;: 마우스의 현재 위치를 나타내는 변수를 선언합니다.
- let windowHalfX = window.innerWidth / 2;: 브라우저 창의 가로 크기를 절반으로 나눈 값으로, 브라우저 창의 중심점 x좌표를 나타냅니다.
- let windowHalfY = window.innerHeight / 2;: 브라우저 창의 세로 크기를 절반으로 나눈 값으로, 브라우저 창의 중심점 y좌표를 나타냅니다.
- 변수 초기화 설명:
- camera, scene, renderer는 각각 Three.js의 카메라, 씬, 렌더러를 나타내는 변수입니다.
- particles는 입자들의 정보를 저장하는 배열입니다.
- particle은 현재 처리 중인 입자를 나타내는 변수입니다.
- count는 애니메이션 카운터로, 애니메이션을 제어하는 데 사용됩니다.
- mouseX, mouseY는 마우스의 현재 위치를 저장하는 변수입니다.
- windowHalfX, windowHalfY는 브라우저 창의 중심점을 나타내는 변수입니다. 이 값은 마우스 이벤트에서 사용되어 마우스의 위치를 화면 중심을 기준으로 계산하는 데 활용됩니다.
2. init 함수
function init() {
camera = new THREE.PerspectiveCamera(
100, // 시야각
window.innerWidth / window.innerHeight, // 종횡비
2, // 전방 절단면
10000 // 후방 절단면
);
camera.position.z = 2000; // z축 방향으로 2000의 거리
scene = new THREE.Scene();
// 입자들을 저장할 배열
particles = new Array();
var PI2 = Math.PI * 2; // 원주율의 두 배를 계산하여 PI2에 저장
var geometry = new THREE.Geometry(); // Three.js의 Geometry 객체를 생성
// 입자의 머티리얼을 설정
var material = new THREE.SpriteCanvasMaterial({
color: "#FF7384", // 입자 색상
program: function (context) {
context.beginPath();
context.arc(0, 0, 0.2, 0, PI2, true); // 입자 크기
context.fill();
},
});
// 반복문
var i = 0;
for (var ix = 0; ix < AMOUNTX; ix++) {
for (var iy = 0; iy < AMOUNTY; iy++) {
particle = particles[i++] = new THREE.Sprite(material);
particle.position.x = ix * SEPARATION - (AMOUNTX * SEPARATION) / 2;
particle.position.z = iy * SEPARATION - (AMOUNTY * SEPARATION) / 2;
scene.add(particle);
if (i > 0) {
geometry.vertices.push(particle.position);
}
}
}
// Three.js의 CanvasRenderer를 생성
renderer = new THREE.CanvasRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
document.addEventListener("mousemove", onDocumentMouseMove, false);
document.addEventListener("touchstart", onDocumentTouchStart, false);
document.addEventListener("touchmove", onDocumentTouchMove, false);
window.addEventListener("resize", onWindowResize, false);
// 스크롤 이벤트 리스너 추가
window.addEventListener("scroll", function () {
// 스크롤 위치 가져오기
let scrollPosition = window.scrollY || window.pageYOffset;
let content03 = document.querySelector("#content03").offsetTop;
let content05 = document.querySelector("#content05").offsetTop;
if (scrollPosition > content03 - 700 && scrollPosition < content05 - 500) {
material.color.set("#ffffff");
renderer.render(scene, camera);
} else {
material.color.set("#FF7384");
renderer.render(scene, camera);
}
});
}
- 카메라 설정:
- PerspectiveCamera를 생성하여 시야각, 종횡비, 전방 및 후방 절단면을 설정합니다.
- 카메라의 위치를 z축 방향으로 2000의 거리에 위치시킵니다.
- Three.js의 Scene 객체 생성:
- THREE.Scene()을 호출하여 새로운 Scene 객체를 생성합니다.
- 입자들 생성:
- 입자들을 저장할 빈 배열 particles을 생성합니다.
- PI2 변수에는 원주율의 두 배를 저장합니다.
- 입자의 형태와 색상을 결정하는 SpriteCanvasMaterial을 생성합니다. 여기서는 원 모양을 그리고 입자의 색상을 지정합니다.
- 이중 반복문을 통해 AMOUNTX 및 AMOUNTY에 따라 입자들을 생성하고 scene에 추가합니다. 입자들은 SEPARATION 값에 따라 간격을 두고 배치됩니다.
- Three.js의 CanvasRenderer 생성:
- THREE.CanvasRenderer()를 호출하여 새로운 CanvasRenderer 객체를 생성합니다.
- 렌더러의 픽셀 비율을 설정하고, 화면 크기를 브라우저 창의 크기로 조정합니다.
- 렌더러의 DOM 요소를 document body에 추가합니다.
- 이벤트 리스너 등록:
- 마우스 및 터치 이벤트에 대한 이벤트 리스너를 추가합니다. 마우스 이벤트가 발생하면 onDocumentMouseMove 함수가 호출됩니다.
- 창 크기 변경 이벤트에 대한 이벤트 리스너를 추가합니다. 창 크기가 변경되면 onWindowResize 함수가 호출됩니다.
- 스크롤 이벤트 리스너를 추가하여 페이지 스크롤에 따라 입자 색상을 변경하고, 변경된 내용을 렌더러를 통해 화면에 다시 렌더링합니다.
여기서 scroll을 감지해 특정 섹션에서는 particle의 색상을 바꾸도록 설정했다.
3. 이벤트를 처리하는 함수 (창 크기 변경, 마우스 이동, 터치 시작, 터치 이동 )
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
}
function onDocumentTouchStart(event) {
if (event.touches.length === 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - windowHalfX;
mouseY = event.touches[0].pageY - windowHalfY;
}
}
function onDocumentTouchMove(event) {
if (event.touches.length === 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - windowHalfX;
mouseY = event.touches[0].pageY - windowHalfY;
}
}
- onWindowResize() 함수:
- 브라우저 창 크기가 변경될 때 호출되는 함수입니다.
- windowHalfX와 windowHalfY를 현재 브라우저 창의 가로와 세로 크기의 절반으로 업데이트합니다.
- 카메라의 종횡비(aspect)를 새로운 창 크기에 맞게 업데이트합니다.
- 카메라의 종횡비가 변경되었으므로, 카메라의 투영 행렬을 업데이트합니다.
- 렌더러의 크기를 새로운 창 크기에 맞게 조정합니다.
- onDocumentMouseMove(event) 함수:
- 마우스가 움직일 때 호출되는 함수입니다.
- 이벤트에서 마우스의 현재 위치를 가져와서 브라우저 창의 중심으로부터의 거리를 계산합니다.
- 이 값을 mouseX와 mouseY 변수에 저장하여 입자들의 위치를 조정하는 데 사용됩니다.
- onDocumentTouchStart(event) 함수:
- 터치가 시작될 때 호출되는 함수입니다.
- 터치가 한 개일 경우에만 이벤트를 처리합니다.
- 터치 이벤트에서 첫 번째 터치의 위치를 가져와서 마우스 이동과 동일하게 처리합니다.
- onDocumentTouchMove(event) 함수:
- 터치가 이동할 때 호출되는 함수입니다.
- 터치가 한 개일 경우에만 이벤트를 처리합니다.
- 터치 이벤트에서 첫 번째 터치의 위치를 가져와서 마우스 이동과 동일하게 처리합니다.
4. render 함수
function render() {
// 현재 색상
let currentColor = new THREE.Color(renderer.getClearColor());
// 목표 색상
let targetColor = new THREE.Color();
let scrollPosition = window.scrollY || window.pageYOffset;
let content03 = document.querySelector("#content03").offsetTop;
let content05 = document.querySelector("#content05").offsetTop;
// 조건문
if (scrollPosition > content03 - 700 && scrollPosition < content05 - 500) {
targetColor.set("#000000");
} else {
targetColor.set("#ffffff");
}
// 색상 보간을 위한 보간 계수
let interpolationFactor = 0.1;
// 보간된 색상 계산
let newColor = new THREE.Color()
.copy(currentColor)
.lerp(targetColor, interpolationFactor);
// 부드럽게 변경된 색상 설정
renderer.setClearColor(newColor, 1);
// 나머지 렌더링 코드는 그대로 유지됨
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
for (let i = 0; i < particles.length; i++) {
let ix = i % AMOUNTX;
let iy = Math.floor(i / AMOUNTX);
let particle = particles[i];
particle.position.y =
Math.sin((ix + count) * 0.2) * 50 + Math.sin((iy + count) * 0.2) * 50;
particle.scale.x = particle.scale.y =
(Math.sin((ix + count) * 0.3) + 1) * 4 +
(Math.sin((iy + count) * 0.5) + 1) * 4;
}
renderer.render(scene, camera);
count += 0.1;
}
- 현재 배경 색상을 가져와 currentColor 변수에 저장합니다.
- 스크롤 위치를 기반으로 목표 색상을 설정합니다. content03과 content05의 위치에 따라 배경 색상이 변경됩니다.
- 보간 계수를 사용하여 현재 색상에서 목표 색상으로 부드럽게 변경될 새로운 색상을 계산합니다.
- 변경된 배경 색상을 설정합니다.
- 카메라의 위치를 부드럽게 조정하여 마우스의 위치를 따라 이동합니다.
- 입자들의 위치와 크기를 부드럽게 변화시켜 애니메이션 효과를 줍니다.
- 변경된 내용을 렌더러를 통해 화면에 렌더링합니다.
5. animate 함수
function animate() {
requestAnimationFrame(animate);
render();
}
- requestAnimationFrame()을 사용하여 애니메이션을 반복 호출합니다.
- 매 프레임마다 render() 함수를 호출하여 화면을 렌더링합니다.
완성된 페이지
코드 확인하기
https://github.com/YeoDaSeul4355/clone_publishing/tree/main/threejs-background
728x90
반응형