Web

Fix SVG Text Editor: Partial Formatting with tspan

Fix JavaScript SVG text editor issues where formatting applies to entire elements instead of selections. Use tspan for partial styling, prevent text collapse, with step-by-step code using Selection API.

1 answer 9 views

How can I fix a JavaScript text editor for SVG files that incorrectly applies formatting to entire text elements instead of selected portions? My text editor in a card maker application has issues where selecting and formatting only part of text (like ‘QUESTO È UN EFFETTO.’ in ‘EFFETTI: QUESTO È UN EFFETTO.’) affects the entire element. Additionally, when clicking on text and applying formatting, the text collapses. I need a solution that works with partial text selections in SVG elements, as I’m building this with limited JavaScript knowledge.

To fix your SVG text editor’s partial formatting issues, wrap only the selected text portion—like “QUESTO È UN EFFETTO.”—inside a <tspan> element instead of styling the whole <text>. This keeps formatting local and prevents collapse by preserving positioning attributes like x or dy. You’ll use the browser’s Selection API to grab the selection, split the text node, and insert the styled <tspan>—simple enough even with basic JS skills.


Contents


Understanding the Problem

Your card maker’s SVG text editor sounds frustrating—select part of “EFFETTI: QUESTO È UN EFFETTO.”, hit bold or color, and suddenly the whole line changes. Worse, clicking to format makes text squish or vanish. This happens because SVG <text> elements treat their content as one big text node by default. No built-in “rich text” like HTML <span>.

Browsers handle SVG text selection okay-ish via the Selection API, but applying styles directly to <text> overrides everything inside it. You’ve got partial selections working visually, but the JS isn’t splitting that selection into format-friendly chunks. Don’t worry; the fix revolves around <tspan>, SVG’s way to style snippets independently. It’s like <span> for SVG, but you have to insert it manually.


Why Formatting Hits the Whole Text Element

SVG <text> is rigid. Drop in some text, and it’s one atomic blob. Select half with your mouse? The browser highlights it fine, but if your bold button does textElement.style.fontWeight = 'bold', boom—entire element bolded. No partial magic.

Text collapse? That’s positioning gone wrong. <text> uses x, y, dx, dy for layout. Click and format without care, and you might reset those, shoving lines together or off-screen. W3Schools on SVG Text nails it: “Formatting a selection changes the whole <text>—wrap in <tspan> first.” Same for MDN’s tspan docs: it’s for “fine-grained styling or per-fragment positioning.”

In your case, Italian text with accents (“È”) adds no extra quirks—SVG handles Unicode fine. The root? Your editor needs to detect the selection range, extract that substring, and replace it with a styled <tspan> while gluing back the rest.


Core Fix: tspan for Partial Formatting

Enter <tspan>. Nest it inside <text>, and it inherits most styles but lets you override fill, font-weight, etc., just for its content. Want “QUESTO È UN EFFETTO.” bold? Split the original text node into three: before (“EFFETTI: “), the <tspan> (bold selection), and after (”.”). Position them sequentially with x or dx.

Why does this solve collapse? <tspan> can copy the parent’s positioning or add relative offsets like dy="1.2em" for new lines. No more global resets.

Basic structure before:

svg
<text x="10" y="20">EFFETTI: QUESTO È UN EFFETTO.</text>

After bolding the middle:

svg
<text x="10" y="20">
 <tspan>EFFETTI: </tspan>
 <tspan font-weight="bold">QUESTO È UN EFFETTO.</tspan>
 <tspan>.</tspan>
</text>

See? Partial. Scalable. Your SVG stays editable.


Step-by-Step Implementation

Let’s build this gently—no libraries, pure JS. Assume your SVG <text> has id="editableText". Add a button like <button onclick="applyBold()">Bold</button>.

1. Grab the Selection

