HTML5画布 ctx.filText 不会做换行吗?

如果文本包含“ n”,我似乎不能将文本添加到画布中。我的意思是,分界线不显示/工作。

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

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

这是 filText 的一个限制还是我做错了?那些“ n”在那里,没有打印出来,但是也不起作用。

130728 次浏览

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

如果您只想处理文本中的换行字符,那么可以通过在换行处拆分文本并多次调用 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()方法,它显示了一个字符串在打印时的长度(以像素为单位)。

恐怕这是 Canvas 的 fillText的一个限制。没有多行支持。更糟糕的是,没有内置的方法来测量线的高度,只有宽度,这使得自己做更困难!

很多人编写了他们自己的多行支持,也许最值得注意的项目是 Mozilla Skywriter

您需要做的要点是在每次将文本的高度添加到 y 值的同时进行多个 fillText调用。(我相信,测量 M 的宽度是写空文的人用来近似文本的方法。)

@ Gaby Petrioli提供的字包装(空格中断)代码非常有用。 我已经扩展了他的代码,以支持换行符 \n。另外,通常有边界框的尺寸是有用的,所以 multiMeasureText()返回宽度和高度。

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

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

希望能帮上忙!

我认为你仍然可以依靠 CSS

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

幸运的是,通过 CSS hack-ardry (参见 Typography 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/

也许来参加这个派对有点晚了,但是我发现下面的教程可以很好地在画布上包装文本。

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>

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

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

希望这对谁有帮助。

Canvas 元素不支持换行’n’、 tab’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);
}

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

你可以在 GitHub中找到最后一个版本:

使用这个函数,您可以在一个框中填充/描边多行文本。您可以垂直和水平对齐文本。(它考虑到 \n的,也可以证明文本)。

原型是:

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

在哪里 vAlign可以是: topcenterbutton

hAlign可以是: leftcenterrightjustify

您可以在这里测试 lib: 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);

下面是我的解决方案,修改这里已经提供的流行的 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/

我碰巧发现了这个,因为我也有同样的问题。我使用的是可变字体大小,所以这里考虑到了这一点:

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

在哪里。Note Content 是用户编辑的 contenteditable div (嵌套在 jQuery each 函数中) ,ctx.font 是“14px Arial”(注意,像素大小排在第一位)

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

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

下面是 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;
}
};

我的 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
}
}

关于这个问题的更多信息是 在我的博客上

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

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

我在这里为这个场景创建了一个小型库: 画布-短信

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

为了使用它,您需要安装它或者使用 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/canvas-txt@2.0.6/build/index.js"></script>


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

这个问题不是考虑帆布是如何工作的。如果需要换行,只需调整下一个 ctx.fillText的坐标即可。

ctx.fillText("line1", w,x,y,z)
ctx.fillText("line2", w,x,y,z+20)

下面的函数可以在画布中绘制多条文本中心线(只能中断线条,不能中断单词)

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>

If you want to fit text size to canvas, you can also check here

有一个维护良好的名为 画布-短信的 JS 库,它能够处理换行和文本换行。我发现它比这个线程中的所有随机代码片段都要有用得多。

我已经创建了 包装文本库,它可以帮助包装文本以适应视图的限制。

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

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

虽然这个答案离问题发布的时间还很远,但我希望这对那些有同样问题的人有所帮助。