Jak złamać linię tekstu SVG w javascript?


107

Oto co mam:

<path class="..." onmousemove="show_tooltip(event,'very long text 
    \\\n I would like to linebreak')" onmouseout="hide_tooltip()" d="..."/>

<rect class="tooltip_bg" id="tooltip_bg" ... />
<text class="tooltip" id="tooltip" ...>Tooltip</text>

<script>
<![CDATA[
function show_tooltip(e,text) {
    var tt = document.getElementById('tooltip');
    var bg = document.getElementById('tooltip_bg');

    // set position ...

    tt.textContent=text;

    bg.setAttribute('width',tt.getBBox().width+10);
    bg.setAttribute('height',tt.getBBox().height+6);

    // set visibility ...
}
...

Teraz mój bardzo długi tekst podpowiedzi nie ma podziału wiersza, nawet jeśli używam funkcji alert (); pokazuje mi, że tekst faktycznie ma dwie linie. (Zawiera jednak znak „\”, a tak przy okazji, jak mogę to usunąć?)
Nie mogę nigdzie zmusić CDATA do działania.


2
Czy jest jakaś szansa, że ​​ten svgjs.com/textflow może pomóc z etykietką?
Alvin K.

@AlvinK. łącze jest zepsute. Próbowałem znaleźć nową lokalizację, ale nie udało mi się.
guettli

Odpowiedzi:


150

To nie jest coś, co obsługuje SVG 1.1. SVG 1.2 ma ten textAreaelement z automatycznym zawijaniem słów, ale nie jest zaimplementowany we wszystkich przeglądarkach. SVG 2 nie planuje wdrożeniatextArea , ale ma automatycznie zawijany tekst .

Biorąc jednak pod uwagę, że już wie, gdzie powinien pojawić się swoimi linebreaks, można przełamać swój tekst na kilka <tspan>sekund, z których każda x="0"i dy="1.4em"symulować rzeczywiste linie tekstu. Na przykład:

<g transform="translate(123 456)"><!-- replace with your target upper left corner coordinates -->
  <text x="0" y="0">
    <tspan x="0" dy="1.2em">very long text</tspan>
    <tspan x="0" dy="1.2em">I would like to linebreak</tspan>
  </text>
</g>

Oczywiście, ponieważ chcesz to zrobić z poziomu JavaScript, będziesz musiał ręcznie utworzyć i wstawić każdy element do DOM.


2
Jak rozpoznać, gdzie umieścić <tspan>s? Zastąpić? Rozdzielać?
sollniss

2
Wypróbowałem, var tspan = document.createElement('tspan') tspan.setAttribute('x','0'); tspan.setAttribute('dy','1.2em'); tspan.textContent = text; tt.appendChild(tspan); nie wyświetla w ogóle żadnego tekstu.
sollniss

2
Czy mógłbyś wyjaśnić, dlaczego x = '0' dy = '1,2em' jest potrzebne? Rzeczywiście działa, tak jak powiedziałeś. Spodziewałem się jednak, że zadziała nawet bez tych atrybutów. Zamiast tego nic nie jest wyświetlane ... Poza tym nie jestem do końca jasne, dlaczego w ogóle występuje podział wiersza . To nie jest tak, że ustawiliśmy szerokość kontenera na coś naprawionego, co może powodować łamanie linii, prawda?
Konrad Viltersten

4
x=0jest współrzędną bezwzględną: przenieś fragment tekstu do początku bieżącego układu współrzędnych . transformAtrybutu na gelemencie określa się nowy bieżący układu współrzędnych, i przy założeniu, że treść jest wyrównany do lewej The tspan przesuwa się w lewo. Działa to jak instrukcja powrotu karetki. dy=1.2emjest współrzędną względną : przesuń fragment tekstu o tę wartość w stosunku do bieżącego fragmentu tekstu. Działa jak instrukcja przesuwu o wiersz. W połączeniu otrzymujesz CR / LF.
Sergiu Dumitriu

Jeszcze tego nie próbowałem: czy mógłbyś to zrobić również bez grupy? <text x = "100" y = "100"> <tspan x = "100" y = "100"> bardzo długi tekst </tspan> <tspan x = "100" y = "115"> Chcę linebreak </tspan> </text> ??
Richard

25

Podejrzewam, że udało ci się to już rozwiązać, ale jak ktoś szuka podobnego rozwiązania to u mnie zadziałało:

 g.append('svg:text')
  .attr('x', 0)
  .attr('y', 30)
  .attr('class', 'id')
  .append('svg:tspan')
  .attr('x', 0)
  .attr('dy', 5)
  .text(function(d) { return d.name; })
  .append('svg:tspan')
  .attr('x', 0)
  .attr('dy', 20)
  .text(function(d) { return d.sname; })
  .append('svg:tspan')
  .attr('x', 0)
  .attr('dy', 20)
  .text(function(d) { return d.idcode; })

Istnieją 3 wiersze oddzielone podziałem wiersza.


21
FWIW: wygląda na to, że OP używał czystego JavaScript; wydaje się, że ta odpowiedź wykorzystuje D3 .
Ben Mosher

Używam D3 i Twoje podejście zadziałało dla mnie. Dzięki za wysłanie. Zauważyłem, że najpierw muszę usunąć stare tspany przed dołączeniem nowych, na przykład: focus.selectAll ("tspan"). Remove ();
Darren Parker

1
Uważaj przy tym podejściu, ponieważ zagnieżdża tagi <tspan>, ponieważ tworzy łańcuch .append (). Może to powodować drobne problemy z CSS w zależności od tego, co chcesz zrobić.
seneyr

Zobacz tutaj podejście, które pozwala uniknąć zagnieżdżenia opisanego przez @seneyr
bszom

16

Dzięki rozwiązaniu tspan powiedzmy, że nie wiesz z góry, gdzie wstawiać znaki końca linii: możesz użyć tej fajnej funkcji, którą znalazłem tutaj: http://bl.ocks.org/mbostock/7555321

To automatycznie powoduje podziały wierszy dla długiego tekstu svg dla danej szerokości w pikselach.

function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      }
    }
  });
}

