HTML5 canvas ctx.fillText 不会做换行符吗?

HTML5 canvas ctx.fillText won't do line breaks?

提问人:Spectraljump 提问时间:2/17/2011 最后编辑:NakilonSpectraljump 更新时间:5/6/2022 访问量:142025

问:

如果文本包含“\n”,我似乎无法将文本添加到画布中。我的意思是,换行符不显示/工作。

ctxPaint.fillText("s  ome \n \\n <br/> thing", x, y);

上面的代码将在一行上绘制 。"s ome \n <br/> thing"

这是 fillText 的限制还是我做错了?“\n”在那里,没有打印出来,但它们也不起作用。

JavaScript HTML 画布

评论

1赞 Gabriele Petrioli 12/19/2010
你想在到达终点时自动换行吗?或者只是为了考虑文本中存在的换行符?
0赞 Tower 12/19/2010
将文本换行为多行。
0赞 Tim 2/18/2011
嗨,twodordan,chrome 和 mozilla 都存在此限制吗?例如,人们经常使用简单的 html 文本,并将其放在画布上,并带有 position:absolute 等值。此外,您可以执行两个 fillText 并移动第二行文本的 Y 原点。
0赞 MvG 8/23/2016
HTML5 Canvas 的可能副本 - 我可以以某种方式在 fillText() 中使用换行符吗?
0赞 Andrew 4/18/2020
TL;DR:要么多次调用并使用字体高度来分隔,要么使用 developer.mozilla.org/en-US/docs/Web/API/TextMetrics developer.mozilla.org/en-US/docs/Web/API/... - 或者,使用下面一个非常复杂的“解决方案”之一,不使用 TextMetrics......fillText()

答:

2赞 Harmen 12/19/2010 #1

我认为这也是不可能的,但解决方法是创建一个元素并使用 Javascript 定位它。<p>

评论

0赞 Tower 12/19/2010
是的,这就是我正在考虑做的事情。只是有了 和,你可以做一些超出 CSS 能做的事情。fillText()strokeText()
0赞 Jerry Asher 6/20/2013
我还没有测试过这个,但我认为这可能是一个更好的解决方案——这里使用 fillText() 的其他解决方案使文本无法被选中(或可能粘贴)。
114赞 Gabriele Petrioli 12/19/2010 #2

如果您只想处理文本中的换行符,您可以通过在换行符处拆分文本并多次调用fillText()

类似 http://jsfiddle.net/BaG4J/1/

var c = document.getElementById('c').getContext('2d');
c.font = '11px Courier';
    console.log(c);
var txt = 'line 1\nline 2\nthird line..';
var x = 30;
var y = 30;
var lineheight = 15;
var lines = txt.split('\n');

for (var i = 0; i<lines.length; i++)
    c.fillText(lines[i], x, y + (i*lineheight) );
