百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

DeepSeek生成绘制SVG的H5页面

myzbx 2025-04-26 19:40 13 浏览

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


完成之后的效果如下:


咨询步骤


  1. 给选中的图形加轮廓线
  1. 要求轮廓然随拖动移动



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



完整代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SVG Drawing Toolbox</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    #toolbox {
      margin-bottom: 10px;
    }
    #toolbox button, #toolbox input {
      margin: 5px;
    }
    #svg-container {
      width: 800px;
      height: 600px;
      border: 1px solid #ccc;
      position: relative;
    }
    #code-viewer {
      width: 800px;
      height: 100px;
      margin-top: 10px;
      font-family: monospace;
    }
    /* 自定义上下文菜单样式 */
    #context-menu {
      position: absolute;
      display: none;
      background-color: white;
      border: 1px solid #ccc;
      box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
      z-index: 1000;
    }
    #context-menu button {
      display: block;
      width: 100%;
      padding: 8px 16px;
      text-align: left;
      border: none;
      background: none;
      cursor: pointer;
    }
    #context-menu button:hover {
      background-color: #f0f0f0;
    }
  </style>
</head>
<body>
  <h1>SVG Drawing Toolbox</h1>
  <div id="toolbox">
    <button id="select-btn">Select</button>
    <button id="rect-btn">Rectangle</button>
    <button id="circle-btn">Circle</button>
    <button id="line-btn">Line</button>
    <button id="text-btn">Text</button>
    <button id="undo-btn">Undo</button>
    <button id="redo-btn">Redo</button>
    <input type="color" id="stroke-color" value="#000000">
    <input type="color" id="fill-color" value="#ffffff">
    <input type="number" id="stroke-width" min="1" max="10" value="2">
  </div>
  <svg id="svg-container" xmlns="http://www.w3.org/2000/svg"></svg>
  <textarea id="code-viewer" readonly></textarea>

  <!-- 自定义上下文菜单 -->
  <div id="context-menu">
    <button id="end-line">结束线段(双击)</button>
    <button id="close-loop">闭环线段(右键)</button>
    <button id="select-drag">选中拖动</button>
    <button id="delete-element" style="display: none;">删除</button>
  </div>

  <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>
</body>
</html>

参考资料

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

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

相关推荐

怎么恢复7z文件 7z文件删除了怎么恢复

7z是一种压缩格式的文件,它运用LZMA压缩算法,该压缩算法的输出稍后被算数编码进行处理以便后续进一步压缩,压缩比十分高。我们可以将文件压缩成这种格式,便于传输,保存,占空间少。了解更多7z文件知识...

郎酒让消费者喝得明明白白 算术题里有答案

日前,『郎酒酱香产品企业内控准则』颁布,郎酒首次公开酱香产品生产全过程,公布酱香产品产能、储能及投放计划。随后,郎酒官微向消费者发出「品控算术题」有奖问答。郎酒亮出家底,消费者踊跃留言。8天后,谜底揭...

学龄前,比识字、算术更重要的是这三件事

“为了给孩子选择一家合适的幼儿园,我曾穿梭于纽约各家幼儿园的开放日,这些幼儿员既包括主流的公立幼儿园,还包括那些遥不可及的私人幼儿园。我的目的就是想了解他们的教育理念是什么,到底厉害在哪里,看看对于我...

参加CSP-J信奥赛需要掌握数学知识

在C++语法的学习中需要储备的数学知识如下①数据类型:需要知道整数、正整数、负整数、小数、判断对错②算术运算符:加法、减法、乘法、除法、取模运算③关系表达式:大于、大于等于、小于、小...

1g米饭能做多少深蹲?今天我们来算一算

减重我们都知道3分在练,7分在吃,吃这件事情上,真的是每一口都算数。今天我们来算一笔账,1粒米饭可以做多少事情?本着认真负责的态度,今天在食物秤上称了1g米饭,是16粒。根据能量换算:100g米饭是4...

web 自动化测试,一定得掌握的 8 个核心知识点

使用cypress进行端对端测试,和其他的一些框架有一个显著不同的地方,它使用JavaScript作为编程语言。传统主流的selenium框架是支持多语言的,大多数QA会的pytho...

大话C语言:赋值运算符(c语言中赋值运算符是什么)

赋值运算符是最基本的运算符之一,用于将右侧的值或表达式的计算结果赋给左侧的变量。它是一个二元运算符,意味着它需要两个操作数:一个是目标变量(左侧),另一个是要赋给该变量的值或表达式(右侧)。赋值运算符...

Vue进阶(幺幺伍):js 将字符串转换为boolean

Boolean();参数为0、null和无参数返回false,有参数返回true。Boolean("");//输出为:falseBoolean(null);//输出为...

mongodb查询的语法(大于,小于,大于或等于,小于或等于等等)

1).大于,小于,大于或等于,小于或等于$gt:大于$lt:小于$gte:大于或等于$lte:小于或等于例子:db.collection.find({"field":{$gt:valu...

Python学不会来打我(21)python表达式知识点汇总

在Python中,表达式是由变量、运算符、函数调用等组合而成的语句,用于产生值或执行特定操作。以下是对Python中常见表达式的详细讲解:1.1算术表达式涉及数学运算的表达式。例如:a=5b...

C|数据存储地址与字节偏移、数据索引

话说C是面向内存的编程语言。数据要能存得进去,取得出来,且要考虑效率。不管是顺序存储还是链式存储,其寻址方式总是很重要。顺序存储是连续存储。同质结构的数组通过其索引表示位置偏移,异质结构的结构体通过其...

下班后累懵?4 个 JS 手写题帮你搞定前端面试高频考点

打工人下班后最痛苦的事,莫过于拖着疲惫的身子还要啃前端面试题吧?看着那些密密麻麻的JS代码,脑子都快转不动了!别担心,今天咱就用轻松的方式,带你吃透4道高频手写题,让你在面试时自信满满,再也不...

嵌入式数据库sqlite3【进阶篇】-子句和函数的使用,小白一文入门

sqlite在《嵌入式数据库sqlite3命令操作基础篇-增删改查,小白一文入门》一文中讲解了如何实现sqlite3的基本操作增删改查,本文介绍一些其他复杂一点的操作。比如where、orderby...

前缀表达式与后缀表达式(前缀表达式后缀表达式中缀表达式计算)

昨天晚上和儿子一起学习了前缀表达式和后缀表达式。这应该是字符串算式如何被计算机识别并计算的2种方法。本来是想先给他讲一个逆波兰式(后缀表达式),以后再讲前缀表达式。没想到他还挺聪明,很快就把2个都掌握...

Python快速入门教程1:基本语法、数据类型、运算符、数字字符串

Python3的基础教程,涵盖了基本语法、数据类型、类型转换、解释器、注释、运算符、数字和字符串等内容,并附有使用实例场景。Python3的基础教程,涵盖了基本语法、数据类型、类型转换、解释器、注释、...