[英]Proper way to change a SVG <text> without changing its children <tspan>
考慮這個簡單的 SVG,它有一個包含一些<tspan>
的<text>
元素:
<svg> <text x="10" y="30"> Foo Bar Baz <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> </text> </svg>
現在假設我們想要更改文本本身的值(在本例中為"Foo Bar Baz" ),同時保留其<tspan>
后代。 如果我們使用textContent
、 innerHTML
(現代瀏覽器支持 SVG 的innerHTML
)等將替換所有內容,包括子項:
document.querySelector("text").textContent = "New Foo";
<svg> <text x="10" y="30"> Foo Bar Baz <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> </text> </svg>
一種解決方法是獲取子節點的第一個 NodeList 元素,通常是文本本身:
document.querySelector("text").childNodes[0].textContent = "New Foo";
<svg> <text x="10" y="30"> Foo Bar Baz <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> </text> </svg>
但是,此解決方法非常麻煩。 “為什么?” ,您可能會問……好吧,在集合中使用硬編碼索引對我來說已經夠老套了。 另外,就像我上面寫的,集合中的第一個元素通常是文本本身,但我不知道這是否依賴於實現,因此,新瀏覽器可以簡單地更改集合中的順序,實現不同的順序.
因此,我的問題標題中的正確方法是:是否有屬性或任何其他方法可以專門更改 SVG <text>
元素中的文本內容?
如我所見,您有兩種選擇。 1)建立在你尋找文本節點的最后一個例子上。 您知道有一個包含內容的文本節點。 您可以測試<text>
的所有子節點以查看它是否是文本節點,然后替換該節點。 2) 將文本節點變成一個<tspan>
元素,就像其他子元素一樣。 這在視覺上不會有任何區別,同時如果需要的話,更容易引用元素和樣式。 添加此元素的“成本”是最小的。
document.querySelector("text >.first").textContent = "New Foo";
<svg> <text x="10" y="30"> <tspan class="first">Foo Bar Baz</tspan> <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> </text> </svg>
您可以在循環中使用nodeType
屬性。 重要的是要注意解析器可以添加其他文本節點,因此您還必須選擇您需要的一個。 一個簡單的實現將第一個節點替換為nodeType===3
。 考慮到其他類型 3 節點將具有像\n
這樣的簡單文本,其他實現也是可能的。 這將替換具有某些字母的第一個文本(不是tspan
)節點:
const r = document.querySelector("text"); let hasText = /\w/; for (x = 0; x < r.childNodes.length; x++){ if (r.childNodes[x].nodeType === 3 && hasText.test(r.childNodes[x].textContent)){ r.childNodes[x].textContent = "New Foo"; break; } }
<svg> <text x="10" y="30"> <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> Foo Bar Baz </text> </svg>
與其他人不同的方法。 直接使用Element.replaceChildren()替換Element.children節點(不兼容 IE):
const textNode = document.querySelector('text'); function updateTextContent(node, text) { node.replaceChildren(text, ...node.children); } updateTextContent(textNode, 'New Foo');
<svg> <text x="10" y="30"> Foo Bar Baz <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> </text> </svg>
如果需要支持IE,替換textContent屬性值,然后使用append添加子節點:
function updateTextContent(node, text) {
const children = [...node.children];
node.textContent = "";
node.append(text, ...children);
}
“是否有屬性或任何其他方法可以專門更改文本內容?”
不,可能不是。 事實上,我認為不存在這種方法可能更好。 例如,您的<text>
節點如下所示:
<text>
Foo
<tspan>Bar</tspan>
Baz
</text>
現在我們將調用 unknown 方法將文本更改為Hello world
。 期望的結果是什么?
<text>
Hello world
<tspan>Bar</tspan>
</text>
<text>
<tspan>Bar</tspan>
Hello world
</text>
甚至
<text>
Hello
<tspan>Bar</tspan>
world
</text>
這樣看,很明顯“文本內容”不能被視為節點樹之外存在的特例:就像您示例中的<tspan>
一樣,任何文本節點中都可以有多個訂單和數量。 因此,很難證明只更改文本節點的特殊方法是合理的,而不引入只更改所有<tspan>
、 <div>
等的方法。
如果您將所有文本節點視為某種無類型元素,如下所示:
<text>
<text>Foo</text>
<tspan>Bar</tspan>
<text>Baz</text>
</text>
毫無疑問,您將如何定位和更改您想要的元素。 選項包括:
Foo
確實永遠是第一個元素。 但就像 CSS 中的:first
選擇器一樣,您可能不想如此嚴格地依賴於特定結構。class="text"
屬性會非常清楚,但是您需要將文本包裝在實際元素中(這可能是最好的解決方案,但我在這里遵循您的規范)。因此,下面的代碼片段將是更改子列表中第一個文本節點的完全有效的方式(以功能方式編寫以保持簡潔)。
Array.from(document.querySelector("text").childNodes).find(e => e.nodeType === Node.TEXT_NODE).textContent = "New Foo";
<svg> <text x="10" y="30"> Foo Bar Baz <tspan x="30" dy="30">FooBar</tspan> <tspan x="30" dy="30">FooBaz</tspan> </text> </svg>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.