在SVG或画布中将文本包装成圆形形状

6
如何将文本适配到网站上的圆圈中,使其遵循圆圈的曲线而不是矩形边界框?以下是我的需求:
在页面上有一些大小固定的黑色圆圈,并且每个圆圈旁边都有一个文本区域。当文本输入到文本区域时,它将出现在圆圈中,并在两个轴上居中对齐。如果输入的文本太多,以至于行变得比圆的半径少去指定值的边距还要长,那么该行会像常规换行一样断开,同时仍然居中对齐。位于顶部或底部的行将比位于中间的行短。
文本的大小将保持不变,当圆圈被填满文本后,额外的内容不应显示(如溢出隐藏)。
这些带有文本的黑色圆圈实际上是漫画气泡,旨在打印并粘贴到海报上。
是否有任何出色的SVG / Canvas库支持此功能,还是我需要从头开始想出一种方法?

你尝试过什么了吗?请分享你遇到问题的代码。 - user1693593
我一直在进行各种不同的实验,问题并不在于代码本身,而是方法论。 一种相当粗糙的版本涉及在黑色圆形的PNG上叠加HTML元素,并将文本区域中的换行符替换为 <br> 标签。 这是可用的,但不太优雅,因为它缺少对非矩形形状的包装元素的依赖,而是依赖用户来打破文本。 - Lowkey
1
几个小时前有一个类似的问题发布在Stack Overflow上:(https://dev59.com/A2Ei5IYBdhLWcg3w0fJR)也许会有一些有趣的答案。 - VividD
1个回答

16

有一个名为“exclusions”的CSS预期功能,它可以使文本在定义的区域内流动:http://www.w3.org/TR/css3-exclusions/

这意味着SVG和Canvas路径很可能被定义为容器,文本将流动/包装在容器内。

我说过"预期" - 它距离在浏览器中实现还有一段距离。

然而...

你可以很容易地使用HTML Canvas在圆形内部换行文本

enter image description here

随着你向下移动圆形,任何一行上可用于显示文本的宽度都会发生变化。

以下是如何确定圆形上任何水平线的最大可用宽度:

// var r is the radius of the circle
// var h is the distance from the top of the circle to the horizontal line you’ll put text on

var maxWidth=2*Math.sqrt(h*(2*r-h));

通过逐个添加单词,直到使用了该行的所有可用宽度,您可以通过测量文本的宽度来将文本调整为一行。

以下是如何使用画布(canvas)使用当前 context.font 来测量任何文本:

var width=ctx.measureText(“This is some test text.”).width;

剩下的就是在每一行添加文本,直到达到最大行宽,然后开始新的一行。

如果你喜欢SVG,你可以使用element.getComputedTextLength方法来测量文本度量,在SVG中进行类似的操作。

这里有代码和一个Fiddle链接:http://jsfiddle.net/m1erickson/upq6L/

<!doctype html>
<html lang="en">
<head>

  <style>
      body{ background-color: ivory; padding:20px; }
      canvas{ border:1px solid red;}
  </style>
  <script src="http://code.jquery.com/jquery-1.9.1.js"></script>

  <script>

  $(function() {

      var canvas=document.getElementById("canvas");
      var ctx=canvas.getContext("2d");

      var text = "'Twas the night before Christmas, when all through the house,  Not a creature was stirring, not even a mouse.  And so begins the story of the day of Christmas";
      var font="12pt verdana";
      var textHeight=15;
      var lineHeight=textHeight+5;
      var lines=[];

      var cx=150;
      var cy=150;
      var r=100;

      initLines();

      wrapText();

      ctx.beginPath();
      ctx.arc(cx,cy,r,0,Math.PI*2,false);
      ctx.closePath();
      ctx.strokeStyle="skyblue";
      ctx.lineWidth=2;
      ctx.stroke();


      // pre-calculate width of each horizontal chord of the circle
      // This is the max width allowed for text

      function initLines(){

          for(var y=r*.90; y>-r; y-=lineHeight){

              var h=Math.abs(r-y);

              if(y-lineHeight<0){ h+=20; }

              var length=2*Math.sqrt(h*(2*r-h));

              if(length && length>10){
                  lines.push({ y:y, maxLength:length });
              }

          }
      }


      // draw text on each line of the circle

      function wrapText(){

          var i=0;
          var words=text.split(" ");

          while(i<lines.length && words.length>0){

              line=lines[i++];

              var lineData=calcAllowableWords(line.maxLength,words);

              ctx.fillText(lineData.text, cx-lineData.width/2, cy-line.y+textHeight);

              words.splice(0,lineData.count);
          };

      }


      // calculate how many words will fit on a line

      function calcAllowableWords(maxWidth,words){

          var wordCount=0;
          var testLine="";
          var spacer="";
          var fittedWidth=0;
          var fittedText="";

          ctx.font=font;

          for(var i=0;i<words.length; i++){

              testLine+=spacer+words[i];
              spacer=" ";

              var width=ctx.measureText(testLine).width;

              if(width>maxWidth){ 
                  return({
                      count:i, 
                      width:fittedWidth, 
                      text:fittedText
                  }); 
              }

              fittedWidth=width;
              fittedText=testLine;

          }

      }


  });   // end $(function(){});

  </script>
</head>
<body>
    <p>Text wrapped and clipped inside a circle</p>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

是的,我了解到关于排除的内容,但只在启用实验标志的金丝雀版本中有效的解决方案并不完全适用。 上面的代码给了我一些可以使用的东西。 如果没有足够的文本来填充圆形,则我将进行一些调整,使文本始终垂直居中。 - Lowkey
1
@Lowkey,我也在寻找同样的东西。你已经成功让它始终垂直居中了吗? - yohairosen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接