데스크 탑에서 Drag 를 이용하여 3D 도형의 회전을 수행했으나, 터치 스크린에서는 touchstart, touchmove, touchend 이벤트를 추가해야합니다. 이를 추가하여 구현합니다.
// Blocks.jsx
/* eslint-disable */
import React, { useRef, useMemo, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Canvas, useFrame } from "@react-three/fiber";
import { Html } from "@react-three/drei";
import * as THREE from "three";
function createMesh(geometry, material, position, name) {
const mesh = new THREE.Mesh(geometry, material.clone());
mesh.position.set(...position);
mesh.name = name;
mesh.castShadow = true;
mesh.receiveShadow = true;
return mesh;
}
function Box({ geometry, material, position, name }) {
const navigate = useNavigate();
const [color, setColor] = useState(() => new THREE.Color(Math.random(), Math.random(), Math.random()));
const [showTooltip, setShowTooltip] = useState(false);
const mesh = useMemo(
() => createMesh(geometry, material, position, name),
[geometry, material, position, name]
);
const handlePointerOver = (event) => {
document.body.style.cursor = 'pointer';
setShowTooltip(true);
};
const handlePointerOut = (event) => {
document.body.style.cursor = 'default';
setShowTooltip(false);
};
const handleClick = (event) => {
// 랜덤 색상 생성
const newColor = new THREE.Color(Math.random(), Math.random(), Math.random());
setColor(newColor);
navigate(`/${name}`);
};
return (
<mesh
position={position}
castShadow
receiveShadow
onPointerOver={handlePointerOver}
onPointerOut={handlePointerOut}
onClick={handleClick}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial attach="material" color={color} />
{showTooltip && (
<Html position={[0, 1.5, 0]} center>
<div style={{
background: "rgba(0, 0, 0, 0.75)",
color: "white",
padding: "5px",
borderRadius: "5px",
whiteSpace: "nowrap" // 텍스트가 한 줄로 유지되도록 설정
}}>
{name}
</div>
</Html>
)}
</mesh>
);
}
const Blocks = React.memo(function Blocks(props) {
const modelRef = useRef();
const isDraggingRef = useRef(false);
const rotationRef = useRef({ x: 0, y: 0 });
const [scale, setScale] = useState([2.5, 2.5, 2.5]);
// Define geometry and material within Blocks
const boxGeometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
const material = useMemo(() => new THREE.MeshStandardMaterial(), []);
useFrame((state) => {
if (modelRef.current) {
// 드래그 중일 때 회전만 적용
if (isDraggingRef.current) {
modelRef.current.rotation.y += rotationRef.current.y;
modelRef.current.rotation.x += rotationRef.current.x;
rotationRef.current.x = 0;
rotationRef.current.y = 0;
} else {
// 드래그가 아닐 때 y축 위치 애니메이션 적용
modelRef.current.position.y = -1.5 + Math.sin(state.clock.getElapsedTime()) * 0.3;
}
}
});
useEffect(() => {
const handleMouseDown = () => {
isDraggingRef.current = true;
};
const handleMouseUp = () => {
isDraggingRef.current = false;
};
const handleMouseMove = (event) => {
if (isDraggingRef.current) {
rotationRef.current = {
x: event.movementY * 0.005,
y: event.movementX * 0.005,
};
}
};
const handleTouchStart = () => {
isDraggingRef.current = true;
};
const handleTouchEnd = () => {
isDraggingRef.current = false;
};
const handleTouchMove = (event) => {
if (isDraggingRef.current && event.touches.length === 1) {
const touch = event.touches[0];
rotationRef.current = {
x: touch.movementY * 0.005,
y: touch.movementX * 0.005,
};
}
};
window.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mouseup", handleMouseUp);
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("touchstart", handleTouchStart);
window.addEventListener("touchend", handleTouchEnd);
window.addEventListener("touchmove", handleTouchMove);
return () => {
window.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("mouseup", handleMouseUp);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("touchstart", handleTouchStart);
window.removeEventListener("touchend", handleTouchEnd);
window.removeEventListener("touchmove", handleTouchMove);
};
}, []);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 768) {
setScale([1, 1, 1]);
} else {
setScale([2.5, 2.5, 2.5]);
}
};
window.addEventListener("resize", handleResize);
// Initial check
handleResize();
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return (
<group
{...props}
dispose={null}
ref={modelRef}
position={[0, 0, 0]}
scale={scale}
>
<Box
geometry={boxGeometry}
material={material}
position={[1.5, 0, 0]}
name="Box A"
/>
<Box
geometry={boxGeometry}
material={material}
position={[-1.5, 0, 0]}
name="Box B"
/>
<Box
geometry={boxGeometry}
material={material}
position={[0, 1.5, 0]}
name="Box C"
/>
<Box
geometry={boxGeometry}
material={material}
position={[0, -1.5, 0]}
name="Box D"
/>
</group>
);
});
export default Blocks;
사방에서 들어오는 에니메이션을 추가한 코드입니다.
/* eslint-disable */
import React, { useRef, useMemo, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Canvas, useFrame } from "@react-three/fiber";
import { Html } from "@react-three/drei";
import { gsap } from "gsap"; // 애니메이션을 위해 gsap 사용
import * as THREE from "three";
function createMesh(geometry, material, position, name) {
const mesh = new THREE.Mesh(geometry, material.clone());
mesh.position.set(...position);
mesh.name = name;
mesh.castShadow = true;
mesh.receiveShadow = true;
return mesh;
}
// function Box({ geometry, material, position, name }) {
function Box({ geometry, material, position, initialPosition, name }) {
// const navigate = useNavigate();
// Random color generation
const [color, setColor] = useState(
() => new THREE.Color(Math.random(), Math.random(), Math.random())
);
const [showTooltip, setShowTooltip] = useState(false);
// const mesh = useMemo(
// () => createMesh(geometry, material, position, name),
// [geometry, material, position, name]
// );
const mesh = useRef();
useEffect(() => {
const animatePosition = () => {
if (mesh.current) {
gsap.fromTo(mesh.current.position,
{
x: initialPosition[0],
y: initialPosition[1],
z: initialPosition[2]
},
{
x: position[0],
y: position[1],
z: position[2],
duration:1.5,
ease: "power2.inOut"
}
);
} else {
requestAnimationFrame(animatePosition);
}
};
requestAnimationFrame(animatePosition);
}, [position, initialPosition]);
const handlePointerOver = (event) => {
document.body.style.cursor = "pointer";
setShowTooltip(true);
};
const handlePointerOut = (event) => {
document.body.style.cursor = "default";
setShowTooltip(false);
};
const handleClick = (event) => {
const newColor = new THREE.Color(
Math.random(),
Math.random(),
Math.random()
);
setColor(newColor);
// navigate(`/${name}`)
console.log("Clicked :", `/${name}`);
};
return (
<mesh
ref={mesh}
position={initialPosition}
castShadow
receiveShadow
onPointerOver={handlePointerOver}
onPointerOut={handlePointerOut}
onClick={handleClick}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial attach="material" color={color} />
{showTooltip && (
<Html position={[0.5, 0, 0]} center>
<div
style={{
background: "rgba(0, 0, 0, 0.70)",
color: "white",
padding: "5px",
borderRadius: "5px",
whiteSpace: "nowrap", // 텍스트가 한 줄로 유지되도록 설정
}}
>
{name}
</div>
</Html>
)}
</mesh>
);
}
const Blocks = React.memo(function Blocks(props) {
const modelRef = useRef();
const isDraggingRef = useRef(false);
const rotationRef = useRef({ x: 0, y: 0 });
const [scale, setScale] = useState([2.5, 2.5, 2.5]);
// Define geometry and material within Blocks
const boxGeometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
const material = useMemo(() => new THREE.MeshStandardMaterial(), []);
// useFrame((state) => {
// modelRef.current.position.y =
// -1.5 + Math.sin(state.clock.elapsedTime) * 0.3;
// });
useFrame((state) => {
if (modelRef.current) {
// 드래그 중일때만 회전 만 적용
if (isDraggingRef.current) {
modelRef.current.rotation.y += rotationRef.current.y;
modelRef.current.rotation.x += rotationRef.current.x; // 회전 후에 값을 초기화하여 부드러운 회전 유지
rotationRef.current.x = 0;
rotationRef.current.y = 0;
} else {
// 드래그가 아닐 때 y축 위치 애니메이션 적용
modelRef.current.position.y =
-1.5 + Math.sin(state.clock.getElapsedTime()) * 0.3;
}
}
});
useEffect(() => {
const handleMouseDown = () => {
isDraggingRef.current = true;
};
const handleMouseUp = () => {
isDraggingRef.current = false;
};
const handleMouseMove = (event) => {
if (isDraggingRef.current) {
rotationRef.current = {
x: event.movementY * 0.005,
y: event.movementX * 0.005,
};
}
};
// touch event for moblie
const handleTouchStart = () => {
isDraggingRef.current = true;
};
const handleTouchEnd = () => {
isDraggingRef.current = false;
};
const handleTouchMove = (event) => {
if (isDraggingRef.current && event.touches.length === 1) {
const touch = event.touches[0];
rotationRef.current = {
x: touch.movementY * 0.005,
y: touch.movementX * 0.005,
};
}
};
window.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mouseup", handleMouseUp);
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("touchstart", handleTouchStart);
window.addEventListener("touchend", handleTouchEnd);
window.addEventListener("touchmove", handleTouchMove);
return () => {
window.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("mouseup", handleMouseUp);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("touchstart", handleTouchStart);
window.removeEventListener("touchend", handleTouchEnd);
window.removeEventListener("touchmove", handleTouchMove);
};
}, []);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 600) {
//768
setScale([1, 1, 1]);
} else {
setScale([2.5, 2.5, 2.5]);
}
};
window.addEventListener("resize", handleResize);
// Initial check
handleResize();
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return (
<group
{...props}
dispose={null}
ref={modelRef}
position={[0, 0, 0]}
scale={scale}
// rotation={[0.25, 0, 0]}
>
<Box
geometry={boxGeometry}
material={material}
position={[1.5, 0, 0]}
initialPosition={[3, 0, 0]} // 초기 위치를 오른쪽 멀리 설정
name="Box A"
/>
<Box
geometry={boxGeometry}
material={material}
position={[-1.5, 0, 0]}
initialPosition={[-3, 0, 0]} // 초기 위치를 왼쪽 멀리 설정
name="Box B"
/>
<Box
geometry={boxGeometry}
material={material}
position={[0, 1.5, 0]}
initialPosition={[0, 3, 0]} // 초기 위치를 위쪽 멀리 설정
name="Box C"
/>
<Box
geometry={boxGeometry}
material={material}
position={[0, -1.5, 0]}
initialPosition={[0, -3, 0]} // 초기 위치를 아래쪽 멀리 설정
name="Box D"
/>
</group>
);
});
export default Blocks;
OrbitControls - target, maxPolarAngle (0) | 2024.12.06 |
---|---|
이미지 매핑하기 (1) | 2024.11.24 |
video backgorund with @react-three/fibre (1) | 2024.11.22 |
resize - Three.js (0) | 2024.11.22 |
lookAt(), getWorldDirection() (1) | 2024.11.21 |