SVG 编辑器 HTML
以下是一个简单的基于HTML/JavaScript的SVG编辑器代码。这个编辑器允许你绘制基本形状(矩形、圆形、线条等),修改它们的属性,并导出SVG代码。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>简易SVG编辑器</title><style>body {font-family: Arial, sans-serif;margin: 0;padding: 20px;display: flex;flex-direction: column;height: 100vh;}.container {display: flex;flex: 1;gap: 20px;}#toolbar {display: flex;gap: 10px;margin-bottom: 20px;padding: 10px;background: #f0f0f0;border-radius: 5px;}button {padding: 8px 12px;cursor: pointer;}#editor-panel {flex: 3;border: 1px solid #ccc;position: relative;}#svg-canvas {width: 100%;height: 100%;background-color: white;}#properties-panel {flex: 1;border: 1px solid #ccc;padding: 15px;overflow-y: auto;}.property-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;font-weight: bold;}input, select {width: 100%;padding: 8px;margin-bottom: 10px;box-sizing: border-box;}#svg-code {width: 100%;height: 200px;margin-top: 20px;font-family: monospace;}.shape {cursor: move;}.shape.selected {outline: 2px dashed #0066ff;}</style>
</head>
<body><h1>简易SVG编辑器</h1><div id="toolbar"><button id="select-btn">选择</button><button id="rect-btn">矩形</button><button id="circle-btn">圆形</button><button id="ellipse-btn">椭圆</button><button id="line-btn">线条</button><button id="text-btn">文本</button><button id="delete-btn">删除</button><button id="export-btn">导出SVG</button></div><div class="container"><div id="editor-panel"><svg id="svg-canvas" xmlns="http://www.w3.org/2000/svg"></svg></div><div id="properties-panel"><h3>属性编辑器</h3><div id="shape-properties"><p>选择一个元素来编辑其属性</p></div><h3>SVG代码</h3><textarea id="svg-code" readonly></textarea><button id="copy-code-btn">复制代码</button></div></div><script>document.addEventListener('DOMContentLoaded', function() {const svgCanvas = document.getElementById('svg-canvas');const shapeProperties = document.getElementById('shape-properties');const svgCodeTextarea = document.getElementById('svg-code');let selectedElement = null;let isDrawing = false;let currentTool = 'select';let startX, startY;// 工具按钮事件监听document.getElementById('select-btn').addEventListener('click', () => currentTool = 'select');document.getElementById('rect-btn').addEventListener('click', () => currentTool = 'rect');document.getElementById('circle-btn').addEventListener('click', () => currentTool = 'circle');document.getElementById('ellipse-btn').addEventListener('click', () => currentTool = 'ellipse');document.getElementById('line-btn').addEventListener('click', () => currentTool = 'line');document.getElementById('text-btn').addEventListener('click', () => currentTool = 'text');document.getElementById('delete-btn').addEventListener('click', deleteSelected);document.getElementById('export-btn').addEventListener('click', exportSVG);document.getElementById('copy-code-btn').addEventListener('click', copySVGCode);// SVG画布事件监听svgCanvas.addEventListener('mousedown', startDrawing);svgCanvas.addEventListener('mousemove', draw);svgCanvas.addEventListener('mouseup', endDrawing);svgCanvas.addEventListener('mouseleave', endDrawing);// 开始绘制function startDrawing(e) {if (currentTool === 'select') {const target = e.target.closest('.shape');if (target) {selectElement(target);} else {deselectElement();}return;}isDrawing = true;const rect = svgCanvas.getBoundingClientRect();startX = e.clientX - rect.left;startY = e.clientY - rect.top;if (currentTool === 'text') {createText(startX, startY);isDrawing = false;}}// 绘制过程中function draw(e) {if (!isDrawing || currentTool === 'select' || currentTool === 'text') return;const rect = svgCanvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;if (!selectedElement) {// 创建新元素switch (currentTool) {case 'rect':selectedElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');break;case 'circle':selectedElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');break;case 'ellipse':selectedElement = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');break;case 'line':selectedElement = document.createElementNS('http://www.w3.org/2000/svg', 'line');selectedElement.setAttribute('x1', startX);selectedElement.setAttribute('y1', startY);break;}if (selectedElement) {selectedElement.classList.add('shape');svgCanvas.appendChild(selectedElement);updateShapeProperties();}}// 更新元素属性if (selectedElement) {switch (currentTool) {case 'rect':selectedElement.setAttribute('x', Math.min(startX, x));selectedElement.setAttribute('y', Math.min(startY, y));selectedElement.setAttribute('width', Math.abs(x - startX));selectedElement.setAttribute('height', Math.abs(y - startY));break;case 'circle':const radius = Math.sqrt(Math.pow(x - startX, 2) + Math.pow(y - startY, 2));selectedElement.setAttribute('cx', startX);selectedElement.setAttribute('cy', startY);selectedElement.setAttribute('r', radius);break;case 'ellipse':const rx = Math.abs(x - startX);const ry = Math.abs(y - startY);selectedElement.setAttribute('cx', startX);selectedElement.setAttribute('cy', startY);selectedElement.setAttribute('rx', rx);selectedElement.setAttribute('ry', ry);break;case 'line':selectedElement.setAttribute('x2', x);selectedElement.setAttribute('y2', y);break;}// 设置默认样式if (!selectedElement.getAttribute('fill')) {selectedElement.setAttribute('fill', 'blue');}if (!selectedElement.getAttribute('stroke')) {selectedElement.setAttribute('stroke', 'black');}if (!selectedElement.getAttribute('stroke-width')) {selectedElement.setAttribute('stroke-width', '2');}updateShapeProperties();}}// 结束绘制function endDrawing() {isDrawing = false;}// 创建文本元素function createText(x, y) {deselectElement();selectedElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');selectedElement.classList.add('shape');selectedElement.setAttribute('x', x);selectedElement.setAttribute('y', y);selectedElement.setAttribute('fill', 'black');selectedElement.textContent = '双击编辑文本';svgCanvas.appendChild(selectedElement);selectedElement.addEventListener('dblclick', function() {const newText = prompt('输入文本:', this.textContent);if (newText !== null) {this.textContent = newText;updateShapeProperties();updateSVGCode();}});selectElement(selectedElement);updateShapeProperties();updateSVGCode();}// 选择元素function selectElement(element) {deselectElement();selectedElement = element;selectedElement.classList.add('selected');updateShapeProperties();updateSVGCode();// 添加拖拽功能let isDragging = false;let offsetX, offsetY;selectedElement.addEventListener('mousedown', startDrag);function startDrag(e) {if (currentTool !== 'select') return;isDragging = true;const rect = selectedElement.getBoundingClientRect();offsetX = e.clientX - rect.left;offsetY = e.clientY - rect.top;e.preventDefault(); // 防止文本选择}svgCanvas.addEventListener('mousemove', drag);svgCanvas.addEventListener('mouseup', endDrag);svgCanvas.addEventListener('mouseleave', endDrag);function drag(e) {if (!isDragging || !selectedElement) return;const rect = svgCanvas.getBoundingClientRect();const x = e.clientX - rect.left - offsetX;const y = e.clientY - rect.top - offsetY;if (selectedElement.tagName === 'text') {selectedElement.setAttribute('x', x);selectedElement.setAttribute('y', y);} else if (selectedElement.tagName === 'circle') {selectedElement.setAttribute('cx', x + offsetX);selectedElement.setAttribute('cy', y + offsetY);} else if (selectedElement.tagName === 'ellipse') {selectedElement.setAttribute('cx', x + offsetX);selectedElement.setAttribute('cy', y + offsetY);} else if (selectedElement.tagName === 'line') {const x1 = parseFloat(selectedElement.getAttribute('x1'));const y1 = parseFloat(selectedElement.getAttribute('y1'));const x2 = parseFloat(selectedElement.getAttribute('x2'));const y2 = parseFloat(selectedElement.getAttribute('y2'));const dx = x - (x1 + x2) / 2 + offsetX;const dy = y - (y1 + y2) / 2 + offsetY;selectedElement.setAttribute('x1', x1 + dx);selectedElement.setAttribute('y1', y1 + dy);selectedElement.setAttribute('x2', x2 + dx);selectedElement.setAttribute('y2', y2 + dy);} else if (selectedElement.tagName === 'rect') {selectedElement.setAttribute('x', x);selectedElement.setAttribute('y', y);}updateShapeProperties();updateSVGCode();}function endDrag() {isDragging = false;}}// 取消选择元素function deselectElement() {if (selectedElement) {selectedElement.classList.remove('selected');selectedElement = null;shapeProperties.innerHTML = '<p>选择一个元素来编辑其属性</p>';}}// 删除选中元素function deleteSelected() {if (selectedElement) {svgCanvas.removeChild(selectedElement);deselectElement();updateSVGCode();}}// 更新形状属性面板function updateShapeProperties() {if (!selectedElement) return;let html = '<div class="property-group">';html += `<h4>${selectedElement.tagName}</h4>`;// 通用属性html += `<label>填充颜色</label><input type="color" id="fill-color" value="${selectedElement.getAttribute('fill') || 'none'}"><label>描边颜色</label><input type="color" id="stroke-color" value="${selectedElement.getAttribute('stroke') || 'none'}"><label>描边宽度</label><input type="number" id="stroke-width" value="${selectedElement.getAttribute('stroke-width') || '1'}" min="0" step="0.5">`;// 特定属性switch (selectedElement.tagName) {case 'rect':html += `<label>X坐标</label><input type="number" id="rect-x" value="${selectedElement.getAttribute('x')}" step="0.1"><label>Y坐标</label><input type="number" id="rect-y" value="${selectedElement.getAttribute('y')}" step="0.1"><label>宽度</label><input type="number" id="rect-width" value="${selectedElement.getAttribute('width')}" min="0" step="0.1"><label>高度</label><input type="number" id="rect-height" value="${selectedElement.getAttribute('height')}" min="0" step="0.1">`;break;case 'circle':html += `<label>中心X</label><input type="number" id="circle-cx" value="${selectedElement.getAttribute('cx')}" step="0.1"><label>中心Y</label><input type="number" id="circle-cy" value="${selectedElement.getAttribute('cy')}" step="0.1"><label>半径</label><input type="number" id="circle-r" value="${selectedElement.getAttribute('r')}" min="0" step="0.1">`;break;case 'ellipse':html += `<label>中心X</label><input type="number" id="ellipse-cx" value="${selectedElement.getAttribute('cx')}" step="0.1"><label>中心Y</label><input type="number" id="ellipse-cy" value="${selectedElement.getAttribute('cy')}" step="0.1"><label>X半径</label><input type="number" id="ellipse-rx" value="${selectedElement.getAttribute('rx')}" min="0" step="0.1"><label>Y半径</label><input type="number" id="ellipse-ry" value="${selectedElement.getAttribute('ry')}" min="0" step="0.1">`;break;case 'line':html += `<label>起点X</label><input type="number" id="line-x1" value="${selectedElement.getAttribute('x1')}" step="0.1"><label>起点Y</label><input type="number" id="line-y1" value="${selectedElement.getAttribute('y1')}" step="0.1"><label>终点X</label><input type="number" id="line-x2" value="${selectedElement.getAttribute('x2')}" step="0.1"><label>终点Y</label><input type="number" id="line-y2" value="${selectedElement.getAttribute('y2')}" step="0.1">`;break;case 'text':html += `<label>X坐标</label><input type="number" id="text-x" value="${selectedElement.getAttribute('x')}" step="0.1"><label>Y坐标</label><input type="number" id="text-y" value="${selectedElement.getAttribute('y')}" step="0.1"><label>文本内容</label><input type="text" id="text-content" value="${selectedElement.textContent}"><label>字体大小</label><input type="number" id="text-font-size" value="${selectedElement.getAttribute('font-size') || '16'}" min="1">`;break;}html += '</div>';shapeProperties.innerHTML = html;// 添加事件监听器document.getElementById('fill-color').addEventListener('input', function() {selectedElement.setAttribute('fill', this.value);updateSVGCode();});document.getElementById('stroke-color').addEventListener('input', function() {selectedElement.setAttribute('stroke', this.value);updateSVGCode();});document.getElementById('stroke-width').addEventListener('input', function() {selectedElement.setAttribute('stroke-width', this.value);updateSVGCode();});// 特定属性事件监听switch (selectedElement.tagName) {case 'rect':document.getElementById('rect-x').addEventListener('input', function() {selectedElement.setAttribute('x', this.value);updateSVGCode();});document.getElementById('rect-y').addEventListener('input', function() {selectedElement.setAttribute('y', this.value);updateSVGCode();});document.getElementById('rect-width').addEventListener('input', function() {selectedElement.setAttribute('width', this.value);updateSVGCode();});document.getElementById('rect-height').addEventListener('input', function() {selectedElement.setAttribute('height', this.value);updateSVGCode();});break;case 'circle':document.getElementById('circle-cx').addEventListener('input', function() {selectedElement.setAttribute('cx', this.value);updateSVGCode();});document.getElementById('circle-cy').addEventListener('input', function() {selectedElement.setAttribute('cy', this.value);updateSVGCode();});document.getElementById('circle-r').addEventListener('input', function() {selectedElement.setAttribute('r', this.value);updateSVGCode();});break;case 'ellipse':document.getElementById('ellipse-cx').addEventListener('input', function() {selectedElement.setAttribute('cx', this.value);updateSVGCode();});document.getElementById('ellipse-cy').addEventListener('input', function() {selectedElement.setAttribute('cy', this.value);updateSVGCode();});document.getElementById('ellipse-rx').addEventListener('input', function() {selectedElement.setAttribute('rx', this.value);updateSVGCode();});document.getElementById('ellipse-ry').addEventListener('input', function() {selectedElement.setAttribute('ry', this.value);updateSVGCode();});break;case 'line':document.getElementById('line-x1').addEventListener('input', function() {selectedElement.setAttribute('x1', this.value);updateSVGCode();});document.getElementById('line-y1').addEventListener('input', function() {selectedElement.setAttribute('y1', this.value);updateSVGCode();});document.getElementById('line-x2').addEventListener('input', function() {selectedElement.setAttribute('x2', this.value);updateSVGCode();});document.getElementById('line-y2').addEventListener('input', function() {selectedElement.setAttribute('y2', this.value);updateSVGCode();});break;case 'text':document.getElementById('text-x').addEventListener('input', function() {selectedElement.setAttribute('x', this.value);updateSVGCode();});document.getElementById('text-y').addEventListener('input', function() {selectedElement.setAttribute('y', this.value);updateSVGCode();});document.getElementById('text-content').addEventListener('input', function() {selectedElement.textContent = this.value;updateSVGCode();});document.getElementById('text-font-size').addEventListener('input', function() {selectedElement.setAttribute('font-size', this.value);updateSVGCode();});break;}}// 更新SVG代码显示function updateSVGCode() {const serializer = new XMLSerializer();const svgString = serializer.serializeToString(svgCanvas);svgCodeTextarea.value = svgString;}// 导出SVGfunction exportSVG() {updateSVGCode();const blob = new Blob([svgCodeTextarea.value], {type: 'image/svg+xml'});const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = 'drawing.svg';document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);}// 复制SVG代码function copySVGCode() {updateSVGCode();svgCodeTextarea.select();document.execCommand('copy');alert('SVG代码已复制到剪贴板');}});</script>
</body>
</html>
功能说明
这个SVG编辑器具有以下功能:
-
绘图工具:
- 选择工具:选择和移动现有元素
- 矩形工具:绘制矩形
- 圆形工具:绘制圆形
- 椭圆工具:绘制椭圆
- 线条工具:绘制直线
- 文本工具:添加文本
-
编辑功能:
- 修改元素的属性(位置、大小、颜色等)
- 双击文本元素可编辑文本内容
- 删除选中的元素
-
导出功能:
- 查看生成的SVG代码
- 复制SVG代码到剪贴板
- 导出SVG文件
使用方法
- 选择左侧工具栏中的绘图工具
- 在画布上点击并拖动来绘制形状
- 使用选择工具点击元素来选中它
- 在右侧属性面板中修改选中元素的属性
- 使用导出按钮保存您的SVG作品
您可以将此代码保存为HTML文件并在浏览器中打开它来使用这个SVG编辑器。