如何在多个SVG路径上继续文本?(路径上的文本行为)



我想建立一个文本不以直线显示而是以曲线显示的网站。我目前使用的是一个SVG路径,它跨越多行,并使用textPath元素将文本放在该路径上。然后我用JavaScript缩放字体大小;"响应";。

以下是我目前所拥有的:

$(function() {
var myFontsize = window.innerWidth * 0.015 + ((window.innerWidth - 1300) / -30);
$( "text" ).css("font-size", myFontsize)
});
$( window ).resize(function() {
var myFontsize = window.innerWidth * 0.015 + ((window.innerWidth - 1300) / -30);
$( "text" ).css("font-size", myFontsize)
});
body {
margin: 1vw;
}
path {
fill: none;
}
#line-box {
position: fixed;
left: 1vw;
top: 0;
width: 101vw;
}
text {
fill: black;
font-size: 1.3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="line-box" viewBox="0 -30 1200 1200" preserveAspectRatio="none">
<path vector-effect="non-scaling-stroke" id="curve" d="M0 0C449.6667 0 857 0 1160 0M0 35C292 35 584 35 876 55 953.6667 55 1031.3333 55 1160 35"/>
<text text-anchor="start">
<textPath xlink:href="#curve">
Are you a human being? We apologize for the confusion, but we can't quite tell if you're a person or a script. Please don't take this personally. Bots and scripts can be remarkably lifelike these days!
</textPath>
</text>
</svg>

(更改视图框的宽度以查看我所说的行为)

现在,路径上的渲染文本的行为当然不像p元素中的文本,所以在有";换行符";它包裹在单词中间的边缘(这些单词不是单词,而是单个呈现的字母)。

我曾想过将每个单词放入tspan元素,以为我可以通过这种方式掌握每个单词,但文本行被放在一个连续的路径上,所以我真的不知道如何计算或检测单词是离开还是进入特定的路径行。

有没有一种方法可以在多个路径上继续文本,而不是用一个路径绘制多行?也许通过这种方式,可以在tspan元素离开路径并且必须进入下一个路径时以某种方式计算它?

我基本上想要文本,它是";换行符";类似于没有连字符的普通段落,只是行在路径上移动。这个想法对我来说听起来并不复杂,但我真的不知道最好的方法是什么。

也许SVG路径不是实现这一点的合适工具。在这种情况下,有人比我更清楚我应该去哪里吗?例如,我毫不犹豫地研究一些js库。

非常感谢:)

您肯定需要Javascript来实现这一点。SVG是描述矢量图形的一种相对低级的格式,而不是文本布局格式。响应式换行是正在酝酿中的一项功能,但尚未完全实现AFAIK。文本路径上有响应换行符吗?这太专业化了。

这个想法对我来说听起来并不那么复杂

嗯,这是由于各种原因。这里有一个有点天真的实现:

