hookehuyr

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

1 <!-- 1 <!--
2 * @Date: 2025-03-28 09:23:04 2 * @Date: 2025-03-28 09:23:04
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-28 23:33:13 4 + * @LastEditTime: 2025-03-29 00:31:56
5 * @FilePath: /mlaj/src/views/animation.vue 5 * @FilePath: /mlaj/src/views/animation.vue
6 * @Description: 贝塞尔曲线动画路径组件 6 * @Description: 贝塞尔曲线动画路径组件
7 * 7 *
...@@ -115,8 +115,11 @@ import img_svg3 from "@/assets/3.svg"; ...@@ -115,8 +115,11 @@ import img_svg3 from "@/assets/3.svg";
115 import img_svg4 from "@/assets/4.svg"; 115 import img_svg4 from "@/assets/4.svg";
116 import img_svg5 from "@/assets/5.svg"; 116 import img_svg5 from "@/assets/5.svg";
117 117
118 -// 定义节点坐标数组,每个节点包含x和y坐标 118 +/**
119 -// 这些点将用于生成贝塞尔曲线的路径 119 + * 节点坐标数组
120 + * 每个元素是一个包含x和y坐标的对象
121 + * 这些点将用于生成贝塞尔曲线的路径
122 + */
120 const points = ref([ 123 const points = ref([
121 { x: 700, y: 50 }, 124 { x: 700, y: 50 },
122 { x: 300, y: 300 }, 125 { x: 300, y: 300 },
...@@ -125,16 +128,32 @@ const points = ref([ ...@@ -125,16 +128,32 @@ const points = ref([
125 { x: 700, y: 1000 }, 128 { x: 700, y: 1000 },
126 ]); 129 ]);
127 130
128 -// 当前激活的节点索引,初始值为-1表示没有节点被激活 131 +/**
132 + * activeNodeIndex 是一个响应式数据,用于存储当前激活的节点索引。
133 + * 初始值为 -1,表示没有节点被激活。
134 + * 当用户点击节点时,该索引会被更新为当前节点的索引。
135 + * 当用户点击下一步按钮时,该索引会被更新为下一个节点的索引。
136 + */
129 const activeNodeIndex = ref(-1); 137 const activeNodeIndex = ref(-1);
130 138
131 -// 存储已激活的路径段数组,每个元素是一个SVG路径字符串 139 +/**
132 -// 用于显示高亮的贝塞尔曲线路径 140 + * activePathSegments 是一个响应式数据,用于存储已激活的路径段数组。
141 + * 初始值为空数组,表示没有路径段被激活。
142 + * 当用户点击下一步按钮时,该数组会被更新为包含当前节点和下一个节点之间的路径段。
143 + * 存储已激活的路径段数组,每个元素是一个SVG路径字符串
144 + * 用于显示高亮的贝塞尔曲线路径
145 + */
133 const activePathSegments = ref([]); 146 const activePathSegments = ref([]);
134 147
135 -// 计算完整的贝塞尔曲线路径 148 +/**
136 -// 使用三次贝塞尔曲线(Cubic Bezier)创建平滑的曲线效果 149 + * 计算完整的贝塞尔曲线路径
137 -// 控制点的位置通过当前点和下一个点的中点来计算 150 + * 该函数使用 points 数组中的点来生成完整的贝塞尔曲线路径
151 + * 每个点之间的路径段通过 calculatePathSegment 函数计算得到
152 + * 控制点的位置通过当前点和下一个点的中点来计算
153 + * 返回一个包含所有路径段的字符串
154 + * 用于在SVG中显示完整的路径
155 + * @returns 返回SVG路径字符串
156 + */
138 const pathData = computed(() => { 157 const pathData = computed(() => {
139 const path = []; 158 const path = [];
140 points.value.forEach((point, index) => { 159 points.value.forEach((point, index) => {
...@@ -149,19 +168,23 @@ const pathData = computed(() => { ...@@ -149,19 +168,23 @@ const pathData = computed(() => {
149 return path.join(" "); 168 return path.join(" ");
150 }); 169 });
151 170
152 -// 计算两点之间的贝塞尔曲线路径段 171 +/**
153 -// 该函数使用三次贝塞尔曲线算法生成平滑的曲线路径: 172 + * 该函数使用三次贝塞尔曲线算法生成平滑的曲线路径, 计算两点之间的贝塞尔曲线路径段:
154 -// 1. 计算路径的方向向量和单位向量 173 + * 1. 计算路径的方向向量和单位向量
155 -// 2. 添加垂直偏移以创建弧形效果 174 + * 2. 添加垂直偏移以创建弧形效果
156 -// 3. 根据路径方向动态调整控制点 175 + * 3. 根据路径方向动态调整控制点
157 -// 4. 生成标准的SVG路径命令字符串 176 + * 4. 生成标准的SVG路径命令字符串
158 -// @param startPoint - 起始点坐标 {x, y} 177 + * @param startPoint - 起始点坐标 {x, y}
159 -// @param endPoint - 结束点坐标 {x, y} 178 + * @param endPoint - 结束点坐标 {x, y}
160 -// @returns 返回SVG路径字符串 179 + * @returns 返回SVG路径字符串
180 + */
161 const calculatePathSegment = (startPoint, endPoint) => { 181 const calculatePathSegment = (startPoint, endPoint) => {
162 // 计算路径的方向向量 182 // 计算路径的方向向量
183 + // 计算起点和终点之间的水平距离差
163 const dx = endPoint.x - startPoint.x; 184 const dx = endPoint.x - startPoint.x;
185 + // 计算起点和终点之间的垂直距离差
164 const dy = endPoint.y - startPoint.y; 186 const dy = endPoint.y - startPoint.y;
187 + // 使用勾股定理计算两点之间的直线距离
165 const distance = Math.sqrt(dx * dx + dy * dy); 188 const distance = Math.sqrt(dx * dx + dy * dy);
166 189
167 // 设置节点间的偏移距离(可以根据需要调整) 190 // 设置节点间的偏移距离(可以根据需要调整)
...@@ -173,26 +196,43 @@ const calculatePathSegment = (startPoint, endPoint) => { ...@@ -173,26 +196,43 @@ const calculatePathSegment = (startPoint, endPoint) => {
173 const unitY = dy / distance; 196 const unitY = dy / distance;
174 197
175 // 计算偏移后的起点和终点,根据路径方向调整垂直偏移 198 // 计算偏移后的起点和终点,根据路径方向调整垂直偏移
199 + // 根据路径方向调整起点坐标
200 + // x坐标: 在原始起点基础上沿单位向量方向偏移
201 + // y坐标: 根据路径是向上还是向下来决定垂直偏移的方向
176 const adjustedStart = { 202 const adjustedStart = {
177 x: startPoint.x + unitX * offset, 203 x: startPoint.x + unitX * offset,
178 y: startPoint.y + (dy > 0 ? -verticalOffset : verticalOffset), 204 y: startPoint.y + (dy > 0 ? -verticalOffset : verticalOffset),
179 }; 205 };
206 +
207 + // 根据路径方向调整终点坐标
208 + // x坐标: 在原始终点基础上沿单位向量反方向偏移
209 + // y坐标: 与起点相反的垂直偏移方向,确保曲线的平滑过渡
180 const adjustedEnd = { 210 const adjustedEnd = {
181 x: endPoint.x - unitX * offset, 211 x: endPoint.x - unitX * offset,
182 y: endPoint.y + (dy > 0 ? verticalOffset : -verticalOffset), 212 y: endPoint.y + (dy > 0 ? verticalOffset : -verticalOffset),
183 }; 213 };
184 214
185 // 计算控制点 215 // 计算控制点
216 + // 计算第一个控制点的x坐标 - 与起点x坐标相同
186 const cpx1 = adjustedStart.x; 217 const cpx1 = adjustedStart.x;
218 + // 计算第一个控制点的y坐标 - 在起点和终点y坐标的中点
187 const cpy1 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5; 219 const cpy1 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5;
220 + // 计算第二个控制点的x坐标 - 与终点x坐标相同
188 const cpx2 = adjustedEnd.x; 221 const cpx2 = adjustedEnd.x;
222 + // 计算第二个控制点的y坐标 - 与第一个控制点y坐标相同,确保平滑过渡
189 const cpy2 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5; 223 const cpy2 = adjustedStart.y + (adjustedEnd.y - adjustedStart.y) * 0.5;
190 224
191 return `M ${adjustedStart.x} ${adjustedStart.y} C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${adjustedEnd.x} ${adjustedEnd.y}`; 225 return `M ${adjustedStart.x} ${adjustedStart.y} C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${adjustedEnd.x} ${adjustedEnd.y}`;
192 }; 226 };
193 227
194 -// 未激活路径的颜色 228 +/**
195 -// 使用浅灰色(#ccc)表示未激活状态 229 + * 计算未激活路径的颜色
230 + * 该函数根据当前激活的节点索引来计算路径的颜色
231 + * 当没有节点被激活时,路径颜色为浅灰色(#ccc)
232 + * 当有节点被激活时,路径颜色为绿色(#4CAF50)
233 + * 返回路径颜色的CSS字符串
234 + * @returns 返回路径颜色的CSS字符串
235 + */
196 const pathColor = computed(() => { 236 const pathColor = computed(() => {
197 return "#ccc"; 237 return "#ccc";
198 }); 238 });
...@@ -215,25 +255,35 @@ const pathColor = computed(() => { ...@@ -215,25 +255,35 @@ const pathColor = computed(() => {
215 // } 255 // }
216 // }; 256 // };
217 257
218 -// 处理下一步按钮点击事件 258 +/**
219 -// 该函数实现了动画的核心交互逻辑: 259 + * 处理下一步按钮点击事件
220 -// 1. 按顺序激活下一个节点 260 + * 该函数实现了动画的核心交互逻辑:
221 -// 2. 创建新的高亮路径段并添加到动画序列 261 + * 1. 按顺序激活下一个节点
222 -// 3. 更新节点状态和路径显示 262 + * 2. 创建新的高亮路径段并添加到动画序列
223 -// 4. 当到达最后一个节点时自动禁用按钮 263 + * 3. 更新节点状态和路径显示
224 -// 5. 确保动画流程的连贯性和用户体验 264 + * 4. 当到达最后一个节点时自动禁用按钮
265 + * 5. 确保动画流程的连贯性和用户体验
266 + */
225 const nextStep = () => { 267 const nextStep = () => {
268 + // 检查是否还有下一个节点可以激活
226 if (activeNodeIndex.value < points.value.length - 1) { 269 if (activeNodeIndex.value < points.value.length - 1) {
270 + // 激活下一个节点
227 activeNodeIndex.value++; 271 activeNodeIndex.value++;
272 +
273 + // 获取当前激活节点的坐标
228 const startPoint = points.value[activeNodeIndex.value]; 274 const startPoint = points.value[activeNodeIndex.value];
275 + // 获取下一个节点的坐标
229 const endPoint = points.value[activeNodeIndex.value + 1]; 276 const endPoint = points.value[activeNodeIndex.value + 1];
277 +
230 // 只有当存在下一个节点时才添加新的路径段 278 // 只有当存在下一个节点时才添加新的路径段
231 if (endPoint) { 279 if (endPoint) {
280 + // 计算当前节点到下一个节点的贝塞尔曲线路径
232 const newSegment = calculatePathSegment(startPoint, endPoint); 281 const newSegment = calculatePathSegment(startPoint, endPoint);
233 - // 保留之前的路径段,添加新的路径段 282 + // 将新的路径段添加到激活路径数组中,保留之前的路径段
234 activePathSegments.value.push(newSegment); 283 activePathSegments.value.push(newSegment);
235 } 284 }
236 - // 如果是最后一个节点,直接将其设置为激活状态 285 +
286 + // 如果当前是最后一个节点,确保其状态为激活
237 if (activeNodeIndex.value === points.value.length - 1) { 287 if (activeNodeIndex.value === points.value.length - 1) {
238 activeNodeIndex.value = points.value.length - 1; 288 activeNodeIndex.value = points.value.length - 1;
239 } 289 }
...@@ -288,10 +338,14 @@ const svgObj = [ ...@@ -288,10 +338,14 @@ const svgObj = [
288 }, 338 },
289 }, 339 },
290 ]; 340 ];
291 -// 计算SVG容器尺寸 341 +
292 -// 该函数用于获取动画容器的实际尺寸,以便于后续计算SVG视图框 342 +/**
293 -// 返回一个包含容器宽度和高度的对象 343 + * 计算SVG容器尺寸
294 -// 如果容器不存在,则返回默认值{width: 0, height: 0} 344 + * 该函数用于获取动画容器的实际尺寸,以便于后续计算SVG视图框
345 + * 返回一个包含容器宽度和高度的对象
346 + * 如果容器不存在,则返回默认值{width: 0, height: 0}
347 + * @returns 返回SVG容器尺寸的对象
348 + */
295 const containerSize = computed(() => { 349 const containerSize = computed(() => {
296 const container = document.querySelector('.animation-container'); 350 const container = document.querySelector('.animation-container');
297 if (!container) return { width: 0, height: 0 }; 351 if (!container) return { width: 0, height: 0 };
...@@ -318,37 +372,48 @@ onUnmounted(() => { ...@@ -318,37 +372,48 @@ onUnmounted(() => {
318 window.removeEventListener('resize', updateContainerSize); 372 window.removeEventListener('resize', updateContainerSize);
319 }); 373 });
320 374
321 -// 计算并更新SVG视图框(viewBox) 375 +/**
322 -// 该函数动态计算SVG视图框的尺寸和位置,以确保: 376 + * 计算并更新SVG视图框(viewBox)
323 -// 1. 所有节点都在可视区域内 377 + * 该函数根据当前节点的位置和容器尺寸来计算SVG视图框的尺寸和位置
324 -// 2. 保持适当的宽高比 378 + * 1. 所有节点都在可视区域内
325 -// 3. 根据容器尺寸自适应调整 379 + * 2. 保持适当的宽高比
326 -// 4. 在不同屏幕尺寸下保持良好的显示效果 380 + * 3. 根据容器尺寸自适应调整
381 + * 4. 在不同屏幕尺寸下保持良好的显示效果
382 + * 返回一个包含视图框尺寸和位置的字符串
383 + * 格式为"minX minY width height"
384 + * 用于设置SVG的viewBox属性
385 + * @returns 返回SVG视图框的字符串
386 + */
327 const svgViewBox = computed(() => { 387 const svgViewBox = computed(() => {
388 + // 计算所有节点中的最大和最小坐标值
328 const maxX = Math.max(...points.value.map(p => p.x)); 389 const maxX = Math.max(...points.value.map(p => p.x));
329 const maxY = Math.max(...points.value.map(p => p.y)); 390 const maxY = Math.max(...points.value.map(p => p.y));
330 const minX = Math.min(...points.value.map(p => p.x)); 391 const minX = Math.min(...points.value.map(p => p.x));
331 const minY = Math.min(...points.value.map(p => p.y)); 392 const minY = Math.min(...points.value.map(p => p.y));
393 + // 设置内边距,确保节点不会贴近边缘
332 const padding = 100; 394 const padding = 100;
333 395
396 + // 计算内容区域的实际尺寸
397 + const contentWidth = maxX - minX + padding * 2; // 内容宽度 = 最大x - 最小x + 两侧内边距
398 + const contentHeight = maxY - minY + padding * 2; // 内容高度 = 最大y - 最小y + 上下内边距
334 // 计算内容的宽高比 399 // 计算内容的宽高比
335 - const contentWidth = maxX - minX + padding * 2;
336 - const contentHeight = maxY - minY + padding * 2;
337 const contentRatio = contentWidth / contentHeight; 400 const contentRatio = contentWidth / contentHeight;
338 401
339 // 获取容器的宽高比 402 // 获取容器的宽高比
340 const containerRatio = containerSize.value.width / containerSize.value.height; 403 const containerRatio = containerSize.value.width / containerSize.value.height;
341 404
342 - // 根据宽高比调整viewBox 405 + // 根据容器和内容的宽高比例关系调整viewBox
343 if (containerRatio > contentRatio) { 406 if (containerRatio > contentRatio) {
344 - // 容器更宽,以高度为基准 407 + // 当容器更宽时,以内容高度为基准进行缩放
345 - const adjustedWidth = contentHeight * containerRatio; 408 + const adjustedWidth = contentHeight * containerRatio; // 调整后的宽度
346 - const extraPadding = (adjustedWidth - contentWidth) / 2; 409 + const extraPadding = (adjustedWidth - contentWidth) / 2; // 计算额外需要的水平内边距
410 + // 返回viewBox参数:x起点、y起点、宽度、高度
347 return `${minX - padding - extraPadding} ${minY - padding} ${adjustedWidth} ${contentHeight}`; 411 return `${minX - padding - extraPadding} ${minY - padding} ${adjustedWidth} ${contentHeight}`;
348 } else { 412 } else {
349 - // 容器更高,以宽度为基准 413 + // 当容器更高时,以内容宽度为基准进行缩放
350 - const adjustedHeight = containerRatio ? contentWidth / containerRatio : contentHeight; 414 + const adjustedHeight = containerRatio ? contentWidth / containerRatio : contentHeight; // 调整后的高度
351 - const extraPadding = isFinite(adjustedHeight) ? (adjustedHeight - contentHeight) / 2 : 0; 415 + const extraPadding = isFinite(adjustedHeight) ? (adjustedHeight - contentHeight) / 2 : 0; // 计算额外需要的垂直内边距
416 + // 返回viewBox参数:x起点、y起点、宽度、高度
352 return `${minX - padding} ${minY - padding - extraPadding} ${contentWidth} ${adjustedHeight}`; 417 return `${minX - padding} ${minY - padding - extraPadding} ${contentWidth} ${adjustedHeight}`;
353 } 418 }
354 }); 419 });
......