feat: Add motion preset registration API

- 모션 프리셋을 동적으로 등록하고 관리할 수 있는 API를 추가했습니다.
- `registerMotionPreset`, `registerMotionPresets`, `unregisterMotionPreset`, `getRegisteredPresets`, `hasPreset`, `resetToBuiltInPresets` 함수를 제공합니다.
- `MotionPreset` 타입을 `BuiltInMotionPreset`과 사용자 정의 문자열을 포함하도록 확장했습니다.
- `MotionPresetDefinition` 타입을 추가하여 커스텀 프리셋 정의 방식을 명확히 했습니다.
This commit is contained in:
BaekRyang 2025-11-26 11:05:36 +09:00
parent 5f6e780b40
commit 4db9839f28
11 changed files with 462 additions and 96 deletions

73
dist/index.d.mts vendored
View File

@ -13,9 +13,20 @@ interface Point {
*/ */
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad'; type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad';
/** /**
* *
*/ */
type MotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2'; type BuiltInMotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2';
/**
* ( + )
* registerMotionPreset()
*/
type MotionPreset = BuiltInMotionPreset | (string & {});
/**
*
* @param strength (기본값: 0.1)
* @returns x, y
*/
type MotionPresetDefinition = (strength: number) => Point;
/** /**
* *
*/ */
@ -409,6 +420,62 @@ declare const DEFAULT_AREA: {
}; };
}; };
/**
*
* @param name
* @param definition (strength를 Point )
* @param options
* @param options.isRotation (true면 )
*
* @example
* // 좌우 진짜 왕복 (좌↔우)
* registerMotionPreset('horizontal-full', (strength) => ({
* x: strength * 2, // 진폭 2배
* y: 0
* }));
*
* // 8자 모양 운동 (회전)
* registerMotionPreset('figure-8', (strength) => ({
* x: strength,
* y: strength * 0.5
* }), { isRotation: true });
*/
declare function registerMotionPreset(name: string, definition: MotionPresetDefinition, options?: {
isRotation?: boolean;
}): void;
/**
*
* @param presets ( )
* @param rotationPresetNames
*
* @example
* registerMotionPresets({
* 'horizontal-full': (s) => ({x: s * 2, y: 0}),
* 'wave': (s) => ({x: s, y: s * 0.3}),
* }, ['wave']); // wave는 회전 애니메이션
*/
declare function registerMotionPresets(presets: Record<string, MotionPresetDefinition>, rotationPresetNames?: string[]): void;
/**
*
* @param name
* @returns
*/
declare function unregisterMotionPreset(name: string): boolean;
/**
*
* @returns
*/
declare function getRegisteredPresets(): string[];
/**
*
* @param name
* @returns
*/
declare function hasPreset(name: string): boolean;
/**
* ( )
*/
declare function resetToBuiltInPresets(): void;
/** /**
* *
* @param preset * @param preset
@ -586,4 +653,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
getInteractingAreaIndices: () => Set<number>; getInteractingAreaIndices: () => Set<number>;
}; };
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity }; export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type BuiltInMotionPreset, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MotionPresetDefinition, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, getRegisteredPresets, hasPreset, isRotationPreset, presetToVector, registerMotionPreset, registerMotionPresets, resetToBuiltInPresets, unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };

73
dist/index.d.ts vendored
View File

@ -13,9 +13,20 @@ interface Point {
*/ */
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad'; type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad';
/** /**
* *
*/ */
type MotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2'; type BuiltInMotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2';
/**
* ( + )
* registerMotionPreset()
*/
type MotionPreset = BuiltInMotionPreset | (string & {});
/**
*
* @param strength (기본값: 0.1)
* @returns x, y
*/
type MotionPresetDefinition = (strength: number) => Point;
/** /**
* *
*/ */
@ -409,6 +420,62 @@ declare const DEFAULT_AREA: {
}; };
}; };
/**
*
* @param name
* @param definition (strength를 Point )
* @param options
* @param options.isRotation (true면 )
*
* @example
* // 좌우 진짜 왕복 (좌↔우)
* registerMotionPreset('horizontal-full', (strength) => ({
* x: strength * 2, // 진폭 2배
* y: 0
* }));
*
* // 8자 모양 운동 (회전)
* registerMotionPreset('figure-8', (strength) => ({
* x: strength,
* y: strength * 0.5
* }), { isRotation: true });
*/
declare function registerMotionPreset(name: string, definition: MotionPresetDefinition, options?: {
isRotation?: boolean;
}): void;
/**
*
* @param presets ( )
* @param rotationPresetNames
*
* @example
* registerMotionPresets({
* 'horizontal-full': (s) => ({x: s * 2, y: 0}),
* 'wave': (s) => ({x: s, y: s * 0.3}),
* }, ['wave']); // wave는 회전 애니메이션
*/
declare function registerMotionPresets(presets: Record<string, MotionPresetDefinition>, rotationPresetNames?: string[]): void;
/**
*
* @param name
* @returns
*/
declare function unregisterMotionPreset(name: string): boolean;
/**
*
* @returns
*/
declare function getRegisteredPresets(): string[];
/**
*
* @param name
* @returns
*/
declare function hasPreset(name: string): boolean;
/**
* ( )
*/
declare function resetToBuiltInPresets(): void;
/** /**
* *
* @param preset * @param preset
@ -586,4 +653,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
getInteractingAreaIndices: () => Set<number>; getInteractingAreaIndices: () => Set<number>;
}; };
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity }; export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type BuiltInMotionPreset, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MotionPresetDefinition, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, getRegisteredPresets, hasPreset, isRotationPreset, presetToVector, registerMotionPreset, registerMotionPresets, resetToBuiltInPresets, unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };

90
dist/index.js vendored
View File

@ -43,8 +43,14 @@ __export(index_exports, {
SpringPhysics: () => SpringPhysics, SpringPhysics: () => SpringPhysics,
ThreeScene: () => ThreeScene, ThreeScene: () => ThreeScene,
applyEasing: () => applyEasing, applyEasing: () => applyEasing,
getRegisteredPresets: () => getRegisteredPresets,
hasPreset: () => hasPreset,
isRotationPreset: () => isRotationPreset, isRotationPreset: () => isRotationPreset,
presetToVector: () => presetToVector, presetToVector: () => presetToVector,
registerMotionPreset: () => registerMotionPreset,
registerMotionPresets: () => registerMotionPresets,
resetToBuiltInPresets: () => resetToBuiltInPresets,
unregisterMotionPreset: () => unregisterMotionPreset,
useAnimationFrame: () => useAnimationFrame, useAnimationFrame: () => useAnimationFrame,
useDistortionEditor: () => useDistortionEditor, useDistortionEditor: () => useDistortionEditor,
useMouseInteraction: () => useMouseInteraction, useMouseInteraction: () => useMouseInteraction,
@ -250,31 +256,65 @@ var applyEasing = (progress, easingType) => {
}; };
// src/utils/motionPresets.ts // src/utils/motionPresets.ts
function presetToVector(preset, strength = 0.1) { var presetRegistry = /* @__PURE__ */ new Map();
switch (preset) { var rotationPresets = /* @__PURE__ */ new Set(["rotate-cw", "rotate-ccw"]);
case "none": var BUILT_IN_PRESETS = {
return { x: 0, y: 0 }; "none": () => ({ x: 0, y: 0 }),
case "horizontal": "horizontal": (strength) => ({ x: strength, y: 0 }),
return { x: strength, y: 0 }; "vertical": (strength) => ({ x: 0, y: strength }),
case "vertical": "rotate-cw": (strength) => ({ x: strength, y: 0 }),
return { x: 0, y: strength }; "rotate-ccw": (strength) => ({ x: -strength, y: 0 }),
case "rotate-cw": "pulse": (strength) => ({ x: strength, y: strength }),
return { x: strength, y: 0 }; "diagonal-1": (strength) => ({ x: strength * 0.707, y: strength * 0.707 }),
case "rotate-ccw": "diagonal-2": (strength) => ({ x: strength * 0.707, y: -strength * 0.707 })
return { x: -strength, y: 0 }; };
case "pulse": Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
return { x: strength, y: strength }; presetRegistry.set(name, definition);
case "diagonal-1": });
return { x: strength * 0.707, y: strength * 0.707 }; function registerMotionPreset(name, definition, options) {
// √2/2 ≈ 0.707 presetRegistry.set(name, definition);
case "diagonal-2": if (options?.isRotation) {
return { x: strength * 0.707, y: -strength * 0.707 }; rotationPresets.add(name);
default: } else {
return { x: 0, y: 0 }; rotationPresets.delete(name);
} }
} }
function registerMotionPresets(presets, rotationPresetNames) {
Object.entries(presets).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
rotationPresetNames?.forEach((name) => rotationPresets.add(name));
}
function unregisterMotionPreset(name) {
rotationPresets.delete(name);
return presetRegistry.delete(name);
}
function getRegisteredPresets() {
return Array.from(presetRegistry.keys());
}
function hasPreset(name) {
return presetRegistry.has(name);
}
function resetToBuiltInPresets() {
presetRegistry.clear();
rotationPresets.clear();
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
rotationPresets.add("rotate-cw");
rotationPresets.add("rotate-ccw");
}
function presetToVector(preset, strength = 0.1) {
const definition = presetRegistry.get(preset);
if (definition) {
return definition(strength);
}
console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`);
return { x: 0, y: 0 };
}
function isRotationPreset(preset) { function isRotationPreset(preset) {
return preset === "rotate-cw" || preset === "rotate-ccw"; if (!preset) return false;
return rotationPresets.has(preset);
} }
// src/engine/AnimationLoop.ts // src/engine/AnimationLoop.ts
@ -1616,8 +1656,14 @@ var EditorCanvas = ({
SpringPhysics, SpringPhysics,
ThreeScene, ThreeScene,
applyEasing, applyEasing,
getRegisteredPresets,
hasPreset,
isRotationPreset, isRotationPreset,
presetToVector, presetToVector,
registerMotionPreset,
registerMotionPresets,
resetToBuiltInPresets,
unregisterMotionPreset,
useAnimationFrame, useAnimationFrame,
useDistortionEditor, useDistortionEditor,
useMouseInteraction, useMouseInteraction,

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

84
dist/index.mjs vendored
View File

@ -196,31 +196,65 @@ var applyEasing = (progress, easingType) => {
}; };
// src/utils/motionPresets.ts // src/utils/motionPresets.ts
function presetToVector(preset, strength = 0.1) { var presetRegistry = /* @__PURE__ */ new Map();
switch (preset) { var rotationPresets = /* @__PURE__ */ new Set(["rotate-cw", "rotate-ccw"]);
case "none": var BUILT_IN_PRESETS = {
return { x: 0, y: 0 }; "none": () => ({ x: 0, y: 0 }),
case "horizontal": "horizontal": (strength) => ({ x: strength, y: 0 }),
return { x: strength, y: 0 }; "vertical": (strength) => ({ x: 0, y: strength }),
case "vertical": "rotate-cw": (strength) => ({ x: strength, y: 0 }),
return { x: 0, y: strength }; "rotate-ccw": (strength) => ({ x: -strength, y: 0 }),
case "rotate-cw": "pulse": (strength) => ({ x: strength, y: strength }),
return { x: strength, y: 0 }; "diagonal-1": (strength) => ({ x: strength * 0.707, y: strength * 0.707 }),
case "rotate-ccw": "diagonal-2": (strength) => ({ x: strength * 0.707, y: -strength * 0.707 })
return { x: -strength, y: 0 }; };
case "pulse": Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
return { x: strength, y: strength }; presetRegistry.set(name, definition);
case "diagonal-1": });
return { x: strength * 0.707, y: strength * 0.707 }; function registerMotionPreset(name, definition, options) {
// √2/2 ≈ 0.707 presetRegistry.set(name, definition);
case "diagonal-2": if (options?.isRotation) {
return { x: strength * 0.707, y: -strength * 0.707 }; rotationPresets.add(name);
default: } else {
return { x: 0, y: 0 }; rotationPresets.delete(name);
} }
} }
function registerMotionPresets(presets, rotationPresetNames) {
Object.entries(presets).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
rotationPresetNames?.forEach((name) => rotationPresets.add(name));
}
function unregisterMotionPreset(name) {
rotationPresets.delete(name);
return presetRegistry.delete(name);
}
function getRegisteredPresets() {
return Array.from(presetRegistry.keys());
}
function hasPreset(name) {
return presetRegistry.has(name);
}
function resetToBuiltInPresets() {
presetRegistry.clear();
rotationPresets.clear();
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
rotationPresets.add("rotate-cw");
rotationPresets.add("rotate-ccw");
}
function presetToVector(preset, strength = 0.1) {
const definition = presetRegistry.get(preset);
if (definition) {
return definition(strength);
}
console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`);
return { x: 0, y: 0 };
}
function isRotationPreset(preset) { function isRotationPreset(preset) {
return preset === "rotate-cw" || preset === "rotate-ccw"; if (!preset) return false;
return rotationPresets.has(preset);
} }
// src/engine/AnimationLoop.ts // src/engine/AnimationLoop.ts
@ -1561,8 +1595,14 @@ export {
SpringPhysics, SpringPhysics,
ThreeScene, ThreeScene,
applyEasing, applyEasing,
getRegisteredPresets,
hasPreset,
isRotationPreset, isRotationPreset,
presetToVector, presetToVector,
registerMotionPreset,
registerMotionPresets,
resetToBuiltInPresets,
unregisterMotionPreset,
useAnimationFrame, useAnimationFrame,
useDistortionEditor, useDistortionEditor,
useMouseInteraction, useMouseInteraction,

2
dist/index.mjs.map vendored

File diff suppressed because one or more lines are too long

14
package-lock.json generated
View File

@ -1,12 +1,13 @@
{ {
"name": "responsive-image-canvas", "name": "@baekryang/responsive-image-canvas",
"version": "1.0.0", "version": "1.0.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "responsive-image-canvas", "name": "@baekryang/responsive-image-canvas",
"version": "1.0.0", "version": "1.0.5",
"license": "MIT",
"devDependencies": { "devDependencies": {
"@types/react": "^19.2.2", "@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.2",
@ -16,6 +17,11 @@
"three": "^0.181.0", "three": "^0.181.0",
"tsup": "^8.5.0", "tsup": "^8.5.0",
"typescript": "^5.5.3" "typescript": "^5.5.3"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"three": ">=0.150.0"
} }
}, },
"node_modules/@dimforge/rapier3d-compat": { "node_modules/@dimforge/rapier3d-compat": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@baekryang/responsive-image-canvas", "name": "@baekryang/responsive-image-canvas",
"version": "1.0.5", "version": "1.1.0",
"publishConfig": { "publishConfig": {
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/" "registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
}, },

View File

@ -31,7 +31,9 @@ export { DEFAULT_EDITOR_CANVAS_STYLE } from './editor/constants';
export type { export type {
Point, Point,
EasingFunction, EasingFunction,
BuiltInMotionPreset,
MotionPreset, MotionPreset,
MotionPresetDefinition,
DistortionMovement, DistortionMovement,
DistortionArea, DistortionArea,
AreaBounds, AreaBounds,
@ -52,7 +54,17 @@ export type {
// 유틸리티 함수 // 유틸리티 함수
export { applyEasing } from './utils/easing'; export { applyEasing } from './utils/easing';
export { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants'; export { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';
export { presetToVector, isRotationPreset } from './utils/motionPresets'; export {
presetToVector,
isRotationPreset,
// 프리셋 레지스트리 API
registerMotionPreset,
registerMotionPresets,
unregisterMotionPreset,
getRegisteredPresets,
hasPreset,
resetToBuiltInPresets,
} from './utils/motionPresets';
// 엔진 클래스 (고급 사용자용) // 엔진 클래스 (고급 사용자용)
export { ThreeScene } from './engine/ThreeScene'; export { ThreeScene } from './engine/ThreeScene';

View File

@ -18,9 +18,9 @@ export type EasingFunction =
| 'easeOutQuad'; | 'easeOutQuad';
/** /**
* *
*/ */
export type MotionPreset = export type BuiltInMotionPreset =
| 'none' // 없음 (애니메이션 없음) | 'none' // 없음 (애니메이션 없음)
| 'horizontal' // 좌우 왕복 | 'horizontal' // 좌우 왕복
| 'vertical' // 상하 왕복 | 'vertical' // 상하 왕복
@ -30,6 +30,24 @@ export type MotionPreset =
| 'diagonal-1' // 대각선 (좌상→우하) | 'diagonal-1' // 대각선 (좌상→우하)
| 'diagonal-2'; // 대각선 (우상→좌하) | 'diagonal-2'; // 대각선 (우상→좌하)
/**
* ( + )
* registerMotionPreset()
*/
export type MotionPreset = BuiltInMotionPreset | (string & {});
/**
*
* @param strength (기본값: 0.1)
* @returns x, y
*/
export type MotionPresetDefinition = (strength: number) => Point;
/**
*
*/
export type RotationPresetChecker = (preset: MotionPreset) => boolean;
/** /**
* *
*/ */

View File

@ -1,4 +1,130 @@
import type {MotionPreset, Point} from '../types'; import type {MotionPreset, MotionPresetDefinition, Point, RotationPresetChecker} from '../types';
/**
* ( + )
*/
const presetRegistry = new Map<string, MotionPresetDefinition>();
/**
*
*/
const rotationPresets = new Set<string>(['rotate-cw', 'rotate-ccw']);
/**
*
*/
const BUILT_IN_PRESETS: Record<string, MotionPresetDefinition> = {
'none': () => ({x: 0, y: 0}),
'horizontal': (strength) => ({x: strength, y: 0}),
'vertical': (strength) => ({x: 0, y: strength}),
'rotate-cw': (strength) => ({x: strength, y: 0}),
'rotate-ccw': (strength) => ({x: -strength, y: 0}),
'pulse': (strength) => ({x: strength, y: strength}),
'diagonal-1': (strength) => ({x: strength * 0.707, y: strength * 0.707}),
'diagonal-2': (strength) => ({x: strength * 0.707, y: -strength * 0.707}),
};
// 내장 프리셋 등록
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
/**
*
* @param name
* @param definition (strength를 Point )
* @param options
* @param options.isRotation (true면 )
*
* @example
* // 좌우 진짜 왕복 (좌↔우)
* registerMotionPreset('horizontal-full', (strength) => ({
* x: strength * 2, // 진폭 2배
* y: 0
* }));
*
* // 8자 모양 운동 (회전)
* registerMotionPreset('figure-8', (strength) => ({
* x: strength,
* y: strength * 0.5
* }), { isRotation: true });
*/
export function registerMotionPreset(
name: string,
definition: MotionPresetDefinition,
options?: { isRotation?: boolean }
): void {
presetRegistry.set(name, definition);
if (options?.isRotation) {
rotationPresets.add(name);
} else {
rotationPresets.delete(name);
}
}
/**
*
* @param presets ( )
* @param rotationPresetNames
*
* @example
* registerMotionPresets({
* 'horizontal-full': (s) => ({x: s * 2, y: 0}),
* 'wave': (s) => ({x: s, y: s * 0.3}),
* }, ['wave']); // wave는 회전 애니메이션
*/
export function registerMotionPresets(
presets: Record<string, MotionPresetDefinition>,
rotationPresetNames?: string[]
): void {
Object.entries(presets).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
rotationPresetNames?.forEach(name => rotationPresets.add(name));
}
/**
*
* @param name
* @returns
*/
export function unregisterMotionPreset(name: string): boolean {
rotationPresets.delete(name);
return presetRegistry.delete(name);
}
/**
*
* @returns
*/
export function getRegisteredPresets(): string[] {
return Array.from(presetRegistry.keys());
}
/**
*
* @param name
* @returns
*/
export function hasPreset(name: string): boolean {
return presetRegistry.has(name);
}
/**
* ( )
*/
export function resetToBuiltInPresets(): void {
presetRegistry.clear();
rotationPresets.clear();
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
presetRegistry.set(name, definition);
});
rotationPresets.add('rotate-cw');
rotationPresets.add('rotate-ccw');
}
/** /**
* *
@ -7,47 +133,31 @@ import type {MotionPreset, Point} from '../types';
* @returns (vectorA) * @returns (vectorA)
*/ */
export function presetToVector(preset: MotionPreset, strength: number = 0.1): Point { export function presetToVector(preset: MotionPreset, strength: number = 0.1): Point {
switch (preset) { const definition = presetRegistry.get(preset);
case 'none':
// 애니메이션 없음
return {x: 0, y: 0};
case 'horizontal': if (definition) {
// 좌우 왕복 return definition(strength);
return {x: strength, y: 0};
case 'vertical':
// 상하 왕복
return {x: 0, y: strength};
case 'rotate-cw':
// 시계방향 회전 (원운동의 시작점)
return {x: strength, y: 0};
case 'rotate-ccw':
// 반시계방향 회전 (원운동의 시작점)
return {x: -strength, y: 0};
case 'pulse':
// 펄스 (중심에서 바깥으로)
return {x: strength, y: strength};
case 'diagonal-1':
// 대각선 (좌상→우하)
return {x: strength * 0.707, y: strength * 0.707}; // √2/2 ≈ 0.707
case 'diagonal-2':
// 대각선 (우상→좌하)
return {x: strength * 0.707, y: -strength * 0.707};
default:
return {x: 0, y: 0};
} }
// 등록되지 않은 프리셋은 none으로 처리
console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`);
return {x: 0, y: 0};
} }
/** /**
* *
*/ */
export function isRotationPreset(preset?: MotionPreset): boolean { export function isRotationPreset(preset?: MotionPreset): boolean {
return preset === 'rotate-cw' || preset === 'rotate-ccw'; if (!preset) return false;
return rotationPresets.has(preset);
}
/**
*
* @param checker
* @deprecated isRotation registerMotionPreset에
*/
export function setRotationChecker(checker: RotationPresetChecker): void {
// Legacy support - 기존 코드 호환성을 위해 유지
console.warn('setRotationChecker is deprecated. Use registerMotionPreset with { isRotation: true } option instead.');
} }