tspan, dx, dy y el texto multilínea
El elemento que permite tramos con estilo propio, la diferencia entre posición absoluta y desplazamiento relativo, el patrón de líneas manuales, y la trampa del espacio en blanco.
Tramos con estilo
El uso más simple: cambiar el aspecto de una parte.
<svg viewBox="0 0 260 50" width="520"> <text x="10" y="32" font-size="22" fill="#cdd6f4" font-family="system-ui, sans-serif"> Ventas: <tspan font-weight="700" fill="#a6e3a1">+18,4 %</tspan> </text></svg>El tspan hereda todo del text salvo lo que declare. El texto fluye de forma continua: no hay salto ni espacio adicional.
Este es el patrón para las etiquetas de un gráfico donde el valor tiene que destacar, y para las unidades en tamaño menor:
<text x="10" y="40" font-size="32" font-weight="700"> 248<tspan font-size="16" font-weight="400" dx="2">ms</tspan></text>El dx="2" añade dos unidades de separación, porque un espacio literal en el marcado sería del tamaño del texto grande.
Absoluto frente a relativo
tspan acepta x e y (posición absoluta) y dx y dy (desplazamiento relativo a donde estaría). Los dos modos coexisten y se aplican en este orden: primero se resuelve la posición absoluta si la hay, después se suma el desplazamiento.
La diferencia crucial está en la acumulación.
x e y reinician la posición. Todo lo que venga después de un tspan con x sigue a partir de ahí.
dx y dy se acumulan. Un tspan con dy="10" desplaza su contenido diez unidades hacia abajo, y todo lo que venga después sigue en esa nueva línea base. No vuelve.
<!-- Los tres tspan bajan en escalera, no vuelven al nivel original --><text x="10" y="20" font-size="18"> A<tspan dy="10">B</tspan><tspan dy="10">C</tspan><tspan dy="10">D</tspan></text>La D está treinta unidades por debajo de la A. Ese comportamiento acumulativo es exactamente lo que se quiere para componer líneas, y exactamente lo que sorprende cuando quieres un subíndice y todo lo demás se queda abajo.
Para un subíndice o superíndice hay que compensar:
<text x="10" y="30" font-size="20"> H<tspan dy="4" font-size="14">2</tspan><tspan dy="-4">O</tspan></text>Baja cuatro, sube cuatro. Es tedioso y es el precio del modelo acumulativo.
El patrón de líneas manuales
Como no hay ajuste automático, las líneas se componen con un tspan por línea, reiniciando la x y acumulando la y:
<text x="20" y="30" font-size="16" fill="#cdd6f4" font-family="system-ui, sans-serif"> <tspan x="20" dy="0">Primera línea del párrafo</tspan> <tspan x="20" dy="1.35em">Segunda línea del párrafo</tspan> <tspan x="20" dy="1.35em">Tercera línea del párrafo</tspan></text>Tres decisiones ahí y las tres importan.
La x se repite en cada tspan. Sin ella, cada línea empezaría donde acabó la anterior, porque dy solo mueve en vertical.
El dy está en em. Así el interlineado escala con el tamaño de fuente y no hay que recalcularlo. El valor 1,35 es un interlineado cómodo; entre 1,2 y 1,5 es el rango sensato.
El primer tspan lleva dy="0". No es imprescindible, pero deja explícito que la primera línea va en la y del text y evita que alguien añada un dy al primero por simetría y desplace todo.
Generado desde código:
function textoMultilinea(lineas, x, y, interlineado = 1.35) { const tramos = lineas .map((l, i) => `<tspan x="${x}" dy="${i === 0 ? 0 : interlineado + 'em'}">${l}</tspan>`) .join(''); return `<text x="${x}" y="${y}">${tramos}</text>`;}Para centrar el bloque verticalmente sobre una y dada hay que desplazar la primera línea hacia arriba la mitad del alto total:
const desplazamiento = -((lineas.length - 1) * interlineado) / 2;// y ese valor va en el dy del primer tspan, en emEl espacio en blanco
Este es el detalle que ensucia todos los textos generados con marcado formateado. Por omisión, SVG colapsa las secuencias de espacios y elimina los del principio y el final de cada tramo, igual que HTML. Pero el marcado indentado introduce saltos de línea y espacios entre los tspan, y esos se convierten en un espacio real.
<!-- Sale con un espacio de mas entre A y B --><text x="10" y="20"> <tspan>A</tspan> <tspan>B</tspan></text><!-- Correcto --><text x="10" y="20"><tspan>A</tspan><tspan>B</tspan></text>En el primero, el salto de línea y la indentación entre los dos tspan producen un espacio. En textos generados por código eso se controla no poniendo separadores; en textos escritos a mano hay que pegar las etiquetas, lo que hace el marcado ilegible.
La propiedad que controla esto es white-space, con los mismos valores que en CSS. white-space: pre conserva todo, incluidos los saltos de línea, pero no los convierte en saltos visuales: siguen apareciendo como espacios, porque el texto de SVG no salta de línea. El atributo antiguo xml:space="preserve" hace algo parecido y está obsoleto en favor de la propiedad.