canvas{background-color:#ccc;}
<canvas id="c" width="150" height="150"></canvas>


我刚刚做了一个包装概念证明(指定宽度的绝对包装。还没有处理单词中断)
示例在 http://jsfiddle.net/BaG4J/2/

var c = document.getElementById('c').getContext('2d');
c.font = '11px Courier';

var txt = 'this is a very long text to print';

printAt(c, txt, 10, 20, 15, 90 );


function printAt( context , text, x, y, lineHeight, fitWidth)
{
    fitWidth = fitWidth || 0;
    
    if (fitWidth <= 0)
    {
         context.fillText( text, x, y );
        return;
    }
    
    for (var idx = 1; idx <= text.length; idx++)
    {
        var str = text.substr(0, idx);
        console.log(str, context.measureText(str).width, fitWidth);
        if (context.measureText(str).width > fitWidth)
        {
            context.fillText( text.substr(0, idx-1), x, y );
            printAt(context, text.substr(idx-1), x, y + lineHeight, lineHeight,  fitWidth);
            return;
        }
    }
    context.fillText( text, x, y );
}
canvas{background-color:#ccc;}
<canvas id="c" width="150" height="150"></canvas>


以及换行(在空格处中断)概念证明。
http://jsfiddle.net/BaG4J/5/ 示例

var c = document.getElementById('c').getContext('2d');
c.font = '11px Courier';

var txt = 'this is a very long text. Some more to print!';

printAtWordWrap(c, txt, 10, 20, 15, 90 );


function printAtWordWrap( context , text, x, y, lineHeight, fitWidth)
{
    fitWidth = fitWidth || 0;
    
    if (fitWidth <= 0)
    {
        context.fillText( text, x, y );
        return;
    }
    var words = text.split(' ');
    var currentLine = 0;
    var idx = 1;
    while (words.length > 0 && idx <= words.length)
    {
        var str = words.slice(0,idx).join(' ');
        var w = context.measureText(str).width;
        if ( w > fitWidth )
        {
            if (idx==1)
            {
                idx=2;
            }
            context.fillText( words.slice(0,idx-1).join(' '), x, y + (lineHeight*currentLine) );
            currentLine++;
            words = words.splice(idx-1);
            idx = 1;
        }
        else
        {idx++;}
    }
    if  (idx > 0)
        context.fillText( words.join(' '), x, y + (lineHeight*currentLine) );
}
canvas{background-color:#ccc;}
<canvas id="c" width="150" height="150"></canvas>


在第二个和第三个示例中,我使用 measureText() 方法,该方法显示字符串在打印时的长度(以像素为单位)。

评论

0赞 amir22 9/2/2019
如何证明整个长文本的合理性?
0赞 Mike 'Pomax' Kamermans 8/10/2020
如果你需要一个长的、对齐的文本,你为什么要使用画布?
0赞 Samnad Sainulabdeen 2/1/2022
我喜欢第三种方法
0赞 Nikunj Bhatt 7/9/2023
在第三种方法中似乎没有任何条件要求。的初始值为 ,它不会在高于条件的任何地方减少。条件将始终是 。还是我错过了什么?ifif(idx > 0)idx1ififtrue
70赞 Simon Sarris 2/18/2011 #3

恐怕是 Canvas 的局限性。没有多行支持。更糟糕的是,没有内置的方法来测量线高,只有宽度,这使得自己动手变得更加困难!fillText

很多人都编写了自己的多行支持,也许最著名的项目是Mozilla Skywriter

您需要做的要点是多次调用,同时每次将文本的高度添加到 y 值。(我相信,测量 M 的宽度是 skywriter 人为近似文本所做的。fillText

评论

1赞 Spectraljump 2/19/2011
谢谢!我有一种感觉,这会很麻烦......很高兴了解 SKYWRITER,但我会“等待”直到 fillText() 得到改进。就我而言,这并不是一笔非常重要的交易。呵呵,没有线高,就像是有人故意这样做的。:D
19赞 SikoSoft 4/17/2011
老实说,我不会对 fillText() 被“改进”以支持这一点屏住呼吸,因为我感觉这就是它的使用方式(多次调用并自己计算 yOffset)。我认为 canvas API 的很多功能在于它将较低级别的绘图功能与您已经可以执行的操作(执行必要的测量)分开。此外,您只需提供以像素为单位的文本大小即可知道文本高度;换句话说: context.font = “16px Arial”;- 你有那里的高度;宽度是唯一动态的宽度。
1赞 SWdV 2/8/2017
添加了一些额外的属性,我认为可以解决问题。Chrome 有一个标志来启用它们,但其他浏览器没有......还!measureText()
0赞 Simon Sarris 2/8/2017
@SWdV需要明确的是,这些已经在规范中存在多年了,可能还需要几年时间才能广泛采用以使用:(
2赞 Summer Sun 9/4/2020
也许可以用来测量线高。actualBoundingBoxAscent
6赞 jeffchan 8/12/2011 #4

@Gaby Petrioli 提供的换行代码(在空格处换行)非常有用。 我已经扩展了他的代码以提供对换行符的支持。此外,通常具有边界框的尺寸很有用,因此同时返回宽度和高度。\nmultiMeasureText()

您可以在此处看到代码:http://jsfiddle.net/jeffchan/WHgaY/76/

评论

0赞 Mike 'Pomax' Kamermans 8/10/2020
链接过期,即使您有工作链接,也请将代码放在此答案中。如果 jsfiddle 关闭,这个答案就变得完全没用了。
8赞 Ramirez 11/2/2011 #5

使用 javascript,我开发了一个解决方案。它并不漂亮,但它对我有用:


function drawMultilineText(){

    // set context and formatting   
    var context = document.getElementById("canvas").getContext('2d');
    context.font = fontStyleStr;
    context.textAlign = "center";
    context.textBaseline = "top";
    context.fillStyle = "#000000";

    // prepare textarea value to be drawn as multiline text.
    var textval = document.getElementByID("textarea").value;
    var textvalArr = toMultiLine(textval);
    var linespacing = 25;
    var startX = 0;
    var startY = 0;

    // draw each line on canvas. 
    for(var i = 0; i < textvalArr.length; i++){
        context.fillText(textvalArr[i], x, y);
        y += linespacing;
    }
}

// Creates an array where the <br/> tag splits the values.
function toMultiLine(text){
   var textArr = new Array();
   text = text.replace(/\n\r?/g, '<br/>');
   textArr = text.split("<br/>");
   return textArr;
}

希望对您有所帮助!

评论

1赞 Amol Navsupe 5/18/2015
你好,假设我的文字是这样的 var text = “啊啊那么在画布中发生了什么???
0赞 KaHa6uc 9/12/2016
它将退出画布,因为@Ramirez没有将 maxWidth 参数放入 fillText :)
3赞 MarioF 5/30/2012 #6

我认为你仍然可以依赖CSS

ctx.measureText().height doesn’t exist.

幸运的是,通过 CSS hack-ardry(参见 Typographic Metrics 了解修复使用 CSS 度量的旧实现的更多方法),我们可以通过测量具有相同字体属性的 a 的 offsetHeight 来找到文本的高度:

var d = document.createElement(”span”);
d.font = “20px arial”
d.textContent = “Hello world!”
var emHeight = d.offsetHeight;

寄件人: http://www.html5rocks.com/en/tutorials/canvas/texteffects/

评论

0赞 Eric Hodonsky 9/21/2016
如果你有内存来构建一个这样的元素,那么这是一个很好的解决方案,每次你需要测量时。你也可以然后,然后,.请注意,我在设置时使用“pt”。然后,它将转换为PX,并能够转换为数字以用作字体的高度。ctx.save()ctx.font = '12pt Arial' parseInt( ctx.font, 10 )
40赞 Colin Wiseman 7/6/2012 #7

也许来这个聚会有点晚,但我发现以下关于在画布上包装文本的教程是完美的。

http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

从那时起,我能够想到让多行工作(对不起,拉米雷斯,你的对我不起作用!我在画布中包装文本的完整代码如下:

<script type="text/javascript">

     // http: //www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/
     function wrapText(context, text, x, y, maxWidth, lineHeight) {
        var cars = text.split("\n");

        for (var ii = 0; ii < cars.length; ii++) {

            var line = "";
            var words = cars[ii].split(" ");

            for (var n = 0; n < words.length; n++) {
                var testLine = line + words[n] + " ";
                var metrics = context.measureText(testLine);
                var testWidth = metrics.width;

                if (testWidth > maxWidth) {
                    context.fillText(line, x, y);
                    line = words[n] + " ";
                    y += lineHeight;
                }
                else {
                    line = testLine;
                }
            }

            context.fillText(line, x, y);
            y += lineHeight;
        }
     }

     function DrawText() {

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

         context.clearRect(0, 0, 500, 600);

         var maxWidth = 400;
         var lineHeight = 60;
         var x = 20; // (canvas.width - maxWidth) / 2;
         var y = 58;


         var text = document.getElementById("text").value.toUpperCase();                

         context.fillStyle = "rgba(255, 0, 0, 1)";
         context.fillRect(0, 0, 600, 500);

         context.font = "51px 'LeagueGothicRegular'";
         context.fillStyle = "#333";

         wrapText(context, text, x, y, maxWidth, lineHeight);
     }

     $(document).ready(function () {

         $("#text").keyup(function () {
             DrawText();
         });

     });

    </script>

其中 是画布的 ID,是文本框的 ID。ctext

正如您可能看到的,我使用的是非标准字体。只要您在操作画布之前在某些文本上使用过该字体,您就可以使用@font面 - 否则画布不会拾取该字体。

希望这对某人有所帮助。

0赞 Dariusz J 11/4/2012 #8

Canvas 元素不支持换行符“\n”、制表符“\t”或 br /> 标记等字符<。

尝试一下:

var newrow = mheight + 30;
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = "bold 24px 'Verdana'";
ctx.textAlign = "center";
ctx.fillText("Game Over", mwidth, mheight); //first line
ctx.fillText("play again", mwidth, newrow); //second line 

或者可能是多行:

var textArray = new Array('line2', 'line3', 'line4', 'line5');
var rows = 98;
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = "bold 24px 'Verdana'";
ctx.textAlign = "center";
ctx.fillText("Game Over", mwidth, mheight); //first line

for(var i=0; i < textArray.length; ++i) {
rows += 30;
ctx.fillText(textArray[i], mwidth, rows); 
}  
16赞 jbaylina 2/21/2013 #9

我刚刚扩展了添加两个函数:mlFillText 和 mlStrokeText。CanvasRenderingContext2D

您可以在 GitHub 中找到最新版本:

使用此功能,您可以在框中填充/描边军事文本。您可以垂直和水平对齐文本。(它考虑了 ,也可以证明文本的合理性)。\n

原型是:

function mlFillText(text,x,y,w,h,vAlign,hAlign,lineheight);
function mlStrokeText(text,x,y,w,h,vAlign,hAlign,lineheight);

其中可以是:或vAligntopcenterbutton

可以是:、 或hAlignleftcenterrightjustify

您可以在此处测试库: http://jsfiddle.net/4WRZj/1/

enter image description here

以下是该库的代码:

// Library: mltext.js
// Desciption: Extends the CanvasRenderingContext2D that adds two functions: mlFillText and mlStrokeText.
//
// The prototypes are: 
//
// function mlFillText(text,x,y,w,h,vAlign,hAlign,lineheight);
// function mlStrokeText(text,x,y,w,h,vAlign,hAlign,lineheight);
// 
// Where vAlign can be: "top", "center" or "button"
// And hAlign can be: "left", "center", "right" or "justify"
// Author: Jordi Baylina. (baylina at uniclau.com)
// License: GPL
// Date: 2013-02-21

function mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, fn) {
    text = text.replace(/[\n]/g, " \n ");
    text = text.replace(/\r/g, "");
    var words = text.split(/[ ]+/);
    var sp = this.measureText(' ').width;
    var lines = [];
    var actualline = 0;
    var actualsize = 0;
    var wo;
    lines[actualline] = {};
    lines[actualline].Words = [];
    i = 0;
    while (i < words.length) {
        var word = words[i];
        if (word == "\n") {
            lines[actualline].EndParagraph = true;
            actualline++;
            actualsize = 0;
            lines[actualline] = {};
            lines[actualline].Words = [];
            i++;
        } else {
            wo = {};
            wo.l = this.measureText(word).width;
            if (actualsize === 0) {
                while (wo.l > w) {
                    word = word.slice(0, word.length - 1);
                    wo.l = this.measureText(word).width;
                }
                if (word === "") return; // I can't fill a single character
                wo.word = word;
                lines[actualline].Words.push(wo);
                actualsize = wo.l;
                if (word != words[i]) {
                    words[i] = words[i].slice(word.length, words[i].length);
                } else {
                    i++;
                }
            } else {
                if (actualsize + sp + wo.l > w) {
                    lines[actualline].EndParagraph = false;
                    actualline++;
                    actualsize = 0;
                    lines[actualline] = {};
                    lines[actualline].Words = [];
                } else {
                    wo.word = word;
                    lines[actualline].Words.push(wo);
                    actualsize += sp + wo.l;
                    i++;
                }
            }
        }
    }
    if (actualsize === 0) lines[actualline].pop();
    lines[actualline].EndParagraph = true;

    var totalH = lineheight * lines.length;
    while (totalH > h) {
        lines.pop();
        totalH = lineheight * lines.length;
    }

    var yy;
    if (vAlign == "bottom") {
        yy = y + h - totalH + lineheight;
    } else if (vAlign == "center") {
        yy = y + h / 2 - totalH / 2 + lineheight;
    } else {
        yy = y + lineheight;
    }

    var oldTextAlign = this.textAlign;
    this.textAlign = "left";

    for (var li in lines) {
        var totallen = 0;
        var xx, usp;
        for (wo in lines[li].Words) totallen += lines[li].Words[wo].l;
        if (hAlign == "center") {
            usp = sp;
            xx = x + w / 2 - (totallen + sp * (lines[li].Words.length - 1)) / 2;
        } else if ((hAlign == "justify") && (!lines[li].EndParagraph)) {
            xx = x;
            usp = (w - totallen) / (lines[li].Words.length - 1);
        } else if (hAlign == "right") {
            xx = x + w - (totallen + sp * (lines[li].Words.length - 1));
            usp = sp;
        } else { // left
            xx = x;
            usp = sp;
        }
        for (wo in lines[li].Words) {
            if (fn == "fillText") {
                this.fillText(lines[li].Words[wo].word, xx, yy);
            } else if (fn == "strokeText") {
                this.strokeText(lines[li].Words[wo].word, xx, yy);
            }
            xx += lines[li].Words[wo].l + usp;
        }
        yy += lineheight;
    }
    this.textAlign = oldTextAlign;
}

(function mlInit() {
    CanvasRenderingContext2D.prototype.mlFunction = mlFunction;

    CanvasRenderingContext2D.prototype.mlFillText = function (text, x, y, w, h, vAlign, hAlign, lineheight) {
        this.mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, "fillText");
    };

    CanvasRenderingContext2D.prototype.mlStrokeText = function (text, x, y, w, h, vAlign, hAlign, lineheight) {
        this.mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, "strokeText");
    };
})();

下面是使用示例:

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

var T = "This is a very long line line with a CR at the end.\n This is the second line.\nAnd this is the last line.";
var lh = 12;

ctx.lineWidth = 1;

ctx.mlFillText(T, 10, 10, 100, 100, 'top', 'left', lh);
ctx.strokeRect(10, 10, 100, 100);

ctx.mlFillText(T, 110, 10, 100, 100, 'top', 'center', lh);
ctx.strokeRect(110, 10, 100, 100);

ctx.mlFillText(T, 210, 10, 100, 100, 'top', 'right', lh);
ctx.strokeRect(210, 10, 100, 100);

ctx.mlFillText(T, 310, 10, 100, 100, 'top', 'justify', lh);
ctx.strokeRect(310, 10, 100, 100);

ctx.mlFillText(T, 10, 110, 100, 100, 'center', 'left', lh);
ctx.strokeRect(10, 110, 100, 100);

ctx.mlFillText(T, 110, 110, 100, 100, 'center', 'center', lh);
ctx.strokeRect(110, 110, 100, 100);

ctx.mlFillText(T, 210, 110, 100, 100, 'center', 'right', lh);
ctx.strokeRect(210, 110, 100, 100);

ctx.mlFillText(T, 310, 110, 100, 100, 'center', 'justify', lh);
ctx.strokeRect(310, 110, 100, 100);

ctx.mlFillText(T, 10, 210, 100, 100, 'bottom', 'left', lh);
ctx.strokeRect(10, 210, 100, 100);

ctx.mlFillText(T, 110, 210, 100, 100, 'bottom', 'center', lh);
ctx.strokeRect(110, 210, 100, 100);

ctx.mlFillText(T, 210, 210, 100, 100, 'bottom', 'right', lh);
ctx.strokeRect(210, 210, 100, 100);

ctx.mlFillText(T, 310, 210, 100, 100, 'bottom', 'justify', lh);
ctx.strokeRect(310, 210, 100, 100);

ctx.mlStrokeText("Yo can also use mlStrokeText!", 0 , 310 , 420, 30, 'center', 'center', lh);

评论

0赞 psycho brm 9/14/2013
Uncaught ReferenceError: Words is not defined如果我尝试更改字体。例如: - 试着把它放在你的小提琴里ctx.font = '40px Arial';
0赞 psycho brm 9/14/2013
顺便说一句,(区分大小写)变量到底是从哪里来的??它在任何地方都没有定义。这部分代码仅在您更改字体时执行。Words
1赞 jbaylina 9/16/2013
@psychobrm 你是绝对正确的。这是一个错误(我已经修复了它)。仅当必须将单词分成两行时,才会执行这部分代码。谢谢!
0赞 psycho brm 10/11/2013
我做了一些我需要的升级:渲染空间、渲染前导/尾随换行符、渲染描边和填充一次调用(不要两次测量文本),我还必须更改迭代,因为不适用于扩展。你能把它放在github上,以便我们可以迭代它吗?for inArray.prototype
0赞 jbaylina 10/26/2013
@psychobrm我合并了您的更改。谢谢!
20赞 Jake 7/22/2013 #10

这是我的解决方案,修改这里已经介绍的流行的 wrapText() 函数。我正在使用 JavaScript 的原型设计功能,以便您可以从画布上下文调用该函数。

CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) {

    var lines = text.split("\n");

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

        var words = lines[i].split(' ');
        var line = '';

        for (var n = 0; n < words.length; n++) {
            var testLine = line + words[n] + ' ';
            var metrics = this.measureText(testLine);
            var testWidth = metrics.width;
            if (testWidth > maxWidth && n > 0) {
                this.fillText(line, x, y);
                line = words[n] + ' ';
                y += lineHeight;
            }
            else {
                line = testLine;
            }
        }

        this.fillText(line, x, y);
        y += lineHeight;
    }
}

