Javascript HTML5 canvas ctx.fillText 不会换行吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5026961/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 15:18:54  来源:igfitidea点击:

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

javascripthtmlcanvaslinebreak

提问by Spectraljump

I can't seem to be able to add text to a canvas if the text includes "\n". I mean, the line breaks do not show/work.

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

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

The above code will draw "s ome \n <br/> thing", on one line.

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

Is this a limitation of fillText or am I doing it wrong? the "\n"s are there, and aren't printed, but they don't work either.

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

采纳答案by Simon Sarris

I'm afraid it is a limitation of Canvas' fillText. There is no multi-line support. Whats worse, there's no built-in way to measure line height, only width, making doing it yourself even harder!

恐怕这是 Canvas' 的限制fillText。没有多线支持。更糟糕的是,没有内置的方法来测量线高,只有宽度,让自己更难!

A lot of people have written their own multi-line support, perhaps the most notable project that has is Mozilla Skywriter.

很多人都编写了自己的多线支持,其中最著名的项目可能是Mozilla Skywriter

The gist of what you'll need to do is multiple fillTextcalls while adding the height of the text to the y value each time. (measuring the width of M is what the skywriter people do to approximate text, I believe.)

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

回答by Gabriele Petrioli

If you just want to take care of the newline chars in the text you could simulate it by splitting the text at the newlines and calling multiple times the fillText()

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

Something like http://jsfiddle.net/BaG4J/1/

类似于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>



I just made a wrapping proof of concept (absolute wrap at specified width. No handling words breaking, yet)
example at http://jsfiddle.net/BaG4J/2/

我刚刚 在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>



And a word-wrapping (breaking at spaces) proof of concept.
example at http://jsfiddle.net/BaG4J/5/

以及一个自动换行(在空格处打断)的概念证明。
示例在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>



In the second and third examples i am using the measureText()method which shows how long (in pixels) a string will be when printed.

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

回答by Colin Wiseman

Maybe coming to this party a bit late, but I found the following tutorial for wrapping text on a canvas perfect.

也许来这个派对有点晚,但我发现以下教程非常适合在画布上包装文本。

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

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

From that I was able to think get multi lines working (sorry Ramirez, yours didn't work for me!). My complete code to wrap text in a canvas is as follows:

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

<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>

Where cis the ID of my canvas and textis the ID of my textbox.

c我的画布的 ID在哪里text,我的文本框的 ID 在哪里。

As you can probably see am using a non-standard font. You can use @font-face as long as you have used the font on some text PRIOR to manipulating the canvas - otherwise the canvas won't pick up the font.

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

Hope this helps someone.

希望这可以帮助某人。

回答by Rok Strni?a

Split the text into lines, and draw each separately:

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

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;
  }
}

回答by Jake

Here's my solution, modifying the popular wrapText() function that is already presented here. I'm using the prototyping feature of JavaScript so that you can call the function from the canvas context.

这是我的解决方案,修改此处已提供的流行 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;
    }
}

Basic usage:

基本用法:

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);

Here's a demonstration I put together: http://jsfiddle.net/7RdbL/

这是我放在一起的演示:http: //jsfiddle.net/7RdbL/

回答by jbaylina

I just extended the CanvasRenderingContext2D adding two functions: mlFillText and mlStrokeText.

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

You can find the last version in GitHub:

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

With this functions you can fill / stroke miltiline text in a box. You can align the text verticaly and horizontaly. (It takes in account \n's and can also justify the text).

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

The prototypes are:

原型是:

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

函数 mlFillText(text,x,y,w,h,vAlign,hAlign,lineheight); 函数 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"

其中 vAlign 可以是:“top”、“center”或“button” 而 hAlign 可以是:“left”、“center”、“right”或“justify”

You can test the lib here: http://jsfiddle.net/4WRZj/1/

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

enter image description here

在此处输入图片说明

Here is the code of the library:

这是库的代码:

// 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");
    };
})();

And here is the use example:

这是使用示例:

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);

回答by Ramirez

Using javascript I developed a solution. It isn't beautiful but it worked for me:

使用 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;
}

Hope that helps!

希望有帮助!

回答by jeffchan

The code for word-wrapping (breaking at spaces) provided by @Gaby Petrioliis very helpful. I've extended his code to provide support for newline characters \n. Also, often times it's useful to have the dimensions of the bounding box, so multiMeasureText()returns both the width and the height.

@Gaby Petrioli提供的自动换行代码(在空格处换行)非常有帮助。我扩展了他的代码以提供对换行符的支持\n。此外,通常情况下,具有边界框的尺寸很有用,因此multiMeasureText()返回宽度和高度。

You can see the code here: http://jsfiddle.net/jeffchan/WHgaY/76/

你可以在这里看到代码:http: //jsfiddle.net/jeffchan/WHgaY/76/

回答by thingEvery

If you only need two lines of text, you can split them into two different fillText calls and give each one a different baseline.

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

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

回答by Tom S?derlund

Here's a version of Colin's wrapText()that also supports vertically centered textwith context.textBaseline = 'middle':

这里是科林的一个版本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;
    }
};