万普插件库

jQuery插件大全与特效教程

DeepSeek生成绘制SVG的H5页面

通过对话,deepseek能辅助完成一个绘图H5页面。如下:


完成之后的效果如下:


如上

咨询步骤


  1. 给选中的图形加轮廓线

选中多线段后出现虚线轮廓

移动时,轮廓线没有移动

  1. 要求轮廓然随拖动移动



  1. 增加右键菜单,用来删除选中对象


添加右键菜单


完整代码




  
  
  SVG Drawing Toolbox
  


  

SVG Drawing Toolbox

<script> const svgContainer = document.getElementById("svg-container"); const codeViewer = document.getElementById("code-viewer"); const contextMenu = document.getElementById("context-menu"); let currentTool = null; let startX, startY; let currentElement = null; // 当前正在绘制的元素 let selectedElements = []; // 存储选中的图形 let isDragging = false; let dragStartX, dragStartY; // 在全局变量中添加虚线预览元素 let previewLine = null; // 线段绘制相关状态 let isDrawingLine = false; // 是否正在绘制线段 let initialPoint = null; // 初始点 let lastPoint = null; // 上一个起始点 let currentLineGroup = null; // 当前线段的组 // Undo/Redo 相关状态 let historyStack = []; // 操作历史栈 let redoStack = []; // 重做栈 // 工具箱事件监听 document.getElementById("select-btn").addEventListener("click", () => { currentTool = "select"; resetLineDrawing(); }); document.getElementById("rect-btn").addEventListener("click", () => { currentTool = "rect"; resetLineDrawing(); }); document.getElementById("circle-btn").addEventListener("click", () => { currentTool = "circle"; resetLineDrawing(); }); document.getElementById("line-btn").addEventListener("click", () => { currentTool = "line"; resetLineDrawing(); }); document.getElementById("text-btn").addEventListener("click", () => { currentTool = "text"; resetLineDrawing(); }); document.getElementById("undo-btn").addEventListener("click", () => { undo(); }); document.getElementById("redo-btn").addEventListener("click", () => { redo(); }); // 自定义上下文菜单事件监听 document.getElementById("end-line").addEventListener("click", () => { resetLineDrawing(); hideContextMenu(); }); document.getElementById("close-loop").addEventListener("click", () => { if (isDrawingLine && initialPoint && lastPoint) { drawLineSegment(lastPoint.x, lastPoint.y, initialPoint.x, initialPoint.y); resetLineDrawing(); } hideContextMenu(); }); document.getElementById("select-drag").addEventListener("click", () => { currentTool = "select"; hideContextMenu(); }); document.getElementById("delete-element").addEventListener("click", () => { // 删除选中的元素 selectedElements.forEach(element => { element.remove(); removeSelectionBorder(element); }); selectedElements = []; saveState(); // 保存状态 updateCodeViewer(); hideContextMenu(); }); function releasePreview(){ if(previewLine){ previewLine.remove(); previewLine = null; } } // 显示自定义上下文菜单 function showContextMenu(x, y) { contextMenu.style.display = "block"; contextMenu.style.left = `${x}px`; contextMenu.style.top = `${y}px`; // 显示或隐藏删除按钮 const deleteButton = document.getElementById("delete-element"); if (selectedElements.length > 0) { deleteButton.style.display = "block"; } else { deleteButton.style.display = "none"; } } // 隐藏自定义上下文菜单 function hideContextMenu() { contextMenu.style.display = "none"; } // 鼠标事件监听 svgContainer.addEventListener("mousedown", (event) => { const x = event.offsetX; const y = event.offsetY; if (event.button === 2) { // 右键点击,显示自定义上下文菜单 event.preventDefault(); showContextMenu(event.clientX, event.clientY); return; } if (currentTool === "select") { // 选择工具:选中图形 console.log(event.target); if (event.target.tagName === "rect" || event.target.tagName === "circle" || event.target.tagName === "text" || event.target.tagName === "line" || event.target.tagName === "g") { var element = event.target; if ((event.target.tagName === "line") && (event.target.parentNode && event.target.parentNode.tagName === "g")) { element = event.target.parentNode; } console.log(element); // 按住 Shift 键多选 if (!event.shiftKey) { selectedElements.forEach(el => { el.classList.remove("selected"); removeSelectionBorder(el); // 移除选中边框 }); selectedElements = []; } if (!selectedElements.includes(element)) { selectedElements.push(element); element.classList.add("selected"); addSelectionBorder(element); // 添加选中边框 } // 开始拖动 isDragging = true; dragStartX = event.offsetX; dragStartY = event.offsetY; } else { // 点击空白区域取消选中 selectedElements.forEach(el => { el.classList.remove("selected"); removeSelectionBorder(el); // 移除选中边框 }); selectedElements = []; } } else if (currentTool === "text") { // 文字工具 const text = prompt("Enter text:"); if (text) { const textElement = document.createElementNS("http://www.w3.org/2000/svg", "text"); textElement.setAttribute("x", x); textElement.setAttribute("y", y); textElement.setAttribute("fill", document.getElementById("stroke-color").value); textElement.setAttribute("font-size", "20"); textElement.textContent = text; svgContainer.appendChild(textElement); saveState(); // 保存状态 updateCodeViewer(); } } else if (currentTool === "line") { if (event.detail === 2) { // 双击清空初始点 resetLineDrawing(); } else if (event.button === 0) { // 左键点击 if (!isDrawingLine) { // 第一次点击,设置初始点和起始点 initialPoint = { x, y }; lastPoint = { x, y }; isDrawingLine = true; // 创建新的线段组 currentLineGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); svgContainer.appendChild(currentLineGroup); } else { // 后续点击,绘制线段并更新起始点 drawLineSegment(lastPoint.x, lastPoint.y, x, y); lastPoint = { x, y }; // 更新起始点 } } } else if (currentTool === "rect" || currentTool === "circle") { // 矩形或圆形工具 startX = x; startY = y; switch (currentTool) { case "rect": currentElement = document.createElementNS("http://www.w3.org/2000/svg", "rect"); currentElement.setAttribute("x", startX); currentElement.setAttribute("y", startY); break; case "circle": currentElement = document.createElementNS("http://www.w3.org/2000/svg", "circle"); currentElement.setAttribute("cx", startX); currentElement.setAttribute("cy", startY); break; } if (currentElement) { currentElement.setAttribute("stroke", document.getElementById("stroke-color").value); currentElement.setAttribute("stroke-width", document.getElementById("stroke-width").value); currentElement.setAttribute("fill", document.getElementById("fill-color").value); svgContainer.appendChild(currentElement); } } }); svgContainer.addEventListener("mousemove", (event) => { if (currentTool === "select" && isDragging) { // 选择工具:拖动图形 const dx = event.offsetX - dragStartX; const dy = event.offsetY - dragStartY; selectedElements.forEach(element => { if (element.tagName === "rect") { const x = parseFloat(element.getAttribute("x")) + dx; const y = parseFloat(element.getAttribute("y")) + dy; element.setAttribute("x", x); element.setAttribute("y", y); } else if (element.tagName === "circle") { const cx = parseFloat(element.getAttribute("cx")) + dx; const cy = parseFloat(element.getAttribute("cy")) + dy; element.setAttribute("cx", cx); element.setAttribute("cy", cy); } else if (element.tagName === "text") { const x = parseFloat(element.getAttribute("x")) + dx; const y = parseFloat(element.getAttribute("y")) + dy; element.setAttribute("x", x); element.setAttribute("y", y); } else if (element.tagName === "line") { const x1 = parseFloat(element.getAttribute("x1")) + dx; const y1 = parseFloat(element.getAttribute("y1")) + dy; const x2 = parseFloat(element.getAttribute("x2")) + dx; const y2 = parseFloat(element.getAttribute("y2")) + dy; element.setAttribute("x1", x1); element.setAttribute("y1", y1); element.setAttribute("x2", x2); element.setAttribute("y2", y2); } else if (element.tagName === "g") { // 移动整个线段组 Array.from(element.children).forEach(line => { const x1 = parseFloat(line.getAttribute("x1")) + dx; const y1 = parseFloat(line.getAttribute("y1")) + dy; const x2 = parseFloat(line.getAttribute("x2")) + dx; const y2 = parseFloat(line.getAttribute("y2")) + dy; line.setAttribute("x1", x1); line.setAttribute("y1", y1); line.setAttribute("x2", x2); line.setAttribute("y2", y2); }); } updateSelectionBorder(element); // 更新选中边框位置 }); dragStartX = event.offsetX; dragStartY = event.offsetY; updateCodeViewer(); } else if ((currentTool === "rect" || currentTool === "circle") && currentElement) { // 矩形或圆形工具:调整图形大小 const currentX = event.offsetX; const currentY = event.offsetY; switch (currentTool) { case "rect": currentElement.setAttribute("width", Math.abs(currentX - startX)); currentElement.setAttribute("height", Math.abs(currentY - startY)); break; case "circle": const radius = Math.sqrt(Math.pow(currentX - startX, 2) + Math.pow(currentY - startY, 2)); currentElement.setAttribute("r", radius); break; } updateCodeViewer(); }else if (currentTool === "line" && isDrawingLine && lastPoint) { // 线段工具:绘制虚线预览 if (!previewLine) { previewLine = document.createElementNS("http://www.w3.org/2000/svg", "line"); previewLine.setAttribute("stroke", "#000000"); // 虚线颜色 previewLine.setAttribute("stroke-width", document.getElementById("stroke-width").value); previewLine.setAttribute("stroke-dasharray", "5,5"); // 虚线样式 previewLine.setAttribute("fill", "none"); svgContainer.appendChild(previewLine); } const x = event.offsetX; const y = event.offsetY; console.log(lastPoint,x,y) previewLine.setAttribute("x1", lastPoint.x); previewLine.setAttribute("y1", lastPoint.y); previewLine.setAttribute("x2", x); previewLine.setAttribute("y2", y); } }); svgContainer.addEventListener("mouseup", () => { if (currentTool === "select") { isDragging = false; } else if (currentTool === "rect" || currentTool === "circle") { currentElement = null; saveState(); // 保存状态 }else if (currentTool === "line") { // 移除虚线预览 releasePreview(); } }); // 绘制线段 function drawLineSegment(x1, y1, x2, y2) { if (!currentLineGroup) { // 如果 currentLineGroup 未初始化,则创建一个新的组 currentLineGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); svgContainer.appendChild(currentLineGroup); } const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute("x1", x1); line.setAttribute("y1", y1); line.setAttribute("x2", x2); line.setAttribute("y2", y2); line.setAttribute("stroke", document.getElementById("stroke-color").value); line.setAttribute("stroke-width", document.getElementById("stroke-width").value); currentLineGroup.appendChild(line); saveState(); // 保存状态 updateCodeViewer(); } // 重置线段绘制状态 function resetLineDrawing() { isDrawingLine = false; initialPoint = null; lastPoint = null; currentLineGroup = null; // 移除虚线预览 releasePreview(); } // 更新代码查看器 function updateCodeViewer() { const serializer = new XMLSerializer(); const svgCode = serializer.serializeToString(svgContainer); codeViewer.value = svgCode; } // 保存当前状态到历史栈 function saveState() { const serializer = new XMLSerializer(); const svgCode = serializer.serializeToString(svgContainer); historyStack.push(svgCode); redoStack = []; // 清空重做栈 } // 撤销操作 function undo() { if (historyStack.length > 1) { redoStack.push(historyStack.pop()); // 将当前状态移到重做栈 const previousState = historyStack[historyStack.length - 1]; svgContainer.innerHTML = previousState; // 恢复到上一个状态 updateCodeViewer(); } } // 重做操作 function redo() { if (redoStack.length > 0) { const nextState = redoStack.pop(); // 从重做栈中取出下一个状态 historyStack.push(nextState); // 将状态移回历史栈 svgContainer.innerHTML = nextState; // 恢复到下一个状态 updateCodeViewer(); } } // 添加选中边框 function addSelectionBorder(element) { const bbox = element.getBBox(); const padding = 5; // 边框比图形大 2px const border = document.createElementNS("http://www.w3.org/2000/svg", "rect"); border.setAttribute("x", bbox.x - padding); border.setAttribute("y", bbox.y - padding); border.setAttribute("width", bbox.width + 2 * padding); border.setAttribute("height", bbox.height + 2 * padding); border.setAttribute("stroke", "#FF0000"); // 红色虚线 border.setAttribute("stroke-width", "2"); border.setAttribute("stroke-dasharray", "5,5"); border.setAttribute("fill", "none"); border.classList.add("selection-border"); svgContainer.appendChild(border); element.border = border; // 将边框引用存储在元素上 } // 移除选中边框 function removeSelectionBorder(element) { if (element && element.border) { element.border.remove(); delete element.border; } } // 更新选中边框位置 function updateSelectionBorder(element) { if (element && element.border) { const bbox = element.getBBox(); const padding = 2; // 边框比图形大 2px element.border.setAttribute("x", bbox.x - padding); element.border.setAttribute("y", bbox.y - padding); element.border.setAttribute("width", bbox.width + 2 * padding); element.border.setAttribute("height", bbox.height + 2 * padding); } } // 点击页面其他区域隐藏上下文菜单 document.addEventListener("click", () => { hideContextMenu(); }); // 阻止浏览器默认右键菜单 document.addEventListener("contextmenu", (event) => { event.preventDefault(); }); </script>

参考资料

上述页面的完整代码在gitee上,路径如下:

https://gitee.com/wapuboy/learning-programming-with-gauss/blob/master/code/javascript/src/svg.html

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言