基本用法:

var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
ctx.fillStyle = "black";
ctx.font = "12px sans-serif";
ctx.textBaseline = "top";
ctx.wrapText("Hello\nWorld!",20,20,160,16);

这是我整理的一个演示: http://jsfiddle.net/7RdbL/

评论

1赞 couzzi 2/11/2016
像魅力一样工作。谢谢。
2赞 MaKR 9/5/2013 #11

由于遇到同样的问题,我碰巧遇到了这个问题。我正在使用可变字体大小,因此考虑到了这一点:

var texts=($(this).find('.noteContent').html()).split("<br>");
for (var k in texts) {
    ctx.fillText(texts[k], left, (top+((parseInt(ctx.font)+2)*k)));
}

其中 .noteContent 是用户编辑的可编辑内容的 div(嵌套在 jQuery 每个函数中),ctx.font 是“14px Arial”(请注意,像素大小排在第一位)

30赞 Rok Strniša 2/5/2014 #12

将文本分成几行,并分别绘制每行:

function fillTextMultiLine(ctx, text, x, y) {
  var lineHeight = ctx.measureText("M").width * 1.2;
  var lines = text.split("\n");
  for (var i = 0; i < lines.length; ++i) {
    ctx.fillText(lines[i], x, y);
    y += lineHeight;
  }
}
6赞 Tom Söderlund 5/9/2015 #13

