提问人:Spectraljump 提问时间:2/17/2011 最后编辑:NakilonSpectraljump 更新时间:5/6/2022 访问量:142025
HTML5 canvas ctx.fillText 不会做换行符吗?
HTML5 canvas ctx.fillText won't do line breaks?
问:
如果文本包含“\n”,我似乎无法将文本添加到画布中。我的意思是,换行符不显示/工作。
ctxPaint.fillText("s ome \n \\n <br/> thing", x, y);
上面的代码将在一行上绘制 。"s ome \n <br/> thing"
这是 fillText 的限制还是我做错了?“\n”在那里,没有打印出来,但它们也不起作用。
答:
我认为这也是不可能的,但解决方法是创建一个元素并使用 Javascript 定位它。<p>
评论
fillText()
strokeText()
如果您只想处理文本中的换行符,您可以通过在换行符处拆分文本并多次调用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()
方法,该方法显示字符串在打印时的长度(以像素为单位)。
评论
if
if(idx > 0)
idx
1
if
if
true
恐怕是 Canvas 的局限性。没有多行支持。更糟糕的是,没有内置的方法来测量线高,只有宽度,这使得自己动手变得更加困难!fillText
很多人都编写了自己的多行支持,也许最著名的项目是Mozilla Skywriter。
您需要做的要点是多次调用,同时每次将文本的高度添加到 y 值。(我相信,测量 M 的宽度是 skywriter 人为近似文本所做的。fillText
评论
actualBoundingBoxAscent
@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(参见 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/
评论
ctx.save()
ctx.font = '12pt Arial'
parseInt( ctx.font, 10 )
也许来这个聚会有点晚,但我发现以下关于在画布上包装文本的教程是完美的。
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。c
text
正如您可能看到的,我使用的是非标准字体。只要您在操作画布之前在某些文本上使用过该字体,您就可以使用@font面 - 否则画布不会拾取该字体。
希望这对某人有所帮助。
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);
}
我刚刚扩展了添加两个函数: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);
其中可以是:或vAlign
top
center
button
可以是:、 或hAlign
left
center
right
justify
您可以在此处测试库: http://jsfiddle.net/4WRZj/1/
以下是该库的代码:
// 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);
评论
Uncaught ReferenceError: Words is not defined
如果我尝试更改字体。例如: - 试着把它放在你的小提琴里ctx.font = '40px Arial';
Words
for in
Array.prototype
这是我的解决方案,修改这里已经介绍的流行的 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)));
}
其中 .noteContent 是用户编辑的可编辑内容的 div(嵌套在 jQuery 每个函数中),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
}
}
有关该问题的更多信息,请访问我的博客。
如果只需要两行文本,则可以将它们拆分为两个不同的 fillText 调用,并为每个调用提供不同的基线。
ctx.textBaseline="bottom";
ctx.fillText("First line", x-position, y-position);
ctx.textBaseline="top";
ctx.fillText("Second line", x-position, y-position);
评论
我在这里为这个场景创建了一个小库: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>
评论
ctx.fillText(txtline.trim(), textanchor, txtY)
2.0.9
Lorem ipsum dolor, sit <many spaces> amet
这个问题不是从画布的工作原理的角度来思考的。如果您想要换行符,只需调整下一个 .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>
如果你想使文字大小适合画布,你也可以在这里查看
有一个名为 Canvas-Txt 的维护良好的 JS 库,它能够处理换行符和文本换行。我发现它比这个线程中的所有随机代码片段都更有用。
我创建了 wraptext 库,它有助于换行文本以适应视图的限制。
为了包装文本,我们需要实现 2 个逻辑:
- 线拟合:确保没有线超过视图宽度。这可以通过使用 Canvas API 测量文本宽度来完成。
- 换行:确保换行在正确的位置断开(换行机会)。此逻辑的实现方式可能不同,但最好遵循此 Unicode 的文档,因为它支持所有语言。
虽然这个答案离问题发布的那一刻太远了,但我希望这对那些有同样问题的人有所帮助。
评论
fillText()