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;
}
// 导出SVG
function 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编辑器。