这是 Colin 的一个版本,它也支持垂直居中的文本wrapText()context.textBaseline = 'middle'

var wrapText = function (context, text, x, y, maxWidth, lineHeight) {
    var paragraphs = text.split("\n");
    var textLines = [];

    // Loop through paragraphs
    for (var p = 0; p < paragraphs.length; p++) {
        var line = "";
        var words = paragraphs[p].split(" ");
        // Loop through words
        for (var w = 0; w < words.length; w++) {
            var testLine = line + words[w] + " ";
            var metrics = context.measureText(testLine);
            var testWidth = metrics.width;
            // Make a line break if line is too long
            if (testWidth > maxWidth) {
                textLines.push(line.trim());
                line = words[w] + " ";
            }
            else {
                line = testLine;
            }
        }
        textLines.push(line.trim());
    }

    // Move text up if centered vertically
    if (context.textBaseline === 'middle')
        y = y - ((textLines.length-1) * lineHeight) / 2;

    // Render text on canvas
    for (var tl = 0; tl < textLines.length; tl++) {
        context.fillText(textLines[tl], x, y);
        y += lineHeight;
    }
};
0赞 Oleg Berman 5/13/2016 #14

我的 ES5 解决方案:

var wrap_text = (ctx, text, x, y, lineHeight, maxWidth, textAlign) => {
  if(!textAlign) textAlign = 'center'
  ctx.textAlign = textAlign
  var words = text.split(' ')
  var lines = []
  var sliceFrom = 0
  for(var i = 0; i < words.length; i++) {
    var chunk = words.slice(sliceFrom, i).join(' ')
    var last = i === words.length - 1
    var bigger = ctx.measureText(chunk).width > maxWidth
    if(bigger) {
      lines.push(words.slice(sliceFrom, i).join(' '))
      sliceFrom = i
    }
    if(last) {
      lines.push(words.slice(sliceFrom, words.length).join(' '))
      sliceFrom = i
    }
  }
  var offsetY = 0
  var offsetX = 0
  if(textAlign === 'center') offsetX = maxWidth / 2
  for(var i = 0; i < lines.length; i++) {
    ctx.fillText(lines[i], x + offsetX, y + offsetY)
    offsetY = offsetY + lineHeight
  }
}

