ต้องการ สร้าง หรือ แก้ไข ภาพ SVG โดยเราสามารถทำการ add text และสามารถ คลิกเพื่อเลื่อน Object ที่เราเห็นในไฟล์รูปภาพนามสกุล SVG ไปยังตำแหน่งต่างๆได้

- user เลือกไฟล์ภาพ SVG มันจะถูกนำขึ้นมาแสดงที่พื้นที่ด้านขวา
- user สามารถ add text เพื่อแทรกตัวอักษรที่ต้องการลงในภาพ
- user สามารถคลิกบน text ที่สร้างขึ้นมาแล้วลากมันไปวางที่ตำแหน่งที่ต้องการ
- user กด download เพื่อ save ภาพที่ผ่านการแก้ไข ลงเป็นไฟล์ใหม่
- user กด reset and clear เพื่อลบภาพออกจากพื้นที่แสดงผล
HTML ส่วน control button
มี control อยู่ทั้งหมด 1 file button + 3 buttons + 1 textbox
1. file button -> id = svg-upload
2. button -> id = add-text-btn
3. text -> id = text-input
4. button -> id = download-btn
5. button -> id = reset-btn
<!-- Controls Column -->
<aside class="lg:col-span-1 bg-white p-6 rounded-lg shadow-lg h-fit">
<h2 class="text-2xl font-bold mb-4 border-b pb-2">Controls</h2>
<!-- File Upload -->
<div class="mb-6">
<label for="svg-upload" class="block text-sm font-medium text-slate-700 mb-2">1. Load SVG File</label>
<input type="file" id="svg-upload" accept=".svg,image/svg+xml" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"/>
<p class="mt-1 text-xs text-slate-500">Elements with class="draggable" will be movable.</p>
</div>
<!-- Add Text -->
<div class="mb-6">
<label for="text-input" class="block text-sm font-medium text-slate-700 mb-2">2. Add Text</label>
<input type="text" id="text-input" placeholder="Enter text to add" class="block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm placeholder-slate-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500">
<button id="add-text-btn" class="mt-2 w-full bg-blue-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors">Add Text</button>
</div>
<!-- Save/Reset -->
<div>
<label class="block text-sm font-medium text-slate-700 mb-2">3. Save or Reset</label>
<button id="download-btn" class="w-full bg-green-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-green-700 transition-colors">Download New SVG File</button>
<button id="reset-btn" class="mt-2 w-full bg-red-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-red-700 transition-colors">Reset and Clear</button>
</div>
</aside>
HTML ส่วนแสดงผล – เป็น div svg-container ธรรมดาเท่านั้น
<!-- SVG Display Column -->
<main class="lg:col-span-2 bg-white p-2 rounded-lg shadow-lg">
<div id="svg-container" class="w-full h-[600px]">
<span id="svg-placeholder" class="text-slate-500 text-center p-4">Upload an SVG file to begin editing.</span>
</div>
</main>
เพิ่ม Javascript สำหรับ control button
1. loadState – สำหรับโหลดภาพที่เปิดทิ้งเอาไว้ล่าสุดขึ้นมา + รวมถึงมีการแก้ไขเอาไว้ด้วย
2. saveState – เก็บภาพที่แก้ไขล่าสุดเอาไว้เอาไว้เผื่อตอนเปิดครั้งหน้า
3. actionListener – reset button : click – ลบภาพออกจากหน้าจอ
4. displaySvg
5. actionListener – file input : change
6. initializeDraggable – สำหรับ add mouse listener (startDrag) ให้กับ svg element ที่มี class draggable ทุกตัว
7. getMousePosition – สำหรับหาตำแหน่งที่ mouse อยู่
8. startDrag – ย้ายตำแหน่งตาม Mouse + add mouse listener สำหรับ (drag + endDrag)
9. drag – ย้ายตำแหน่งตาม Mouse
10. endDrag – end drag แล้วก็ลบ mouse listener ออก จะได้ไม่ซ้ำซ้อน เวลาเริ่ม move ใหม่
11. actionListener – add text button : click
12. actionListener – download button : click
ไฟล์เต็ม
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive SVG Editor</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Style for the SVG container */
#svg-container {
width: 100%;
height: 100%;
border: 2px dashed #cbd5e1;
border-radius: 0.5rem;
background-color: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden; /* Important for SVG scaling */
}
#svg-container.has-content {
border-style: solid;
border-color: #94a3b8;
}
/* Add a grabbing cursor when an element is being dragged */
#svg-container.dragging {
cursor: grabbing;
}
/* Add a move cursor to elements with the 'draggable' class */
#svg-container .draggable {
cursor: grab;
user-select: none;
}
</style>
</head>
<body class="bg-slate-100 text-slate-800">
<div class="container mx-auto p-4 md:p-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-slate-900">Interactive SVG Editor</h1>
<p class="mt-2 text-lg text-slate-600">Upload, drag, add text, and save your SVG. Your work is saved in the browser automatically.</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Controls Column -->
<aside class="lg:col-span-1 bg-white p-6 rounded-lg shadow-lg h-fit">
<h2 class="text-2xl font-bold mb-4 border-b pb-2">Controls</h2>
<!-- File Upload -->
<div class="mb-6">
<label for="svg-upload" class="block text-sm font-medium text-slate-700 mb-2">1. Load SVG File</label>
<input type="file" id="svg-upload" accept=".svg,image/svg+xml" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"/>
<p class="mt-1 text-xs text-slate-500">Elements with class="draggable" will be movable.</p>
</div>
<!-- Add Text -->
<div class="mb-6">
<label for="text-input" class="block text-sm font-medium text-slate-700 mb-2">2. Add Text</label>
<input type="text" id="text-input" placeholder="Enter text to add" class="block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm placeholder-slate-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500">
<button id="add-text-btn" class="mt-2 w-full bg-blue-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors">Add Text</button>
</div>
<!-- Save/Reset -->
<div>
<label class="block text-sm font-medium text-slate-700 mb-2">3. Save or Reset</label>
<button id="download-btn" class="w-full bg-green-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-green-700 transition-colors">Download New SVG File</button>
<button id="reset-btn" class="mt-2 w-full bg-red-600 text-white font-bold py-2 px-4 rounded-lg hover:bg-red-700 transition-colors">Reset and Clear</button>
</div>
</aside>
<!-- SVG Display Column -->
<main class="lg:col-span-2 bg-white p-2 rounded-lg shadow-lg">
<div id="svg-container" class="w-full h-[600px]">
<span id="svg-placeholder" class="text-slate-500 text-center p-4">Upload an SVG file to begin editing.</span>
</div>
</main>
</div>
</div>
<script>
window.onload = () => {
// DOM Elements
const svgContainer = document.getElementById('svg-container');
const svgPlaceholder = document.getElementById('svg-placeholder');
const fileInput = document.getElementById('svg-upload');
const textInput = document.getElementById('text-input');
const addTextBtn = document.getElementById('add-text-btn');
const downloadBtn = document.getElementById('download-btn');
const resetBtn = document.getElementById('reset-btn');
// SVG Manipulation State
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
let activeElement = null;
let offset = { x: 0, y: 0 };
let currentSVG = null;
// --- LOCAL STORAGE & INITIALIZATION ---
/**
* Loads SVG content from localStorage if it exists.
*/
function loadState() {
const savedSVG = localStorage.getItem('savedUserSVG');
if (savedSVG) {
displaySVG(savedSVG);
}
}
/**
* Saves the current SVG content to localStorage.
*/
function saveState() {
if (currentSVG) {
const svgString = new XMLSerializer().serializeToString(currentSVG);
localStorage.setItem('savedUserSVG', svgString);
console.log("SVG state saved.");
}
}
/**
* Resets the application state and clears localStorage.
*/
resetBtn.addEventListener('click', () => {
if (confirm("Are you sure you want to clear your current work? This cannot be undone.")) {
localStorage.removeItem('savedUserSVG');
svgContainer.innerHTML = '';
svgContainer.appendChild(svgPlaceholder);
svgContainer.classList.remove('has-content');
currentSVG = null;
}
});
// --- SVG DISPLAY & FILE HANDLING ---
/**
* Injects SVG string into the container and sets up interactivity.
* @param {string} svgString - The SVG content as a string.
*/
function displaySVG(svgString) {
svgContainer.innerHTML = svgString;
currentSVG = svgContainer.querySelector('svg');
if (currentSVG) {
svgContainer.classList.add('has-content');
// Ensure the SVG scales within the container
currentSVG.setAttribute('width', '100%');
currentSVG.setAttribute('height', '100%');
initializeDraggable(currentSVG);
} else {
svgContainer.classList.remove('has-content');
}
}
/**
* Handles the file upload event.
*/
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file && file.type === "image/svg+xml") {
const reader = new FileReader();
reader.onload = (e) => {
displaySVG(e.target.result);
saveState(); // Save the newly loaded SVG as the current state
};
reader.readAsText(file);
} else {
alert("Please upload a valid .svg file.");
}
});
// --- DRAG AND DROP LOGIC ---
/**
* Sets up mousedown listeners on all 'draggable' elements within the SVG.
* @param {SVGElement} svgElement - The root SVG element.
*/
function initializeDraggable(svgElement) {
const draggables = svgElement.querySelectorAll('.draggable');
draggables.forEach(el => {
el.addEventListener('mousedown', startDrag);
});
}
/**
* Converts screen coordinates to SVG coordinates.
* @param {MouseEvent} event - The mouse event.
* @returns {{x: number, y: number}} The coordinates within the SVG space.
*/
function getMousePosition(event) {
const CTM = currentSVG.getScreenCTM();
return {
x: (event.clientX - CTM.e) / CTM.a,
y: (event.clientY - CTM.f) / CTM.d
};
}
/**
* Initiates the drag operation.
* @param {MouseEvent} event - The mousedown event.
*/
function startDrag(event) {
event.preventDefault();
if (event.target.classList.contains('draggable')) {
activeElement = event.target;
svgContainer.classList.add('dragging');
offset = getMousePosition(event);
// Handle existing transforms
const transform = activeElement.transform.baseVal;
if (transform.length === 0 || transform.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) {
const translate = currentSVG.createSVGTransform();
translate.setTranslate(0, 0);
activeElement.transform.baseVal.insertItemBefore(translate, 0);
}
const initialTransform = transform.getItem(0).matrix;
offset.x -= initialTransform.e;
offset.y -= initialTransform.f;
// Add listeners to the whole container for smoother dragging
svgContainer.addEventListener('mousemove', drag);
svgContainer.addEventListener('mouseup', endDrag);
svgContainer.addEventListener('mouseleave', endDrag);
}
}
/**
* Performs the drag translation.
* @param {MouseEvent} event - The mousemove event.
*/
function drag(event) {
if (activeElement) {
event.preventDefault();
const coord = getMousePosition(event);
const transform = activeElement.transform.baseVal.getItem(0);
transform.setTranslate(coord.x - offset.x, coord.y - offset.y);
}
}
/**
* Ends the drag operation and saves the state.
*/
function endDrag() {
if (activeElement) {
activeElement = null;
svgContainer.classList.remove('dragging');
svgContainer.removeEventListener('mousemove', drag);
svgContainer.removeEventListener('mouseup', endDrag);
svgContainer.removeEventListener('mouseleave', endDrag);
saveState(); // Save state after modification
}
}
// --- ADD TEXT LOGIC ---
addTextBtn.addEventListener('click', () => {
const textContent = textInput.value;
if (!textContent || !currentSVG) {
alert("Please load an SVG and enter some text first.");
return;
}
const newText = document.createElementNS(SVG_NAMESPACE, "text");
const viewBox = currentSVG.viewBox.baseVal;
const randomX = viewBox.x + viewBox.width / 2;
const randomY = viewBox.y + viewBox.height / 2;
newText.setAttribute("x", randomX);
newText.setAttribute("y", randomY);
newText.setAttribute("font-size", "20");
newText.setAttribute("font-family", "Inter, sans-serif");
newText.setAttribute("fill", "#1e293b");
newText.setAttribute("text-anchor", "middle");
newText.classList.add('draggable'); // Make new text draggable by default
newText.textContent = textContent;
currentSVG.appendChild(newText);
initializeDraggable(currentSVG); // Re-initialize to include the new text element
saveState(); // Save state after adding text
textInput.value = ''; // Clear input field
});
// --- DOWNLOAD LOGIC ---
downloadBtn.addEventListener('click', () => {
if (!currentSVG) {
alert("There is no SVG to download. Please load one first.");
return;
}
const svgData = new XMLSerializer().serializeToString(currentSVG);
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "modified-svg-image.svg";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
// --- INITIAL LOAD ---
loadState();
};
</script>
</body>
</html>