9

Myślę, że robi to, co chcesz:

function ShowTooltip(evt, mouseovertext){
    // Make tooltip text        
    var tooltip_text = tt.childNodes.item(1);
    var words = mouseovertext.split("\\\n");
    var max_length = 0;

    for (var i=0; i<3; i++){
        tooltip_text.childNodes.item(i).firstChild.data = i<words.length ?  words[i] : " ";
        length = tooltip_text.childNodes.item(i).getComputedTextLength();
        if (length > max_length) {max_length = length;}
    }

    var x = evt.clientX + 14 + max_length/2;
    var y = evt.clientY + 29;
    tt.setAttributeNS(null,"transform", "translate(" + x + " " + y + ")")

    // Make tooltip background
    bg.setAttributeNS(null,"width", max_length+15);
    bg.setAttributeNS(null,"height", words.length*15+6);
    bg.setAttributeNS(null,"x",evt.clientX+8);
    bg.setAttributeNS(null,"y",evt.clientY+14);

    // Show everything
    tt.setAttributeNS(null,"visibility","visible");
    bg.setAttributeNS(null,"visibility","visible");
}

Dzieli tekst \\\ni dla każdego umieszcza każdy fragment w tspan. Następnie oblicza wymagany rozmiar pola na podstawie najdłuższej długości tekstu i liczby wierszy. Będziesz także musiał zmienić element tekstowy podpowiedzi tak, aby zawierał trzy tspan:

<g id="tooltip" visibility="hidden">
    <text><tspan>x</tspan><tspan x="0" dy="15">x</tspan><tspan x="0" dy="15">x</tspan></text>
</g>

Zakłada się, że nigdy nie masz więcej niż trzy linie. Jeśli chcesz mieć więcej niż trzy wiersze, możesz dodać więcej łyżeczek i zwiększyć długość pętli for.


Dlaczego tak jest, "\\\n"a nie "\n"?
ralien

2

Dostosowałem nieco rozwiązanie @steco, przełączając zależność z d3na jqueryi dodając heightelement tekstowy jako parametr

function wrap(text, width, height) {
  text.each(function(idx,elem) {
    var text = $(elem);
    text.attr("dy",height);
        var words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat( text.attr("dy") ),
        tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (elem.getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
      }
    }
  });
}

2

używaj HTML zamiast javascript

<html>
  <head><style> * { margin: 0; padding: 0; } </style></head>
  <body>
    <h1>svg foreignObject to embed html</h1>

    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 300 300"
      x="0" y="0" height="300" width="300"
    >

      <circle
        r="142" cx="150" cy="150"
        fill="none" stroke="#000000" stroke-width="2"
      />

      <foreignObject
        x="50" y="50" width="200" height="200"
      >
        <div
          xmlns="http://www.w3.org/1999/xhtml"
          style="
            width: 196px; height: 196px;
            border: solid 2px #000000;
            font-size: 32px;
            overflow: auto; /* scroll */
          "
        >
          <p>this is html in svg 1</p>
          <p>this is html in svg 2</p>
          <p>this is html in svg 3</p>
          <p>this is html in svg 4</p>
        </div>
      </foreignObject>

    </svg>

</body></html>


Myślę, że masz na myśli „użyj SVG zamiast JavaScript”
Valerio Bozz

To „HTML w SVG”, najlepsze rozwiązanie dla mnie!
Kévin Berthommier
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.