有关该问题的更多信息,请访问我的博客

7赞 thingEvery 2/12/2018 #15

如果只需要两行文本,则可以将它们拆分为两个不同的 fillText 调用,并为每个调用提供不同的基线。

ctx.textBaseline="bottom";
ctx.fillText("First line", x-position, y-position);
ctx.textBaseline="top";
ctx.fillText("Second line", x-position, y-position);

评论

0赞 Filip BRSJAK 9/27/2023
这是一个很好的解决方案,谢谢!
15赞 Geon George 1/28/2019 #16

我在这里为这个场景创建了一个小库:Canvas-Txt

它以多行方式呈现文本,并提供不错的对齐模式。

为了使用它,您需要安装它或使用 CDN。

安装

npm install canvas-txt --save

JavaScript的

import canvasTxt from 'canvas-txt'

var c = document.getElementById('myCanvas')
var ctx = c.getContext('2d')

var txt = 'Lorem ipsum dolor sit amet'

canvasTxt.fontSize = 24

canvasTxt.drawText(ctx, txt, 100, 200, 200, 200)

这将在位置/尺寸为以下位置的不可见框中呈现文本:

{ x: 100, y: 200, height: 200, width: 200 }

示例小提琴

/* https://github.com/geongeorge/Canvas-Txt  */

