hookehuyr

docs(animation.vue): 完善代码注释以提高可读性和维护性

<!--
* @Date: 2025-03-28 09:23:04
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-03-28 23:33:13
* @LastEditTime: 2025-03-29 00:31:56
* @FilePath: /mlaj/src/views/animation.vue
* @Description: 贝塞尔曲线动画路径组件
*
......@@ -115,8 +115,11 @@ import img_svg3 from "@/assets/3.svg";
import img_svg4 from "@/assets/4.svg";
import img_svg5 from "@/assets/5.svg";
// 定义节点坐标数组,每个节点包含x和y坐标
// 这些点将用于生成贝塞尔曲线的路径
/**
* 节点坐标数组
* 每个元素是一个包含x和y坐标的对象
* 这些点将用于生成贝塞尔曲线的路径
*/
const points = ref([
{ x: 700, y: 50 },
{ x: 300, y: 300 },
......@@ -125,16 +128,32 @@ const points = ref([
{ x: 700, y: 1000 },
]);
// 当前激活的节点索引,初始值为-1表示没有节点被激活
/**
* activeNodeIndex 是一个响应式数据,用于存储当前激活的节点索引。
* 初始值为 -1,表示没有节点被激活。
* 当用户点击节点时,该索引会被更新为当前节点的索引。
* 当用户点击下一步按钮时,该索引会被更新为下一个节点的索引。
*/
const activeNodeIndex = ref(-1);
// 存储已激活的路径段数组,每个元素是一个SVG路径字符串
// 用于显示高亮的贝塞尔曲线路径
/**
* activePathSegments 是一个响应式数据,用于存储已激活的路径段数组。
* 初始值为空数组,表示没有路径段被激活。
* 当用户点击下一步按钮时,该数组会被更新为包含当前节点和下一个节点之间的路径段。
* 存储已激活的路径段数组,每个元素是一个SVG路径字符串
* 用于显示高亮的贝塞尔曲线路径
*/
const activePathSegments = ref([]);
// 计算完整的贝塞尔曲线路径
// 使用三次贝塞尔曲线(Cubic Bezier)创建平滑的曲线效果
// 控制点的位置通过当前点和下一个点的中点来计算
/**
* 计算完整的贝塞尔曲线路径
* 该函数使用 points 数组中的点来生成完整的贝塞尔曲线路径
* 每个点之间的路径段通过 calculatePathSegment 函数计算得到
* 控制点的位置通过当前点和下一个点的中点来计算
* 返回一个包含所有路径段的字符串
* 用于在SVG中显示完整的路径
* @returns 返回SVG路径字符串
*/
const pathData = computed(() => {
const path = [];
points.value.forEach((point, index) => {
......@@ -149,19 +168,23 @@ const pathData = computed(() => {
return path.join(" ");
});
// 计算两点之间的贝塞尔曲线路径段
// 该函数使用三次贝塞尔曲线算法生成平滑的曲线路径:
// 1. 计算路径的方向向量和单位向量
// 2. 添加垂直偏移以创建弧形效果
// 3. 根据路径方向动态调整控制点
// 4. 生成标准的SVG路径命令字符串
// @param startPoint - 起始点坐标 {x, y}
// @param endPoint - 结束点坐标 {x, y}
// @returns 返回SVG路径字符串
/**
* 该函数使用三次贝塞尔曲线算法生成平滑的曲线路径, 计算两点之间的贝塞尔曲线路径段:
* 1. 计算路径的方向向量和单位向量
* 2. 添加垂直偏移以创建弧形效果
* 3. 根据路径方向动态调整控制点
* 4. 生成标准的SVG路径命令字符串
* @param startPoint - 起始点坐标 {x, y}
* @param endPoint - 结束点坐标 {x, y}
* @returns 返回SVG路径字符串
*/
const calculatePathSegment = (startPoint, endPoint) => {
// 计算路径的方向向量
// 计算起点和终点之间的水平距离差
const dx = endPoint.x - startPoint.x;
// 计算起点和终点之间的垂直距离差
const dy = endPoint.y - startPoint.y;
// 使用勾股定理计算两点之间的直线距离
const distance = Math.sqrt(dx * dx + dy * dy);
// 设置节点间的偏移距离(可以根据需要调整)
......@@ -173,26 +196,43 @@ const calculatePathSegment = (startPoint, endPoint) => {
const unitY = dy / distance;
// 计算偏移后的起点和终点,根据路径方向调整垂直偏移
// 根据路径方向调整起点坐标
// x坐标: 在原始起点基础上沿单位向量方向偏移
// y坐标: 根据路径是向上还是向下来决定垂直偏移的方向
const adjustedStart = {
x: startPoint.x + unitX * offset,
y: startPoint.y + (dy > 0 ? -verticalOffset : verticalOffset),
};
// 根据路径方向调整终点坐标
// x坐标: 在原始终点基础上沿单位向量反方向偏移
// y坐标: 与起点相反的垂直偏移方向,确保曲线的平滑过渡
const adjustedEnd = {
x: endPoint.x - unitX * offset,
y: endPoint.y + (dy > 0 ? verticalOffset : -verticalOffset),
};
// 计算控制点
// 计算第一个控制点的x坐标 - 与起点x坐标相同
const cpx1 = adjustedStart.x;
// 计算第一个控制点的y坐标 - 在起点和终点y坐标的中点
const cpy1 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5;
// 计算第二个控制点的x坐标 - 与终点x坐标相同
const cpx2 = adjustedEnd.x;
// 计算第二个控制点的y坐标 - 与第一个控制点y坐标相同,确保平滑过渡
const cpy2 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5;
return `M ${adjustedStart.x} ${adjustedStart.y} C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${adjustedEnd.x} ${adjustedEnd.y}`;
};
// 未激活路径的颜色
// 使用浅灰色(#ccc)表示未激活状态
/**
* 计算未激活路径的颜色
* 该函数根据当前激活的节点索引来计算路径的颜色
* 当没有节点被激活时,路径颜色为浅灰色(#ccc)
* 当有节点被激活时,路径颜色为绿色(#4CAF50)
* 返回路径颜色的CSS字符串
* @returns 返回路径颜色的CSS字符串
*/
const pathColor = computed(() => {
return "#ccc";
});
......@@ -215,25 +255,35 @@ const pathColor = computed(() => {
// }
// };
// 处理下一步按钮点击事件
// 该函数实现了动画的核心交互逻辑:
// 1. 按顺序激活下一个节点
// 2. 创建新的高亮路径段并添加到动画序列
// 3. 更新节点状态和路径显示
// 4. 当到达最后一个节点时自动禁用按钮
// 5. 确保动画流程的连贯性和用户体验
/**
* 处理下一步按钮点击事件
* 该函数实现了动画的核心交互逻辑:
* 1. 按顺序激活下一个节点
* 2. 创建新的高亮路径段并添加到动画序列
* 3. 更新节点状态和路径显示
* 4. 当到达最后一个节点时自动禁用按钮
* 5. 确保动画流程的连贯性和用户体验
*/
const nextStep = () => {
// 检查是否还有下一个节点可以激活
if (activeNodeIndex.value < points.value.length - 1) {
// 激活下一个节点
activeNodeIndex.value++;
// 获取当前激活节点的坐标
const startPoint = points.value[activeNodeIndex.value];
// 获取下一个节点的坐标
const endPoint = points.value[activeNodeIndex.value + 1];
// 只有当存在下一个节点时才添加新的路径段
if (endPoint) {
// 计算当前节点到下一个节点的贝塞尔曲线路径
const newSegment = calculatePathSegment(startPoint, endPoint);
// 保留之前的路径段,添加新的路径段
// 将新的路径段添加到激活路径数组中,保留之前的路径段
activePathSegments.value.push(newSegment);
}
// 如果是最后一个节点,直接将其设置为激活状态
// 如果当前是最后一个节点,确保其状态为激活
if (activeNodeIndex.value === points.value.length - 1) {
activeNodeIndex.value = points.value.length - 1;
}
......@@ -288,10 +338,14 @@ const svgObj = [
},
},
];
// 计算SVG容器尺寸
// 该函数用于获取动画容器的实际尺寸,以便于后续计算SVG视图框
// 返回一个包含容器宽度和高度的对象
// 如果容器不存在,则返回默认值{width: 0, height: 0}
/**
* 计算SVG容器尺寸
* 该函数用于获取动画容器的实际尺寸,以便于后续计算SVG视图框
* 返回一个包含容器宽度和高度的对象
* 如果容器不存在,则返回默认值{width: 0, height: 0}
* @returns 返回SVG容器尺寸的对象
*/
const containerSize = computed(() => {
const container = document.querySelector('.animation-container');
if (!container) return { width: 0, height: 0 };
......@@ -318,37 +372,48 @@ onUnmounted(() => {
window.removeEventListener('resize', updateContainerSize);
});
// 计算并更新SVG视图框(viewBox)
// 该函数动态计算SVG视图框的尺寸和位置,以确保:
// 1. 所有节点都在可视区域内
// 2. 保持适当的宽高比
// 3. 根据容器尺寸自适应调整
// 4. 在不同屏幕尺寸下保持良好的显示效果
/**
* 计算并更新SVG视图框(viewBox)
* 该函数根据当前节点的位置和容器尺寸来计算SVG视图框的尺寸和位置
* 1. 所有节点都在可视区域内
* 2. 保持适当的宽高比
* 3. 根据容器尺寸自适应调整
* 4. 在不同屏幕尺寸下保持良好的显示效果
* 返回一个包含视图框尺寸和位置的字符串
* 格式为"minX minY width height"
* 用于设置SVG的viewBox属性
* @returns 返回SVG视图框的字符串
*/
const svgViewBox = computed(() => {
// 计算所有节点中的最大和最小坐标值
const maxX = Math.max(...points.value.map(p => p.x));
const maxY = Math.max(...points.value.map(p => p.y));
const minX = Math.min(...points.value.map(p => p.x));
const minY = Math.min(...points.value.map(p => p.y));
// 设置内边距,确保节点不会贴近边缘
const padding = 100;
// 计算内容区域的实际尺寸
const contentWidth = maxX - minX + padding * 2; // 内容宽度 = 最大x - 最小x + 两侧内边距
const contentHeight = maxY - minY + padding * 2; // 内容高度 = 最大y - 最小y + 上下内边距
// 计算内容的宽高比
const contentWidth = maxX - minX + padding * 2;
const contentHeight = maxY - minY + padding * 2;
const contentRatio = contentWidth / contentHeight;
// 获取容器的宽高比
const containerRatio = containerSize.value.width / containerSize.value.height;
// 根据宽高比调整viewBox
// 根据容器和内容的宽高比例关系调整viewBox
if (containerRatio > contentRatio) {
// 容器更宽,以高度为基准
const adjustedWidth = contentHeight * containerRatio;
const extraPadding = (adjustedWidth - contentWidth) / 2;
// 当容器更宽时,以内容高度为基准进行缩放
const adjustedWidth = contentHeight * containerRatio; // 调整后的宽度
const extraPadding = (adjustedWidth - contentWidth) / 2; // 计算额外需要的水平内边距
// 返回viewBox参数:x起点、y起点、宽度、高度
return `${minX - padding - extraPadding} ${minY - padding} ${adjustedWidth} ${contentHeight}`;
} else {
// 容器更高,以宽度为基准
const adjustedHeight = containerRatio ? contentWidth / containerRatio : contentHeight;
const extraPadding = isFinite(adjustedHeight) ? (adjustedHeight - contentHeight) / 2 : 0;
// 当容器更高时,以内容宽度为基准进行缩放
const adjustedHeight = containerRatio ? contentWidth / containerRatio : contentHeight; // 调整后的高度
const extraPadding = isFinite(adjustedHeight) ? (adjustedHeight - contentHeight) / 2 : 0; // 计算额外需要的垂直内边距
// 返回viewBox参数:x起点、y起点、宽度、高度
return `${minX - padding} ${minY - padding - extraPadding} ${contentWidth} ${adjustedHeight}`;
}
});
......