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

DeepSeek生成绘制SVG的H5页面

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

通过对话,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

相关推荐

如何设计一个优秀的电子商务产品详情页

加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...

怎么在JS中使用Ajax进行异步请求?

大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...

中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革

前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...

前端监控 SDK 开发分享_前端监控系统 开源

一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...

Ajax 会被 fetch 取代吗?Axios 怎么办?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...

前端面试题《AJAX》_前端面试ajax考点汇总

1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...

Ajax 详细介绍_ajax

1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...

6款可替代dreamweaver的工具_替代powerdesigner的工具

dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...

我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

福斯《死侍》发布新剧照 &quot;小贱贱&quot;韦德被改造前造型曝光

时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...

不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!

Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...

2025 年 Python 爬虫四大前沿技术:从异步到 AI

作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...

最贱超级英雄《死侍》来了!_死侍超燃

死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...

停止javascript的ajax请求,取消axios请求,取消reactfetch请求

一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...