const canvasTxt = window.canvasTxt.default;
const ctx = document.getElementById('myCanvas').getContext('2d');

const txt = "Lorem ipsum dolor sit amet";
const bounds = { width: 240, height: 80 };

let origin = { x: ctx.canvas.width / 2, y: ctx.canvas.height / 2, };
let offset = { x: origin.x - (bounds.width / 2), y: origin.y - (bounds.height / 2) };

canvasTxt.fontSize = 20;

ctx.fillStyle = '#C1A700';
ctx.fillRect(offset.x, offset.y, bounds.width, bounds.height);

ctx.fillStyle = '#FFFFFF';
canvasTxt.drawText(ctx, txt, offset.x, offset.y, bounds.width, bounds.height);
body {
  background: #111;
}

canvas {
  border: 1px solid #333;
  background: #222; /* Could alternatively be painted on the canvas */
}
<script src="https://unpkg.com/[email protected]/build/index.js"></script>

<canvas id="myCanvas" width="300" height="160"></canvas>

评论

0赞 Mr. Polywhirl 6/3/2020
我继续定义了一些变量来帮助“自我记录”示例。它还处理画布内边界框的中心。我还在后面添加了一个矩形,因此您实际上可以看到它以关系为中心。干得好!+1 我注意到的一件小事是,换行的行不会抑制其前导空格。您可能希望修剪每条线,例如 我只是在您网站上的交互式演示中注意到了这一点。ctx.fillText(txtline.trim(), textanchor, txtY)
0赞 Geon George 6/3/2020
@Mr.Polywhirl 谢谢你澄清答案。我已经修复了修剪问题并发布了版本。演示站点通过更新软件包版本进行修复。多个空格存在问题。我不知道是采用固执己见的软件包更好还是忽略这个问题更好。从多个地方收到这方面的请求。无论如何,我继续添加修剪。 这就是我一开始没有这样做的原因。您认为我应该考虑多个空格并在只有一个空格时才删除吗?2.0.9Lorem ipsum dolor, sit <many spaces> amet
0赞 Geon George 6/3/2020
编辑:似乎StackOverflow代码块也忽略了多个空格
4赞 jayjey 3/3/2020 #17

