forked from 77media/video-flow
197 lines
5.2 KiB
TypeScript
197 lines
5.2 KiB
TypeScript
/**
|
||
* 视频编辑连接线统一配置
|
||
* 确保所有连接线组件使用一致的视觉参数和几何计算
|
||
*/
|
||
|
||
/**
|
||
* 统一的连接线视觉样式配置
|
||
*/
|
||
export const CONNECTION_STYLE = {
|
||
// 颜色配置
|
||
color: 'rgba(255, 255, 255, 0.9)', // 统一的白色,确保在深色背景下清晰可见
|
||
strokeWidth: 2, // 统一的线条粗细
|
||
dashArray: '8,4', // 统一的虚线样式:8px实线,4px间隔
|
||
|
||
// 阴影效果
|
||
dropShadow: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))',
|
||
|
||
// 动画配置
|
||
animation: {
|
||
pathDuration: 0.6,
|
||
pathEasing: 'easeInOut',
|
||
opacityDuration: 0.3,
|
||
arrowDelay: 0.4,
|
||
arrowDuration: 0.3,
|
||
springConfig: {
|
||
stiffness: 300,
|
||
damping: 25
|
||
}
|
||
}
|
||
} as const;
|
||
|
||
/**
|
||
* 统一的箭头几何参数
|
||
*/
|
||
export const ARROW_GEOMETRY = {
|
||
size: 8, // 箭头长度
|
||
halfHeight: 4, // 箭头半高(宽度的一半)
|
||
centerOffset: 0.6 // 连接线连接到箭头的位置比例(0.6表示稍微向前偏移)
|
||
} as const;
|
||
|
||
/**
|
||
* 弧线计算参数
|
||
*/
|
||
export const CURVE_CONFIG = {
|
||
curvature: 0.3, // 弧线弯曲程度
|
||
minControlOffset: 10, // 最小控制点偏移
|
||
maxControlOffset: 60 // 最大控制点偏移
|
||
} as const;
|
||
|
||
/**
|
||
* 计算统一的箭头几何形状
|
||
*/
|
||
export function calculateArrowGeometry(
|
||
startPoint: { x: number; y: number },
|
||
endPoint: { x: number; y: number }
|
||
) {
|
||
// 计算连接方向向量
|
||
const dx = endPoint.x - startPoint.x;
|
||
const dy = endPoint.y - startPoint.y;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
const normalizedDx = dx / distance;
|
||
const normalizedDy = dy / distance;
|
||
|
||
// 箭头几何计算
|
||
const arrowTip = { x: endPoint.x, y: endPoint.y };
|
||
const arrowBase = {
|
||
x: endPoint.x - normalizedDx * ARROW_GEOMETRY.size,
|
||
y: endPoint.y - normalizedDy * ARROW_GEOMETRY.size
|
||
};
|
||
const arrowCenter = {
|
||
x: endPoint.x - normalizedDx * (ARROW_GEOMETRY.size * ARROW_GEOMETRY.centerOffset),
|
||
y: endPoint.y - normalizedDy * (ARROW_GEOMETRY.size * ARROW_GEOMETRY.centerOffset)
|
||
};
|
||
|
||
// 计算垂直向量用于箭头宽度
|
||
const perpX = -normalizedDy;
|
||
const perpY = normalizedDx;
|
||
|
||
const arrowPoints = [
|
||
arrowTip,
|
||
{
|
||
x: arrowBase.x + perpX * ARROW_GEOMETRY.halfHeight,
|
||
y: arrowBase.y + perpY * ARROW_GEOMETRY.halfHeight
|
||
},
|
||
{
|
||
x: arrowBase.x - perpX * ARROW_GEOMETRY.halfHeight,
|
||
y: arrowBase.y - perpY * ARROW_GEOMETRY.halfHeight
|
||
}
|
||
];
|
||
|
||
return {
|
||
tip: arrowTip,
|
||
base: arrowBase,
|
||
center: arrowCenter,
|
||
points: arrowPoints,
|
||
direction: { dx: normalizedDx, dy: normalizedDy },
|
||
perpendicular: { perpX, perpY },
|
||
distance
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 计算统一的弧线路径
|
||
*/
|
||
export function calculateCurvePath(
|
||
startPoint: { x: number; y: number },
|
||
endPoint: { x: number; y: number },
|
||
containerSize: { width: number; height: number }
|
||
): string {
|
||
const dx = endPoint.x - startPoint.x;
|
||
const dy = endPoint.y - startPoint.y;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
// 计算控制点,创建优雅的弧线
|
||
const midX = (startPoint.x + endPoint.x) / 2;
|
||
const midY = (startPoint.y + endPoint.y) / 2;
|
||
|
||
let controlX = midX;
|
||
let controlY = midY;
|
||
|
||
// 根据方向调整控制点
|
||
if (Math.abs(dx) > Math.abs(dy)) {
|
||
controlY = midY + (dy > 0 ? -1 : 1) * distance * CURVE_CONFIG.curvature;
|
||
} else {
|
||
controlX = midX + (dx > 0 ? -1 : 1) * distance * CURVE_CONFIG.curvature;
|
||
}
|
||
|
||
// 确保控制点在容器范围内
|
||
controlX = Math.max(CURVE_CONFIG.minControlOffset,
|
||
Math.min(containerSize.width - CURVE_CONFIG.minControlOffset, controlX));
|
||
controlY = Math.max(CURVE_CONFIG.minControlOffset,
|
||
Math.min(containerSize.height - CURVE_CONFIG.minControlOffset, controlY));
|
||
|
||
// 创建二次贝塞尔曲线路径
|
||
return `M ${startPoint.x} ${startPoint.y} Q ${controlX} ${controlY} ${endPoint.x} ${endPoint.y}`;
|
||
}
|
||
|
||
/**
|
||
* 动画配置类型定义
|
||
*/
|
||
export interface ConnectionAnimationConfig {
|
||
line: {
|
||
initial: Record<string, any>;
|
||
animate: Record<string, any>;
|
||
transition: Record<string, any>;
|
||
};
|
||
arrow: {
|
||
initial: Record<string, any>;
|
||
animate: Record<string, any>;
|
||
transition: Record<string, any>;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取统一的动画配置
|
||
*/
|
||
export function getConnectionAnimationConfig(animated: boolean = true): ConnectionAnimationConfig {
|
||
if (!animated) {
|
||
return {
|
||
line: {
|
||
initial: {},
|
||
animate: {},
|
||
transition: {}
|
||
},
|
||
arrow: {
|
||
initial: {},
|
||
animate: {},
|
||
transition: {}
|
||
}
|
||
};
|
||
}
|
||
|
||
return {
|
||
line: {
|
||
initial: { pathLength: 0, opacity: 0 },
|
||
animate: { pathLength: 1, opacity: 1 },
|
||
transition: {
|
||
pathLength: {
|
||
duration: CONNECTION_STYLE.animation.pathDuration,
|
||
ease: CONNECTION_STYLE.animation.pathEasing as any
|
||
},
|
||
opacity: { duration: CONNECTION_STYLE.animation.opacityDuration }
|
||
}
|
||
},
|
||
arrow: {
|
||
initial: { scale: 0, opacity: 0 },
|
||
animate: { scale: 1, opacity: 1 },
|
||
transition: {
|
||
delay: CONNECTION_STYLE.animation.arrowDelay,
|
||
duration: CONNECTION_STYLE.animation.arrowDuration,
|
||
type: "spring" as const,
|
||
...CONNECTION_STYLE.animation.springConfig
|
||
}
|
||
}
|
||
};
|
||
}
|