const nssvg = "http://www.w3.org/2000/svg";
const nsxlink = "http://www.w3.org/1999/xlink";
const text = document.querySelector('text');
const originalText = text.textContent.trim();
function nextCurve(curves) {
const currentCurve = curves.shift();
const currentLength = currentCurve.getTotalLength();
return [currentCurve, currentLength, /s+/g, 0];
}
function makeTextPath(content, id) {
const textPath = document.createElementNS(nssvg, 'textPath');
textPath.setAttributeNS(nsxlink, 'xlink:href', '#' + id);
textPath.textContent = content;
text.appendChild(textPath);
}
function onResize() {
text.style.fontSize = (window.innerWidth * 0.015 + ((window.innerWidth - 1300) / -30)) + 'px';
// flush old layout
text.innerHTML = "";
let currentText = originalText;
// set text in a provisorial tspan so it can be measured
const tspan = document.createElementNS(nssvg, 'tspan');
tspan.textContent = currentText;
text.appendChild(tspan);
const curves = [...document.querySelectorAll('.curve')];
let [currentCurve, currentLength, re, previousIndex] = nextCurve(curves);
// search for spaces
while (re.exec(currentText)) {
// measure the position of the space
const length = tspan.getSubStringLength(0, re.lastIndex)
if ( (length < currentLength) ) {
previousIndex = re.lastIndex;
} else {
// if the space is beyond the end, add a textPath with one word less,
// provided there is still a curve left to use
if (currentCurve) {
makeTextPath(currentText.substring(0, previousIndex), currentCurve.id);
}
// remove the already used part from the tspan
// and initialize for next line
tspan.textContent = currentText = currentText.substring(previousIndex).trim();
[currentCurve, currentLength, re, previousIndex] = nextCurve(curves);
}
}
// there is a rest that still needs to be layed out
if (currentCurve) {
makeTextPath(currentText, currentCurve.id);
}
//the provisorial tspan can be removed now
tspan.remove();
}
onResize();
window.addEventListener('resize', onResize);
#line-box {
left: 1vw;
width: 98vw;
}
text {
text-anchor: start;
fill: black;
font-size: 1.3rem;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="line-box" viewBox="0 -30 1200 1200" preserveAspectRatio="none">
<defs>
<path class="curve" id="c1" d="M0 0C449.6667 0 857 0 1160 0"/>
<path class="curve" id="c2" d="M0 35C292 35 584 35 876 55 953.6667 55 1031.3333 55 1160 35"/>
<path class="curve" id="c3" d="M 0,70 C 292,70 584,90 876,110 953.67,110 1031.3,90 1160,70"/>
</defs>
<text>
Are you a human being? We apologize for the confusion, but we can't quite tell if you're a person or a script. Please don't take this personally. Bots and scripts can be remarkably lifelike these days!
</text>
</svg>

关于这一点,你自己提到过一个简化:潜在的换行符是用正则表达式/s+/g找到的,它将识别各种空间字符。这甚至不如HTML中用于换行的算法(默认为hyphens: none)复杂。

最大的问题是确定什么适合<textPath>中使用的路径。有一些为SVGTextContentElement定义的接口看起来可能会有所帮助。但在实践中,他们做得不够。描述如何定位字形的算法很长,但它对无法定位的字形没有太多说明:

  • 路径中点偏离路径的雕文不会渲染
  • 继续渲染图示符,直到不再有图示符(或路径上不再有空间)

假设第n个字符未渲染。上面的引用没有说明textPath.getSubStringLength(0, n)textPath.getStartPositionOfChar(n)的返回值。例如,Firefox返回的值就好像它们都集中在路径末尾的某个地方一样。其他浏览器可能会返回完全不同的东西,无法保证会返回什么。

我决定绕过这个问题,首先将字符串放在一条直线上,然后测量空格的终点,并在最后一个空格处断开,这个空格的位置仍然小于将要放置的路径的长度。但这只是一个粗略的估计。实际上,直线布局和曲线布局之间的字符间距不同。特别是对于窄曲线,结果会是错误的,当文本位于其内部时,一些字母可能会消失。

另一个问题产生于"的概念;可寻址字符">用于在SVG中生成字符索引:

可通过文本定位属性和SVG DOM文本方法寻址的字符。布局过程中丢弃的字符(如折叠的空白字符)不可寻址,显示属性值为none的元素中的字符也不可寻址。可寻址字符通过以UTF-16代码单元测量的索引来寻址(因此,在U+FFFF之上的单个Unicode代码点将映射到两个可寻址字符,因为UTF-16代码单位由16位组成)。

如果您有一个从textContent属性返回的字符串,并应用JavascriptString原型的方法,那么相同的索引可能指向完全不同的字符。

要完全解决这两个问题,还需要更多的代码,而所有要考虑的特殊情况都远远超出了这个答案的范围。也许某个地方有一个库试图这样做,但我不知道。

最新更新