这个问题不是从画布的工作原理的角度来思考的。如果您想要换行符,只需调整下一个 .ctx.fillText

ctx.fillText("line1", w,x,y,z)
ctx.fillText("line2", w,x,y,z+20)
1赞 Linh 12/24/2020 #18

这是我在画布中绘制多行文本中心的功能(只换行,不要换字)

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

let text = "Hello World \n Hello World 2222 \n AAAAA \n thisisaveryveryveryveryveryverylongword. "
ctx.font = "20px Arial";

fillTextCenter(ctx, text, 0, 0, c.width, c.height)

function fillTextCenter(ctx, text, x, y, width, height) {
    ctx.textBaseline = 'middle';
    ctx.textAlign = "center";

    const lines = text.match(/[^\r\n]+/g);
    for(let i = 0; i < lines.length; i++) {
        let xL = (width - x) / 2
        let yL =  y + (height / (lines.length + 1)) * (i+1)

        ctx.fillText(lines[i], xL, yL)
    }
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #000;"></canvas>

如果你想使文字大小适合画布,你也可以在这里查看

4赞 Steve Landey 4/19/2021 #19

有一个名为 Canvas-Txt 的维护良好的 JS 库,它能够处理换行符和文本换行。我发现它比这个线程中的所有随机代码片段都更有用。

3赞 Văn Quyết 5/6/2022 #20

我创建了 wraptext 库,它有助于换行文本以适应视图的限制。

为了包装文本,我们需要实现 2 个逻辑:

  • 线拟合:确保没有线超过视图宽度。这可以通过使用 Canvas API 测量文本宽度来完成。
  • 换行:确保换行在正确的位置断开(换行机会)。此逻辑的实现方式可能不同,但最好遵循此 Unicode 的文档,因为它支持所有语言。

虽然这个答案离问题发布的那一刻太远了,但我希望这对那些有同样问题的人有所帮助。