javascript
function getSelectionInText(textElement) {
 const selection = window.getSelection();
 if (selection.rangeCount === 0) return null;
 
 const range = selection.getRangeAt(0);
 const textNode = textElement.firstChild; // Assuming single text node
 
 if (!range.intersectsNode(textNode)) return null;
 
 const startOffset = Math.max(0, range.startOffset);
 const endOffset = Math.min(textNode.length, range.endOffset);
 
 return { textNode, start: startOffset, end: endOffset, selectedText: textNode.textContent.slice(startOffset, endOffset) };
}

This pulls the exact partial selection inside your <text>.

2. Split and Insert tspan

javascript
function applyFormatting(textElement, styleObj = { fontWeight: 'bold' }) {
 const sel = getSelectionInText(textElement);
 if (!sel) { alert('Select some text first!'); return; }
 
 const { textNode, start, end, selectedText } = sel;
 
 // Split: before + selected + after
 const before = textNode.textContent.slice(0, start);
 const after = textNode.textContent.slice(end);
 
 // Clear old node, add tspans
 textNode.textContent = '';
 textElement.appendChild(document.createTextNode(before));
 
 const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
 tspan.textContent = selectedText;
 Object.assign(tspan.style, styleObj); // e.g., {fontWeight: 'bold', fill: 'red'}
 textElement.appendChild(tspan);
 
 textElement.appendChild(document.createTextNode(after));
 
 selection.removeAllRanges(); // Clear highlight
}

Hook it up: applyBold = () => applyFormatting(document.getElementById('editableText'), {fontWeight: 'bold'});

Test it. Select middle text, click Bold. Partial win! For color: {fill: 'red'}.

3. Handle Multiple tspans

If formatting again? Your function now works on existing ones—Selection API crosses <tspan> boundaries seamlessly.


Alternative: contentEditable Overlay

Splitting tspans too fiddly? Overlay an HTML <div contentEditable> matching your SVG text’s position/font. Edit there WYSIWYG, then sync back to SVG tspans.

From Stack Overflow on inline SVG editing: Position a transparent contenteditable div over the <text>. On blur, parse the HTML (bold spans) and convert to tspans.

Quick sketch:

javascript
// Create overlay
const overlay = document.createElement('div');
overlay.contentEditable = true;
overlay.style.cssText = 'position:absolute; background:transparent; font: inherit; color: inherit; pointer-events:none;'; // Match SVG text
overlay.style.pointerEvents = 'auto'; // For editing

// Position to match SVG text bbox
const bbox = textElement.getBBox();
overlay.style.left = bbox.x + 'px';
// etc.

// On edit end: parse innerHTML, build tspans

Easier for bold/italic lists, but sync logic adds ~20 lines. Great if tspans feel low-level.


Pitfalls and Text Collapse Fixes

Text vanishing post-format? Check positioning. Add x to each <tspan> or use relative dx="0" dy="0". Example:

javascript
tspan.setAttribute('x', textElement.getAttribute('x')); // Inherit start

Whitespace ate? SVG collapses spaces—use xml:space="preserve" on <text>.

Browser quirks: Firefox Selection stricter; test there. Multi-line? Stack <tspan dy="1.2em">.

Medium’s SVG editor tutorial warns: “Emulate line breaks with tspans.” Matches your needs.

Pro tip: After inserting, force reflow with textElement.getBBox() to repaint.


Sources

  1. SVG Text and tspan - W3Schools
  2. tspan - SVG | MDN
  3. Inline text editing in SVG - Stack Overflow
  4. JavaScript text editor for SVG - Medium

Conclusion

Your SVG text editor gets fixed with <tspan> splitting—detect selection, wrap the partial bit, style it solo. Collapse vanishes once positioning sticks. Start with the step-by-step code; it’ll handle “QUESTO È UN EFFETTO.” perfectly. For fancier cards, layer on overlays later. Grab those demos from sources, tweak, and you’re golden. Questions? Drop your SVG snippet for a custom patch.

Authors
Verified by moderation
Moderation
Fix SVG Text Editor: Partial Formatting with tspan