【问题标题】:Proper way to change a SVG <text> without changing its children <tspan>更改 SVG <text> 而不更改其子 <tspan> 的正确方法
【发布时间】:2021-08-05 06:19:36
【问题描述】:

考虑一下这个简单的 SVG,它带有一个 &lt;text&gt; 元素,其中包含一些 &lt;tspan&gt;s:

<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”),同时保留其 &lt;tspan&gt; 后代。如果我们使用textContentinnerHTML(现代浏览器支持innerHTML for SVG)等将替换所有内容,包括儿童:

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 &lt;text&gt; 元素中的文本内容?

【问题讨论】:

  • 您必须更清楚地说明您想要做什么,以避免混淆,从而让人们愿意帮助您发布错误的答案。改进你的问题'你会得到更好的答案。 – SIMBIOSIS 7 分钟前 删除

标签: javascript html svg


【解决方案1】:

在我看来,您有两个选择。 1)在您查找文本节点的最后一个示例的基础上构建。你知道有 one 文本节点的内容。你可以测试&lt;text&gt;的所有childNodes,看看它是否是一个文本节点,然后替换那个节点。 2)您将文本节点转换为&lt;tspan&gt; 元素,就像其他子元素一样。这在视觉上没有任何区别,同时如果需要,更容易引用元素并对其进行样式设置。添加此元素的“成本”很小。

document.querySelector("text &gt; .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>

【讨论】:

  • 我感谢您的努力(因此我的 +1),但尽管 #1 是一个很好的方法(这就是我所做的,顺便说一句),这不是我正在寻找的...什么我在这个问题中寻找的是合适的 SVG 规范,但恐怕没有。
  • 谢谢。如果您看到这样的情况,我们在 HTML 中也有同样的问题——或者更准确地说,在底层 XML 和 SGML 规范中。我不认为这在 SVG 本身中是可以解决的。为了在实践中解决这个问题,我自己总是这么想:“我可以使用 CSS 选择器访问这个内容吗?” -- 这是我的第二个解决方案。
  • 对不起,我之前的评论应该附在问题后面,我把它放在这里了
【解决方案2】:

您可以在循环中使用nodeType property。重要的是要注意解析器可以添加其他文本节点,因此您还必须选择您需要的节点。一个简单的实现将第一个节点替换为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>

【讨论】:

    【解决方案3】:

    与其他方法不同。直接用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);
    }
    

    【讨论】:

      【解决方案4】:

      “是否有一个属性或任何其他方法可以专门更改文本内容?”

      不,可能不会。事实上,我认为这种方法不存在可能更好。例如,假设您的 &lt;text&gt; 节点如下所示:

      <text>
        Foo
        <tspan>Bar</tspan>
        Baz
      </text>
      

      现在我们将调用未知方法将文本更改为Hello world。期望的结果是什么?

      <text>
        Hello world
        <tspan>Bar</tspan>
      </text>
      
      <text>
        <tspan>Bar</tspan>
        Hello world
      </text>
      

      甚至

      <text>
        Hello
        <tspan>Bar</tspan>
        world
      </text>
      

      这样看,很明显“文本内容”不能被视为存在于节点树之外的特殊情况:就像您示例中的&lt;tspan&gt;一样,可以有多个文本任何顺序和数量的节点。因此很难证明一个只改变文本节点的特殊方法是合理的,而不引入只改变所有&lt;tspan&gt;&lt;div&gt;等的方法。

      解决方案

      如果您将所有文本节点视为某种无类型元素,如下所示:

      <text>
        <text>Foo</text>
        <tspan>Bar</tspan>
        <text>Baz</text>
      </text>
      

      您将如何进行定位和更改您想要的元素,这是毫无疑问的。选项包括:

      • 按索引抓取它(就像您已经做过的那样)。从上面虚构的 sn-p 判断,可以很安全地假设 Foo 确实总是第一个元素。但就像 CSS 中的 :first 选择器一样,您可能不想如此严格地依赖特定的结构。
      • class="text" 属性会非常清晰,但是您需要将文本包装在实际元素中(这可能是最好的解决方案,但我在这里遵循您的规范)。
      • 抓取第一个类型正确的元素,就像处理任何其他元素一样。

      因此,下面的 sn-p 将是更改子列表中第一个文本节点的一种完全有效的方法(以功能方式编写以保持简洁)。

      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>

      【讨论】:

        【解决方案5】:

        vqf did 采用同样的方法进行了一些改进。

        const element = document.querySelector('text');
        
        function changeText(text, node) {
          for (const n of node.childNodes) {
            let string = n.textContent.trim();
            if (!n.hasChildNodes() && string !== '') {
              n.textContent = text;
            }
          }
        }
        
        changeText('New Foo Baz', element);
        <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>

        第二种方法只是清理第一行并使用insertAdjacentText 追加新文本,这提供了更灵活的方法。

        const element = document.querySelector('text');
        
        function changeText(text, node) {
          for (const n of node.childNodes) {
            let string = n.textContent.trim();
            if (!n.hasChildNodes() && string !== '') {
              n.textContent = '';
            }
          }
        
          node.insertAdjacentText('afterbegin', text);
          node.insertAdjacentText('beforeend', text);
        }
        
        changeText('New Foo Baz', element);
        <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>

        【讨论】:

          猜你喜欢
          • 2015-12-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多