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

DeepSeek生成绘制SVG的H5页面

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

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

相关推荐

以文本的方式绘制简单的SVG流程图——flowchart.js

介绍flowchart.js是在浏览器和终端中运行的流程图DSL和SVG渲染。节点和连接是分别定义的,因此可以重复使用节点,并可以快速更改连接。也可以在DSL中对节点和连接器样式进行细微的更改。Git...

全国首套构网型SVG在木垒投运

中新网新疆新闻1月5日电(翟文辉)12月29日,全国首套构网型SVG在新疆木垒华电220千伏四十个井子汇集站并网,本项目是新疆电网继阿克陶构网型储能后又一次构网型支撑项目示范。为全面响应国家“双碳”...

Popmotion – 小巧,灵活的 JS 运动引擎

Popmotion是一个只有12KB的JavaScript运动引擎,可以用来实现动画,物理效果和输入跟踪。原生的DOM支持:CSS,SVG,SVG路径和DOM属性的支持,开箱即用。Popmoti...

零基础教你学前端——43、初识SVG

解决网站图标问题的最佳方案——SVG!SVG是一种基于XML语法的图像格式,英文全称是:ScalableVectorGraphics,即可缩放矢量图,是W3C的一项建议。我们用手机拍摄...

2.3 文件格式全解:PSD/JPG/PNG/SVG/GIF

2.3文件格式全解:PSD/JPG/PNG/SVG/GIF一、文件格式的核心意义文件格式是数字图像的存储规则,决定了:-信息保留程度(图层/透明度/动画)-压缩方式与画质损失-跨平台兼容性-...

vite v6.3.2 发布!HMR 优化+CSS 增强+稳定性提升,前端开发再提速!

前言:Vite6.3.2来了!2025年4月18日,Vite团队正式发布了v6.3.2版本!虽然是一个小版本更新,但修复了多个关键问题,并带来了性能优化和稳定性提升,让开发体验更丝滑!如果你还...

一篇文章带你了解SVG 蒙版(Mask)

SVG蒙版功能可将蒙版应用于SVG形状。蒙版可确定SVG形状的哪些部分可见,以及具有什么透明度。运行效果可以将SVG蒙版视为剪切路径的更高级版本。一、简单的蒙版代码解析:本示例使用ID=mask1定义...

SVG实现的流程图绘制

一、项目简介使用SVG技术实现的流程图绘制二、实现功能流程图块生成、连线、拖拽产生相应的xml和xpdl导入导出json数据放大缩小功能保存操作(选择、自动插入、开始结束、普通活动、子活动、块活动、路...

解锁国内 404 页面:Next.js 设置指南和 33 个有趣 SVG 资源分享

前言当我们访问网站时,如果访问到不存在的路径时,会出现404错误。为了避免给访问者带来不良体验,设计网站时通常会在页面上展示“404页面不存在”的提示,并引导用户进行返回首页等操作。因此在建立网...

交互设计师做好动画后,提交给开发的文档有哪些?

谢邀!简单的说一下自己的看法。首先从制作动画开始。目前制作动画的方式主要有:Gif动画视频动画Web动画,而Web动画又包括:CSS动画、JS动画(Canvas动画、原生JS动画API)、SVG动画等...

Motion for Vue:为Vue量身定制的强大动画库

在前端开发中,动画效果是提升用户体验的重要手段。Vue生态系统中虽然有许多动画库,但真正能做到高性能、易用且功能丰富的并不多。今天,我们要介绍的是MotionforVue(motion-v),...

Web开发人员的福音!8个实用的SVG工具

SVG可缩放矢量图形(ScalableVectorGraphics)是基于可扩展标记语言(XML),用于描述二维矢量图形的一种图形格式。SVG是W3C在2000年8月制定的一种新的二维矢量图形格式...

一键画波浪线、一键多图片调色?这3个网站好玩到停不下来

作为一个经常收集网站的PPT设计师,无意中发现了一些超级有趣的网站。只要你动手能力足够强,就一定会利用它做出创意作品。不说废话,直接进入主题。1、炫酷的光线绘画网站http://weavesilk.c...

vite 6.2.5 更新速递:告别SVG路径Bug,构建效率再提升!

Vite6.2.5更新公告2025年4月3日,Vite团队正式发布了Vite6.2.5版本!此次更新虽然是一个小版本迭代,但修复了一个关键问题,涉及SVG文件路径检查,对前端开发者尤...

DrawSVG – SVG 路径动画 jQuery 插件

jQueryDrawSVG使用了jQuery内置的动画引擎实现SVG路径动画,用到了stroke-dasharray和stroke-dashoffset属性。DrawSVG是完全...