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

DeepSeek生成绘制SVG的H5页面

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

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

相关推荐

泰国野猪足球队一17岁队员在英去世,曾被困洞穴18天后奇迹获救

泰国网图当地时间2月14日,现年17岁的泰国野猪队队员多姆(Dom,本名DuangpetchPromthep)在英国去世,他曾于2018年被困于洞穴18天后奇迹获救。据英国广播公司(BBC)报道,...

你需要知道的 19 个 console 实用调试技巧

大家好,我是Echa。之前给大家介绍了《H5移动端调试攻略——超实用》,有兴趣的小伙们可以回过头看看。浏览器的开发者工具为我们提供了强大的调试系统,可以用来查看DOM树结构、CSS样式调试、动画调试...

深圳嘉华学校:什么是JQuery?_深圳嘉华职业技术学校

什么是JQuery?这里将由北大青鸟深圳嘉华来介绍下关于JQuery部分知识,希望能让大家对JQuery有初步的映象。JQuery是继prototype之后又一个优秀的Javascript库。它是轻量...

Vue3 实现一个简单的放大动画_vue放大图片

设计思路定位动画我们在之前已经实现了。那么这里只要考虑如何实现放大动画,最后将两者结合起来就好。从后端拿到的返回值是一个固定长度的数组,所以这里还是用div利用flex布局将图片平铺展示,利用...

JavaScript 事件循环机制详解_js事件循环队列

记录、分享IT相关知识和见闻!想要了解更多软件相关知识的朋友!记得右上角添加【关注】,支持一下!JavaScript是单线程语言,意味着同一时间只能执行一个任务。为了处理异步操作(如定时器、网络请求...

前端性能优化新维度:渲染流水线深度解析

当开发者沉迷于框架选型和语法特性时,浏览器渲染引擎正在以每秒60帧的速度执行着精密计算。本文将揭示现代浏览器的渲染流水线工作原理,探索超越传统性能优化的新思路。一、渲染流水线的五大阶段1.JavaSc...

一组动漫人物插画,浓烈的光与影超棒,插画师DOM

...

如果看未来,DOM应该也不是答案_如果知道未来

Managershare:未来,还会有连通APP的APP。不过,一切都不会基于网页。有一个词"手机网站"(mobileweb),指供手机浏览的网站,但它是不存在的。人们提到"移动互联网"的时候,其实...

Springboot之登录模块探索(含Token,验证码,网络安全等知识)

简介登录模块很简单,前端发送账号密码的表单,后端接收验证后即可~淦!可是我想多了,于是有了以下几个问题(里面还包含网络安全问题):1.登录时的验证码2.自动登录的实现3.怎么维护前后端登录状态在这和大...

总结100+前端优质库,让你成为前端百事通

1年多时间,陆陆续续整理了一些常用且实用的开源项目,方便大家更高效地学习和工作.js相关库js常用工具类「lodash」一个一致性、模块化、高性能的JavaScript实用工具库。「xij...

基于ssm的XATU实验室安全管理系统 [SSM]-计算机毕业设计源码+文档

摘要:实验室安全管理是高校和科研机构工作中的重要环节。本文介绍了基于SSM(Spring+SpringMVC+MyBatis)框架的XATU实验室安全管理系统。该系统涵盖系统用户管理、安全教...

Dynamics.js – 创建逼真的物理动画的 JS 库

Dynamics.js是一个用于创建物理动画JavaScript库。你只需要把dynamics.js引入你的页面,然后就可以激活任何DOM元素的CSS属性动画,也可以结合SVG使...

Vue3 神级工具:终于可以实现打字的动画效果了!

Typed.js是一个轻量级的JavaScript库,用于在网页上实现打字机动画效果。它支持自定义打字速度、循环模式、回调函数等,非常适合用于动态展示标语、代码片段或交互式文本效果。核心特性打字...

创建酷炫动画效果的10个JavaScript库

Dynamics.js是设计基于物理规律的动画的重要JavaScript库。它可以赋予生命给所有包含CSS和SVG属性的DOM(文本对象模型)元素,换句话说,Dynamics.js适用于所有Java...

《速度与激情》动画剧首曝剧照,12月26日奈飞上线

新京报讯11月19日,《速度与激情》动画剧《速度与激情:间谍赛车手》发布首批剧照,并宣布将于12月26日在奈飞上线。该剧由范·迪塞尔担任制片人,他的女儿SimiliceDiesel加盟配音。此外,...