feat: Improve mouse interaction to affect multiple areas
- 마우스가 닿는 모든 영역에 왜곡 효과가 적용되도록 수정했습니다. - 기존에는 마우스가 처음 닿는 단일 영역에만 효과가 적용되었으나, 이제는 마우스 커서가 영역을 벗어나도 해당 영역에 대한 스프링 물리 효과가 유지되도록 변경되었습니다. - `useMouseInteraction` 훅에서 `interactingAreaIndex` 대신 `interactingAreaIndices` (Set)를 사용하여 여러 영역을 동시에 추적합니다. - 영역 진입 시 스프링이 리셋되고, 영역 이탈 시 평형 상태로 복귀하는 로직이 추가되었습니다.
This commit is contained in:
parent
7f6a72c058
commit
ddcf8b463a
2
dist/index.d.mts
vendored
2
dist/index.d.mts
vendored
@ -531,7 +531,7 @@ declare const useMouseVelocity: (containerRef: React.RefObject<HTMLElement | nul
|
||||
|
||||
/**
|
||||
* 마우스 인터랙션 기반 기존 영역 제어 훅
|
||||
* 기존 영역을 손으로 튕기는 효과
|
||||
* 마우스가 지나가는 모든 영역에 효과 적용
|
||||
*/
|
||||
declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement | null>, config: MouseInteractionConfig) => {
|
||||
updateInteraction: (areas: DistortionArea[], deltaTime: number) => DistortionArea[];
|
||||
|
||||
2
dist/index.d.ts
vendored
2
dist/index.d.ts
vendored
@ -531,7 +531,7 @@ declare const useMouseVelocity: (containerRef: React.RefObject<HTMLElement | nul
|
||||
|
||||
/**
|
||||
* 마우스 인터랙션 기반 기존 영역 제어 훅
|
||||
* 기존 영역을 손으로 튕기는 효과
|
||||
* 마우스가 지나가는 모든 영역에 효과 적용
|
||||
*/
|
||||
declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement | null>, config: MouseInteractionConfig) => {
|
||||
updateInteraction: (areas: DistortionArea[], deltaTime: number) => DistortionArea[];
|
||||
|
||||
38
dist/index.js
vendored
38
dist/index.js
vendored
@ -582,7 +582,7 @@ var isPointInPolygon = (point, polygon) => {
|
||||
};
|
||||
var useMouseInteraction = (containerRef, config) => {
|
||||
const { getState } = useMouseVelocity(containerRef);
|
||||
const [interactingAreaIndex, setInteractingAreaIndex] = (0, import_react3.useState)(null);
|
||||
const [interactingAreaIndices, setInteractingAreaIndices] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
||||
const springPhysicsMapRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
||||
const getSpringPhysics = (0, import_react3.useCallback)((areaIndex) => {
|
||||
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
||||
@ -594,24 +594,27 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
if (!config.enabled) return areas;
|
||||
const mouseState = getState();
|
||||
if (mouseState.isDragging && mouseState.position) {
|
||||
if (interactingAreaIndex === null) {
|
||||
for (let i = areas.length - 1; i >= 0; i--) {
|
||||
const currentlyInAreas = /* @__PURE__ */ new Set();
|
||||
for (let i = 0; i < areas.length; i++) {
|
||||
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
||||
setInteractingAreaIndex(i);
|
||||
currentlyInAreas.add(i);
|
||||
if (!interactingAreaIndices.has(i)) {
|
||||
getSpringPhysics(i).reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interactingAreaIndex !== null) {
|
||||
interactingAreaIndices.forEach((areaIndex) => {
|
||||
if (!currentlyInAreas.has(areaIndex)) {
|
||||
getSpringPhysics(areaIndex).returnToEquilibrium();
|
||||
}
|
||||
});
|
||||
setInteractingAreaIndices(currentlyInAreas);
|
||||
const velocityMult = config.velocityMultiplier || 1;
|
||||
const spring = getSpringPhysics(interactingAreaIndex);
|
||||
const velocityMag = Math.sqrt(
|
||||
mouseState.velocity.x ** 2 + mouseState.velocity.y ** 2
|
||||
);
|
||||
const minVel = config.minVelocity || 0.05;
|
||||
const maxVel = config.maxVelocity || 5;
|
||||
if (velocityMag >= minVel) {
|
||||
let clampedVelocity = mouseState.velocity;
|
||||
if (velocityMag > maxVel) {
|
||||
const scale = maxVel / velocityMag;
|
||||
@ -620,15 +623,17 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
y: mouseState.velocity.y * scale
|
||||
};
|
||||
}
|
||||
currentlyInAreas.forEach((areaIndex) => {
|
||||
const spring = getSpringPhysics(areaIndex);
|
||||
if (velocityMag >= minVel) {
|
||||
spring.setTarget(clampedVelocity, velocityMult);
|
||||
} else {
|
||||
spring.returnToEquilibrium();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (interactingAreaIndex !== null) {
|
||||
if (interactingAreaIndices.size > 0) {
|
||||
const velocityMult = config.velocityMultiplier || 1;
|
||||
const spring = getSpringPhysics(interactingAreaIndex);
|
||||
const maxVel = config.maxVelocity || 5;
|
||||
const velocityMag = Math.sqrt(
|
||||
mouseState.velocity.x ** 2 + mouseState.velocity.y ** 2
|
||||
@ -641,8 +646,11 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
y: mouseState.velocity.y * scale
|
||||
};
|
||||
}
|
||||
interactingAreaIndices.forEach((areaIndex) => {
|
||||
const spring = getSpringPhysics(areaIndex);
|
||||
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
||||
setInteractingAreaIndex(null);
|
||||
});
|
||||
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
||||
}
|
||||
}
|
||||
return areas.map((area, index) => {
|
||||
@ -651,7 +659,7 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
const springVelocity = spring.getVelocity();
|
||||
const springDisplacement = spring.getDisplacement();
|
||||
const isSpringActive = Math.sqrt(springVelocity.x ** 2 + springVelocity.y ** 2) > 1e-3 || Math.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 1e-3;
|
||||
if (index !== interactingAreaIndex && !isSpringActive) {
|
||||
if (!interactingAreaIndices.has(index) && !isSpringActive) {
|
||||
return area;
|
||||
}
|
||||
const displacement = spring.update(deltaTime);
|
||||
@ -667,7 +675,7 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
}
|
||||
};
|
||||
});
|
||||
}, [config, getState, interactingAreaIndex, getSpringPhysics]);
|
||||
}, [config, getState, interactingAreaIndices, getSpringPhysics]);
|
||||
const updateConfig = (0, import_react3.useCallback)((newConfig) => {
|
||||
const physicsConfig = newConfig.physics;
|
||||
if (physicsConfig) {
|
||||
@ -680,7 +688,7 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
springPhysicsMapRef.current.forEach((spring) => {
|
||||
spring.reset();
|
||||
});
|
||||
setInteractingAreaIndex(null);
|
||||
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
||||
}, []);
|
||||
return {
|
||||
updateInteraction,
|
||||
|
||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
38
dist/index.mjs
vendored
38
dist/index.mjs
vendored
@ -533,7 +533,7 @@ var isPointInPolygon = (point, polygon) => {
|
||||
};
|
||||
var useMouseInteraction = (containerRef, config) => {
|
||||
const { getState } = useMouseVelocity(containerRef);
|
||||
const [interactingAreaIndex, setInteractingAreaIndex] = useState(null);
|
||||
const [interactingAreaIndices, setInteractingAreaIndices] = useState(/* @__PURE__ */ new Set());
|
||||
const springPhysicsMapRef = useRef3(/* @__PURE__ */ new Map());
|
||||
const getSpringPhysics = useCallback2((areaIndex) => {
|
||||
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
||||
@ -545,24 +545,27 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
if (!config.enabled) return areas;
|
||||
const mouseState = getState();
|
||||
if (mouseState.isDragging && mouseState.position) {
|
||||
if (interactingAreaIndex === null) {
|
||||
for (let i = areas.length - 1; i >= 0; i--) {
|
||||
const currentlyInAreas = /* @__PURE__ */ new Set();
|
||||
for (let i = 0; i < areas.length; i++) {
|
||||
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
||||
setInteractingAreaIndex(i);
|
||||
currentlyInAreas.add(i);
|
||||
if (!interactingAreaIndices.has(i)) {
|
||||
getSpringPhysics(i).reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interactingAreaIndex !== null) {
|
||||
interactingAreaIndices.forEach((areaIndex) => {
|
||||
if (!currentlyInAreas.has(areaIndex)) {
|
||||
getSpringPhysics(areaIndex).returnToEquilibrium();
|
||||
}
|
||||
});
|
||||
setInteractingAreaIndices(currentlyInAreas);
|
||||
const velocityMult = config.velocityMultiplier || 1;
|
||||
const spring = getSpringPhysics(interactingAreaIndex);
|
||||
const velocityMag = Math.sqrt(
|
||||
mouseState.velocity.x ** 2 + mouseState.velocity.y ** 2
|
||||
);
|
||||
const minVel = config.minVelocity || 0.05;
|
||||
const maxVel = config.maxVelocity || 5;
|
||||
if (velocityMag >= minVel) {
|
||||
let clampedVelocity = mouseState.velocity;
|
||||
if (velocityMag > maxVel) {
|
||||
const scale = maxVel / velocityMag;
|
||||
@ -571,15 +574,17 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
y: mouseState.velocity.y * scale
|
||||
};
|
||||
}
|
||||
currentlyInAreas.forEach((areaIndex) => {
|
||||
const spring = getSpringPhysics(areaIndex);
|
||||
if (velocityMag >= minVel) {
|
||||
spring.setTarget(clampedVelocity, velocityMult);
|
||||
} else {
|
||||
spring.returnToEquilibrium();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (interactingAreaIndex !== null) {
|
||||
if (interactingAreaIndices.size > 0) {
|
||||
const velocityMult = config.velocityMultiplier || 1;
|
||||
const spring = getSpringPhysics(interactingAreaIndex);
|
||||
const maxVel = config.maxVelocity || 5;
|
||||
const velocityMag = Math.sqrt(
|
||||
mouseState.velocity.x ** 2 + mouseState.velocity.y ** 2
|
||||
@ -592,8 +597,11 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
y: mouseState.velocity.y * scale
|
||||
};
|
||||
}
|
||||
interactingAreaIndices.forEach((areaIndex) => {
|
||||
const spring = getSpringPhysics(areaIndex);
|
||||
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
||||
setInteractingAreaIndex(null);
|
||||
});
|
||||
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
||||
}
|
||||
}
|
||||
return areas.map((area, index) => {
|
||||
@ -602,7 +610,7 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
const springVelocity = spring.getVelocity();
|
||||
const springDisplacement = spring.getDisplacement();
|
||||
const isSpringActive = Math.sqrt(springVelocity.x ** 2 + springVelocity.y ** 2) > 1e-3 || Math.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 1e-3;
|
||||
if (index !== interactingAreaIndex && !isSpringActive) {
|
||||
if (!interactingAreaIndices.has(index) && !isSpringActive) {
|
||||
return area;
|
||||
}
|
||||
const displacement = spring.update(deltaTime);
|
||||
@ -618,7 +626,7 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
}
|
||||
};
|
||||
});
|
||||
}, [config, getState, interactingAreaIndex, getSpringPhysics]);
|
||||
}, [config, getState, interactingAreaIndices, getSpringPhysics]);
|
||||
const updateConfig = useCallback2((newConfig) => {
|
||||
const physicsConfig = newConfig.physics;
|
||||
if (physicsConfig) {
|
||||
@ -631,7 +639,7 @@ var useMouseInteraction = (containerRef, config) => {
|
||||
springPhysicsMapRef.current.forEach((spring) => {
|
||||
spring.reset();
|
||||
});
|
||||
setInteractingAreaIndex(null);
|
||||
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
||||
}, []);
|
||||
return {
|
||||
updateInteraction,
|
||||
|
||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -21,14 +21,14 @@ const isPointInPolygon = (point: Point, polygon: Point[]): boolean => {
|
||||
|
||||
/**
|
||||
* 마우스 인터랙션 기반 기존 영역 제어 훅
|
||||
* 기존 영역을 손으로 튕기는 효과
|
||||
* 마우스가 지나가는 모든 영역에 효과 적용
|
||||
*/
|
||||
export const useMouseInteraction = (
|
||||
containerRef: React.RefObject<HTMLElement | null>,
|
||||
config: MouseInteractionConfig
|
||||
) => {
|
||||
const { getState } = useMouseVelocity(containerRef);
|
||||
const [interactingAreaIndex, setInteractingAreaIndex] = useState<number | null>(null);
|
||||
const [interactingAreaIndices, setInteractingAreaIndices] = useState<Set<number>>(new Set());
|
||||
const springPhysicsMapRef = useRef<Map<number, SpringPhysics>>(new Map());
|
||||
|
||||
/**
|
||||
@ -51,33 +51,38 @@ export const useMouseInteraction = (
|
||||
|
||||
// 마우스 클릭/드래그 중이고 위치가 있으면
|
||||
if (mouseState.isDragging && mouseState.position) {
|
||||
// 아직 영역을 선택하지 않았으면 찾기
|
||||
if (interactingAreaIndex === null) {
|
||||
// 마우스 위치가 포함된 영역 찾기 (마지막 영역부터 - 위에 있는 영역 우선)
|
||||
for (let i = areas.length - 1; i >= 0; i--) {
|
||||
// 현재 마우스 위치가 포함된 모든 영역 찾기
|
||||
const currentlyInAreas = new Set<number>();
|
||||
for (let i = 0; i < areas.length; i++) {
|
||||
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
||||
setInteractingAreaIndex(i);
|
||||
// 해당 영역의 스프링 리셋
|
||||
currentlyInAreas.add(i);
|
||||
|
||||
// 새로 진입한 영역이면 스프링 리셋
|
||||
if (!interactingAreaIndices.has(i)) {
|
||||
getSpringPhysics(i).reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 드래그 중인 영역이 있으면 마우스 방향으로 실시간 늘어남
|
||||
if (interactingAreaIndex !== null) {
|
||||
// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀
|
||||
interactingAreaIndices.forEach((areaIndex) => {
|
||||
if (!currentlyInAreas.has(areaIndex)) {
|
||||
getSpringPhysics(areaIndex).returnToEquilibrium();
|
||||
}
|
||||
});
|
||||
|
||||
// 인터랙션 영역 업데이트
|
||||
setInteractingAreaIndices(currentlyInAreas);
|
||||
|
||||
// 현재 위치의 모든 영역에 속도 적용
|
||||
const velocityMult = config.velocityMultiplier || 1.0;
|
||||
const spring = getSpringPhysics(interactingAreaIndex);
|
||||
|
||||
// 속도 크기 확인
|
||||
const velocityMag = Math.sqrt(
|
||||
mouseState.velocity.x ** 2 + mouseState.velocity.y ** 2
|
||||
);
|
||||
const minVel = config.minVelocity || 0.05;
|
||||
const maxVel = config.maxVelocity || 5.0;
|
||||
|
||||
if (velocityMag >= minVel) {
|
||||
// 속도 클램핑 (너무 빠른 움직임 제한)
|
||||
// 속도 클램핑
|
||||
let clampedVelocity = mouseState.velocity;
|
||||
if (velocityMag > maxVel) {
|
||||
const scale = maxVel / velocityMag;
|
||||
@ -87,21 +92,24 @@ export const useMouseInteraction = (
|
||||
};
|
||||
}
|
||||
|
||||
// 드래그 중: 클램핑된 마우스 속도를 목표로 설정
|
||||
currentlyInAreas.forEach((areaIndex) => {
|
||||
const spring = getSpringPhysics(areaIndex);
|
||||
|
||||
if (velocityMag >= minVel) {
|
||||
// 드래그 중: 마우스 속도를 목표로 설정
|
||||
spring.setTarget(clampedVelocity, velocityMult);
|
||||
} else {
|
||||
// 드래그 중이지만 마우스가 멈춰있으면 평형으로 복귀
|
||||
spring.returnToEquilibrium();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 마우스를 놓았으면 마지막 속도를 초기 속도로 설정하여 튕김
|
||||
if (interactingAreaIndex !== null) {
|
||||
// 마우스를 놓았으면 인터랙션 중이던 모든 영역에 튕김 효과
|
||||
if (interactingAreaIndices.size > 0) {
|
||||
const velocityMult = config.velocityMultiplier || 1.0;
|
||||
const spring = getSpringPhysics(interactingAreaIndex);
|
||||
const maxVel = config.maxVelocity || 5.0;
|
||||
|
||||
// 속도 클램핑 (놓을 때도 제한)
|
||||
// 속도 클램핑
|
||||
const velocityMag = Math.sqrt(
|
||||
mouseState.velocity.x ** 2 + mouseState.velocity.y ** 2
|
||||
);
|
||||
@ -114,9 +122,13 @@ export const useMouseInteraction = (
|
||||
};
|
||||
}
|
||||
|
||||
// 클램핑된 속도를 초기 속도로 설정하여 튕김
|
||||
// 모든 인터랙션 영역에 초기 속도 설정
|
||||
interactingAreaIndices.forEach((areaIndex) => {
|
||||
const spring = getSpringPhysics(areaIndex);
|
||||
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
||||
setInteractingAreaIndex(null);
|
||||
});
|
||||
|
||||
setInteractingAreaIndices(new Set());
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +144,7 @@ export const useMouseInteraction = (
|
||||
Math.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 0.001;
|
||||
|
||||
// 드래그 중이 아니고 스프링도 비활성이면 업데이트 안 함
|
||||
if (index !== interactingAreaIndex && !isSpringActive) {
|
||||
if (!interactingAreaIndices.has(index) && !isSpringActive) {
|
||||
return area;
|
||||
}
|
||||
|
||||
@ -157,7 +169,7 @@ export const useMouseInteraction = (
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [config, getState, interactingAreaIndex, getSpringPhysics]);
|
||||
}, [config, getState, interactingAreaIndices, getSpringPhysics]);
|
||||
|
||||
/**
|
||||
* 물리 파라미터 업데이트
|
||||
@ -179,7 +191,7 @@ export const useMouseInteraction = (
|
||||
springPhysicsMapRef.current.forEach((spring) => {
|
||||
spring.reset();
|
||||
});
|
||||
setInteractingAreaIndex(null);
|
||||
setInteractingAreaIndices(new Set());
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user