안녕하세요 오늘은 WebGL 기반 3D 그래픽 라이브러리인 three 를 사용해 3D 효과를 구현해보도록 하겠습니다.
먼저 three 는 WebGL 기반의 3D 그래픽 라이브러리 입니다.
브라우저에서 3D 그래픽을 구현할 수 있도록 도와주는 자바스크립트 라이브러리로,
기본적으로 WebGL API를 추상화하여 훨씬 쉽게 3D 씬, 모델, 애니메이션 등을 구현할 수 있게 해줍니다.
Thress.js 를 리액트 환경에서 사용하기 위해서는 React 랜더러인 "React Three Fiber" 를 설치해주어야 합니다.
react-three-fiber 는 React의 컴포넌트 기반 철학을 유지하면서 3D 그래픽을 구성할 수 있도록 해주는 React 렌더러입니다.
1. react-three-fiber 설치하기
아래 사이트를 확인해 본인의 개발환경에 맞는 install 설치 명령어를 확인합니다.
Installation - React Three Fiber
Learn how to install react-three-fiber
r3f.docs.pmnd.rs
현재 리액트 vite 프로젝트이므로 아래 명령어를 사용하겠습니다.
cd my-app #my-app 에는 본인의 react 프로젝트명을 입력합니다. (프로젝트 루트 경로)
npm install three @react-three/fiber
2. 이외에 필요한 라이브러리 설치
npm install three @react-three/fiber @react-three/drei
three: 핵심 3D 라이브러리
@react-three/fiber: React 렌더러
@react-three/drei: 유용한 헬퍼 컴포넌트들 (OrbitControls, Text 등)
3. 기존 프로젝트에 3d 효과를 추가합니다.
기존 프로젝트는 아래 링크에서 확인하실 수 있습니다.
https://stickode.tistory.com/1562
[React] react-scroll을 활용한 부드러운 앵커 스크롤 (Anchor Scroll) 구현하기
안녕하세요 오늘은 React 환경에서 react-scroll 라이브러리를 활용해 클릭 시 부드럽게 섹션으로 이동하는 스크롤 내비게이션을 만들어보겠습니다. 1. 먼저 생성돼 있는 react 프로젝트로 이동합니다
stickode.tistory.com
추가된 파일 내용입니다.
1) ThreeDScene.jsx
import React from 'react'
import { Canvas, useFrame, useThree } from '@react-three/fiber'
import { OrbitControls, Text, Box, Sphere, Cylinder } from '@react-three/drei'
import { useRef, useState } from 'react'
import * as THREE from 'three'
// 회전하는 카드 컴포넌트
function RotatingCard({ position, color, text, speed = 1 }) {
const meshRef = useRef()
const [hovered, setHover] = useState(false)
const [clicked, setClicked] = useState(false)
useFrame((state) => {
if (meshRef.current) {
meshRef.current.rotation.y += 0.01 * speed
meshRef.current.rotation.x = Math.sin(state.clock.elapsedTime * 0.5) * 0.1
}
})
return (
<group position={position}>
<Box
ref={meshRef}
args={[2, 3, 0.1]}
onClick={() => setClicked(!clicked)}
onPointerOver={() => setHover(true)}
onPointerOut={() => setHover(false)}
scale={clicked ? 1.2 : hovered ? 1.1 : 1}
>
<meshStandardMaterial
color={hovered ? '#ff6b6b' : color}
metalness={0.3}
roughness={0.4}
/>
</Box>
<Text
position={[0, 0, 0.06]}
fontSize={0.3}
color="white"
anchorX="center"
anchorY="middle"
font="/fonts/Inter-Bold.woff"
>
{text}
</Text>
</group>
)
}
// 떠다니는 구체들
function FloatingSpheres() {
const spheres = useRef([])
useFrame((state) => {
spheres.current.forEach((sphere, i) => {
if (sphere) {
sphere.position.y = Math.sin(state.clock.elapsedTime + i) * 2 + 5
sphere.rotation.x += 0.01
sphere.rotation.y += 0.01
}
})
})
return (
<>
{[...Array(5)].map((_, i) => (
<Sphere
key={i}
ref={(el) => (spheres.current[i] = el)}
args={[0.3, 16, 16]}
position={[
(i - 2) * 4,
Math.sin(i) * 2 + 5,
Math.cos(i) * 2
]}
>
<meshStandardMaterial
color={`hsl(${i * 60}, 70%, 60%)`}
transparent
opacity={0.8}
/>
</Sphere>
))}
</>
)
}
// 배경 원통들
function BackgroundCylinders() {
const cylinders = useRef([])
useFrame((state) => {
cylinders.current.forEach((cylinder, i) => {
if (cylinder) {
cylinder.rotation.y += 0.005 * (i + 1)
cylinder.position.y = Math.sin(state.clock.elapsedTime * 0.5 + i) * 0.5
}
})
})
return (
<>
{[...Array(3)].map((_, i) => (
<Cylinder
key={i}
ref={(el) => (cylinders.current[i] = el)}
args={[0.5, 0.5, 8, 8]}
position={[
(i - 1) * 8,
0,
-10
]}
>
<meshStandardMaterial
color={`hsl(${i * 120}, 50%, 40%)`}
transparent
opacity={0.3}
wireframe
/>
</Cylinder>
))}
</>
)
}
// 메인 3D 씬 컴포넌트
function ThreeDScene() {
const { camera } = useThree()
// 카메라 초기 위치 설정
React.useEffect(() => {
camera.position.set(0, 5, 10)
camera.lookAt(0, 0, 0)
}, [camera])
return (
<div className="w-full h-screen">
<Canvas
camera={{ position: [0, 5, 10], fov: 75 }}
style={{ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }}
>
{/* 조명 */}
<ambientLight intensity={0.4} />
<pointLight position={[10, 10, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} intensity={0.5} color="#ff6b6b" />
{/* 카드들 */}
<RotatingCard
position={[-4, 0, 0]}
color="#4ecdc4"
text="React"
speed={1}
/>
<RotatingCard
position={[0, 0, 0]}
color="#45b7d1"
text="Three.js"
speed={1.2}
/>
<RotatingCard
position={[4, 0, 0]}
color="#96ceb4"
text="WebGL"
speed={0.8}
/>
{/* 떠다니는 구체들 */}
<FloatingSpheres />
{/* 배경 원통들 */}
<BackgroundCylinders />
{/* 컨트롤 */}
<OrbitControls
enablePan={true}
enableZoom={true}
enableRotate={true}
maxDistance={20}
minDistance={5}
/>
</Canvas>
</div>
)
}
export default ThreeDScene
주요 기능입니다.
- 자동회전, 마우스 호버시 색상 변경, 클릭하면 크기가 커지는 등 3d 형태로 구현된 모습입니다.
- FloatingSpheres 컴포넌트에서는 떠다니는 구체 3개에 대한 회전 방향, 투명도 효과, 움직임 방향등을 설정합니다.
2) ThreeDPortfolio.jsx
import React from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Box, Sphere, Text3D } from '@react-three/drei'
import { useRef, useState } from 'react'
// 회전하는 기술 스택 박스
function TechBox({ position, color, tech, speed = 1 }) {
const meshRef = useRef()
const [hovered, setHover] = useState(false)
const [clicked, setClicked] = useState(false)
useFrame((state) => {
if (meshRef.current) {
meshRef.current.rotation.y += 0.01 * speed
meshRef.current.rotation.x = Math.sin(state.clock.elapsedTime * 0.5) * 0.1
}
})
return (
<Box
ref={meshRef}
args={[1.5, 1.5, 1.5]}
position={position}
onClick={() => setClicked(!clicked)}
onPointerOver={() => setHover(true)}
onPointerOut={() => setHover(false)}
scale={clicked ? 1.3 : hovered ? 1.1 : 1}
>
<meshStandardMaterial
color={hovered ? '#ff6b6b' : color}
metalness={0.5}
roughness={0.2}
transparent
opacity={0.9}
/>
</Box>
)
}
// 떠다니는 구체들
function FloatingSpheres() {
const spheres = useRef([])
useFrame((state) => {
spheres.current.forEach((sphere, i) => {
if (sphere) {
sphere.position.y = Math.sin(state.clock.elapsedTime + i) * 1.5 + 3
sphere.rotation.x += 0.01
sphere.rotation.y += 0.01
}
})
})
return (
<>
{[...Array(3)].map((_, i) => (
<Sphere
key={i}
ref={(el) => (spheres.current[i] = el)}
args={[0.2, 16, 16]}
position={[
(i - 1) * 3,
Math.sin(i) * 1.5 + 3,
Math.cos(i) * 1.5
]}
>
<meshStandardMaterial
color={`hsl(${i * 120}, 70%, 60%)`}
transparent
opacity={0.7}
/>
</Sphere>
))}
</>
)
}
// 3D 씬 컴포넌트
function ThreeDScene() {
return (
<div className="w-full h-96">
<Canvas
camera={{ position: [0, 3, 8], fov: 60 }}
style={{ background: 'transparent' }}
>
{/* 조명 */}
<ambientLight intensity={0.6} />
<pointLight position={[10, 10, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} intensity={0.3} color="#ff6b6b" />
{/* 기술 스택 박스들 */}
<TechBox
position={[-3, 0, 0]}
color="#61dafb"
tech="React"
speed={1}
/>
<TechBox
position={[0, 0, 0]}
color="#f7df1e"
tech="JavaScript"
speed={1.2}
/>
<TechBox
position={[3, 0, 0]}
color="#38bdf8"
tech="Three.js"
speed={0.8}
/>
{/* 떠다니는 구체들 */}
<FloatingSpheres />
{/* 컨트롤 */}
<OrbitControls
enablePan={false}
enableZoom={true}
enableRotate={true}
maxDistance={15}
minDistance={5}
autoRotate
autoRotateSpeed={0.5}
/>
</Canvas>
</div>
)
}
// 메인 포트폴리오 섹션
function ThreeDPortfolio() {
return (
<section className="min-h-screen flex flex-col justify-center items-center bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900 py-20 px-6">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-12">
<h2 className="text-5xl font-bold text-white mb-6">
3D Interactive Portfolio
</h2>
<p className="text-xl text-gray-300 max-w-3xl mx-auto leading-relaxed">
Explore my skills and projects in an immersive 3D environment.
Click and interact with the elements to discover more about my work.
</p>
</div>
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* 3D 씬 */}
<div className="bg-black/20 rounded-2xl p-8 backdrop-blur-sm border border-white/10">
<ThreeDScene />
</div>
{/* 텍스트 내용 */}
<div className="space-y-6">
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
<h3 className="text-2xl font-bold text-white mb-4">Frontend Development</h3>
<p className="text-gray-300 leading-relaxed">
Specialized in React, Vue.js, and modern JavaScript frameworks.
Creating responsive and interactive user interfaces with cutting-edge technologies.
</p>
</div>
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
<h3 className="text-2xl font-bold text-white mb-4">3D & WebGL</h3>
<p className="text-gray-300 leading-relaxed">
Experienced with Three.js and React Three Fiber for creating
immersive 3D web experiences and interactive visualizations.
</p>
</div>
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
<h3 className="text-2xl font-bold text-white mb-4">Full-Stack Solutions</h3>
<p className="text-gray-300 leading-relaxed">
Building complete web applications from database design to
deployment, with expertise in Node.js, Python, and cloud platforms.
</p>
</div>
</div>
</div>
{/* 인터랙션 가이드 */}
<div className="text-center mt-12">
<div className="inline-flex items-center space-x-4 bg-white/10 backdrop-blur-sm rounded-full px-6 py-3 border border-white/20">
<span className="text-white text-sm">💡</span>
<span className="text-gray-300 text-sm">
Click on the 3D elements to interact • Drag to rotate • Scroll to zoom
</span>
</div>
</div>
</div>
</section>
)
}
export default ThreeDPortfolio
3) App.jsx
import Header from "./components/Header";
import Hero from "./components/Hero";
import About from "./components/About";
import Contact from "./components/Contact";
import ThreeDPortfolio from "./components/ThreeDPortfolio";
function App() {
return (
<>
<Header />
<Hero />
<About />
<ThreeDPortfolio />
<Contact />
</>
);
}
export default App;
시연 영상 입니다.
마우스의 움직임에 따라 도형들이 움직이는 것을 확인할 수 있습니다.
'React' 카테고리의 다른 글
| [React] Tailwind CSS 라이브러리 사용해 페이지네이션 구현하기 (3) | 2025.07.24 |
|---|---|
| [React] Motion 라이브러리 사용하여 스크롤 진행바 (scrollYProgress 사용) 애니메이션 구현하기 (6) | 2025.07.17 |
| [React] react-scroll을 활용한 부드러운 앵커 스크롤 (Anchor Scroll) 구현하기 (0) | 2025.07.14 |
| [React] 텍스트 입력 시 자동으로 필드 추가하기 (Material UI 활용) (2) | 2025.07.12 |
| [React] Hook 함수형 컴포넌트 (2) | 2025.06.30 |