HTML5 canvas ctx.fillText จะไม่ทำการแบ่งบรรทัด?


110

ดูเหมือนว่าฉันจะไม่สามารถเพิ่มข้อความลงในผืนผ้าใบได้หากข้อความมี "\ n" ฉันหมายความว่าเส้นแบ่งไม่แสดง / ทำงาน

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

โค้ดด้านบนจะวาด"s ome \n <br/> thing"เป็นบรรทัดเดียว

นี่เป็นข้อ จำกัด ของ FillText หรือฉันทำผิด? "\ n" อยู่ที่นั่นและไม่ได้พิมพ์ออกมา แต่ก็ใช้ไม่ได้เช่นกัน


1
คุณต้องการห่อโดยอัตโนมัติเมื่อถึงจุดสิ้นสุดหรือไม่? หรือเพียงแค่คำนึงถึงตัวอักษรขึ้นบรรทัดใหม่ที่มีอยู่ในข้อความ
Gabriele Petrioli

ตัดข้อความเป็นหลายบรรทัด
ทาวเวอร์

สวัสดีคุณ twodordan ข้อ จำกัด นี้มีอยู่ทั้งใน Chrome และ Mozilla หรือไม่? ผู้คนมักใช้ข้อความ html ธรรมดาที่วางไว้บนผืนผ้าใบด้วยตำแหน่ง: ตัวอย่างแน่นอน นอกจากนี้คุณสามารถทำสอง FillText และย้ายจุดเริ่มต้น Y ของข้อความของคุณสำหรับบรรทัดที่สองของคุณ
ทิม


TL; DR:เรียกfillText()หลายครั้งและใช้ความสูงแบบอักษรของคุณเพื่อแยกหรือใช้developer.mozilla.org/en-US/docs/Web/API/TextMetrics developer.mozilla.org/en-US/docs/Web/API / … - หรือใช้ "วิธีแก้ปัญหา" ที่ซับซ้อนมากด้านล่างซึ่งไม่ใช้ TextMetrics ...
Andrew

คำตอบ:


63

ฉันเกรงว่ามันเป็นข้อ จำกัด ของผ้าใบ fillTextไม่มีการสนับสนุนหลายบรรทัด ที่แย่ไปกว่านั้นคือไม่มีวิธีวัดความสูงของเส้นในตัวมี แต่ความกว้างทำให้การทำด้วยตัวเองยากยิ่งขึ้น!

ผู้คนจำนวนมากได้เขียนสนับสนุนหลายสายของตัวเองอาจจะเป็นโครงการที่โดดเด่นที่สุดคือการที่มีMozilla Skywriter

ส่วนสำคัญของสิ่งที่คุณต้องทำคือการfillTextโทรหลายครั้งในขณะที่เพิ่มความสูงของข้อความเป็นค่า y ในแต่ละครั้ง (การวัดความกว้างของ M คือสิ่งที่คนเขียนสกายเขียนโดยประมาณฉันเชื่อว่า)


ขอบคุณ! ฉันรู้สึกว่ามันน่ารำคาญ ... ยินดีที่ได้รู้จัก SKYWRITER แต่ฉันจะ "รอ" จนกว่า fillText () จะดีขึ้น มันไม่ใช่ข้อตกลงที่สำคัญอย่างยิ่งในกรณีของฉัน ฮ่า ๆ ไม่มีความสูงของเส้นมันเหมือนมีใครบางคนทำแบบนั้นโดยตั้งใจ : D
Spectraljump

19
บอกตามตรงว่าฉันจะไม่กลั้นหายใจเมื่อ fillText () ได้รับการ "ปรับปรุง" เพื่อรองรับสิ่งนี้เนื่องจากฉันรู้สึกว่านี่คือวิธีการใช้งาน (การโทรหลายครั้งและการคำนวณ yOffset ด้วยตัวคุณเอง) ฉันคิดว่าพลังอย่างมากของ canvas API คือการแยกฟังก์ชันการวาดภาพระดับล่างออกจากสิ่งที่คุณทำได้อยู่แล้ว (ทำการวัดที่จำเป็น) นอกจากนี้คุณสามารถทราบความสูงของข้อความได้ง่ายๆโดยระบุขนาดตัวอักษรเป็นพิกเซล กล่าวอีกนัยหนึ่ง: context.font = "16px Arial"; - คุณมีความสูงที่นั่น ความกว้างเป็นความกว้างเดียวที่เป็นแบบไดนามิก
Lev

1
บางคุณสมบัติเพิ่มเติมสำหรับการmeasureText()ได้รับการเพิ่มซึ่งผมคิดว่าสามารถแก้ปัญหาที่เกิดขึ้น Chrome มีการตั้งค่าสถานะให้เปิดใช้งาน แต่เบราว์เซอร์อื่น ๆ ยังไม่ ...
SWdV

@SWdV เพื่อให้ชัดเจนสิ่งเหล่านี้อยู่ในสเป็คมาหลายปีแล้วอาจเป็นเวลาหลายปีจนกว่าเราจะมีการนำไปใช้อย่างกว้างขวางพอที่จะใช้ :(
Simon Sarris

อาจactualBoundingBoxAscentใช้วัดความสูงของเส้นได้
อาทิตย์ฤดูร้อน

69

หากคุณต้องการดูแลตัวอักษรขึ้นบรรทัดใหม่ในข้อความคุณสามารถจำลองได้โดยการแยกข้อความที่บรรทัดใหม่และโทรหลาย ๆ ครั้ง 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()วิธีการที่แสดงให้เห็นว่าสตริงจะยาวแค่ไหน (เป็นพิกเซล ) เมื่อพิมพ์


วิธีการปรับข้อความยาวทั้งหมด?
Amirhossein Tarmast

หากคุณต้องการข้อความที่ยาวและสมเหตุสมผลทำไมคุณถึงใช้ผ้าใบ
Mike 'Pomax' Kamermans

39

อาจจะมางานปาร์ตี้นี้ช้าหน่อย แต่ฉันพบว่าบทช่วยสอนต่อไปนี้สำหรับการตัดข้อความบนผืนผ้าใบนั้นสมบูรณ์แบบ

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>

cID ของผืนผ้าใบของฉันอยู่ที่ไหนและtextเป็นรหัสของกล่องข้อความของฉัน

ดังที่คุณอาจเห็นว่าฉันใช้แบบอักษรที่ไม่ได้มาตรฐาน คุณสามารถใช้ @ font-face ได้ตราบเท่าที่คุณใช้ฟอนต์กับข้อความบางอย่าง PRIOR เพื่อจัดการกับผืนผ้าใบมิฉะนั้นผืนผ้าใบจะไม่รับแบบอักษร

หวังว่านี่จะช่วยใครบางคนได้


26

แยกข้อความออกเป็นเส้นและวาดแยกกัน:

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

17

นี่คือวิธีแก้ปัญหาของฉันโดยแก้ไขฟังก์ชัน 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/


ทำงานอย่างมีเสน่ห์ ขอบคุณ.
couzzi

13

ฉันเพิ่งขยาย CanvasRenderingContext2D เพิ่มสองฟังก์ชัน: mlFillText และ mlStrokeText

คุณสามารถค้นหาเวอร์ชันล่าสุดได้ในGitHub :

ด้วยฟังก์ชั่นนี้คุณสามารถเติม / ขีดเส้นข้อความ miltiline ในกล่อง คุณสามารถจัดแนวข้อความในแนวตั้งและแนวนอน (ใช้ในบัญชี \ n และยังสามารถปรับข้อความได้)

ต้นแบบคือ:

ฟังก์ชัน mlFillText (ข้อความ, x, y, w, h, vAlign, hAlign, lineheight); ฟังก์ชัน mlStrokeText (ข้อความ, x, y, w, h, vAlign, hAlign, lineheight);

โดยที่ vAlign สามารถเป็น: "top", "center" หรือ "button" และ hAlign สามารถเป็น: "left", "center", "right" หรือ "justify"

คุณสามารถทดสอบ lib ได้ที่นี่: 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';- ลองใส่มันลงในซอของคุณ
psycho brm

Btw Wordsตัวแปรนรก(กรณี - ตัวพิมพ์เล็ก) มาจากไหน ?? ไม่ได้กำหนดไว้ที่ใดก็ได้ โค้ดส่วนนั้นจะถูกเรียกใช้งานเมื่อคุณเปลี่ยนฟอนต์เท่านั้น ..
psycho brm

1
@psychobrm คุณพูดถูก มันเป็นจุดบกพร่อง (ฉันแก้ไขแล้ว) โค้ดส่วนนี้จะดำเนินการก็ต่อเมื่อคุณต้องแบ่งคำออกเป็นสองบรรทัด ขอบคุณ!
jbaylina

ผมได้ทำการอัพเกรดบางอย่างที่ฉันต้องการ: ทำให้ช่องว่างทำให้ชั้นนำ / ต่อท้ายบรรทัดใหม่ทำให้จังหวะและปฏิบัติกับคนโทร (Dont มาตรการข้อความสอง) ฉันยังมีการเปลี่ยนแปลงซ้ำเนื่องจากไม่ได้ทำงานได้ดีกับการขยายfor in Array.prototypeคุณช่วยวางไว้ใน github เพื่อให้เราทำซ้ำได้ไหม
psycho brm

@psychobrm ฉันรวมการเปลี่ยนแปลงของคุณ ขอบคุณ!
jbaylina

8

การใช้จาวาสคริปต์ฉันได้พัฒนาโซลูชัน มันไม่สวยงาม แต่มันใช้ได้สำหรับฉัน:


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
สวัสดีสมมติว่าข้อความของฉันเป็นแบบนี้ var text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa แล้วอะไรคือความสุขบนผืนผ้าใบ ???
Amol Navsupe

มันจะออกไปจากผืนผ้าใบเนื่องจาก @Ramirez ไม่ได้ใส่พารามิเตอร์ maxWidth ใน fillText :)
KaHa6uc

6

รหัสสำหรับการตัดคำ (แบ่งช่องว่าง) ที่ให้ไว้โดย@Gaby Petrioliมีประโยชน์มาก \nเราได้ขยายรหัสของเขาที่จะให้การสนับสนุนสำหรับตัวอักษรขึ้นบรรทัดใหม่ นอกจากนี้บ่อยครั้งที่การมีขนาดของกรอบขอบเขตมีประโยชน์ดังนั้นจึงmultiMeasureText()ส่งคืนทั้งความกว้างและความสูง

คุณสามารถดูรหัสได้ที่นี่: http://jsfiddle.net/jeffchan/WHgaY/76/


ลิงก์หมดอายุโปรดใส่รหัสในคำตอบนี้แม้ว่าคุณจะมีลิงก์ที่ใช้งานได้ก็ตาม หาก jsfiddle ปิดตัวลงคำตอบนี้จะไร้ประโยชน์อย่างที่เป็นอยู่
Mike 'Pomax' Kamermans

5

นี่คือเวอร์ชันของ 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;
    }
};

5

หากคุณต้องการข้อความเพียงสองบรรทัดคุณสามารถแบ่งมันออกเป็นสองการเรียก FillText ที่แตกต่างกันและกำหนดพื้นฐานที่แตกต่างกัน

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

4

คำถามนี้ไม่ได้คิดในแง่ของวิธีการทำงานของแคนวาส ctx.fillTextถ้าคุณต้องการแบ่งบรรทัดเพียงแค่ปรับพิกัดของต่อไปของคุณ

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

3

ฉันคิดว่าคุณยังสามารถพึ่งพา CSS ได้

ctx.measureText().height doesnt exist.

โชคดีที่ผ่าน CSS hack-ardry (ดูเมตริกแบบตัวพิมพ์สำหรับวิธีอื่น ๆ ในการแก้ไขการใช้งานการวัด CSS แบบเก่า) เราสามารถค้นหาความสูงของข้อความผ่านการวัดออฟเซ็ตความสูงของ a ที่มีคุณสมบัติแบบอักษรเดียวกัน:

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 )โปรดทราบว่าฉันใช้ 'pt' เมื่อตั้งค่า จากนั้นจะแปลเป็น PX และสามารถเปลี่ยนเป็นตัวเลขเพื่อการบริโภคตามความสูงของแบบอักษร
Eric Hodonsky

3

ฉันสร้างไลบรารีเล็ก ๆ สำหรับสถานการณ์นี้ที่นี่: 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 }

ตัวอย่าง Fiddle

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


ฉันดำเนินการต่อและกำหนดตัวแปรบางอย่างเพื่อช่วยในการ "จัดทำเอกสาร" ตัวอย่าง นอกจากนี้ยังจัดการกับการจัดกรอบขอบเขตภายในผ้าใบ ฉันเพิ่มรูปสี่เหลี่ยมด้านหลังด้วยดังนั้นคุณจะเห็นว่ามันอยู่กึ่งกลางโดยสัมพันธ์กัน การทำงานที่ดี! +1 สิ่งเล็ก ๆ น้อย ๆ ที่ฉันสังเกตเห็นก็คือเส้นที่พันจะไม่มีช่องว่างนำหน้า คุณอาจต้องการตัดแต่ละบรรทัดเช่นctx.fillText(txtline.trim(), textanchor, txtY)ฉันสังเกตเห็นสิ่งนี้ในการสาธิตแบบโต้ตอบบนเว็บไซต์ของคุณเท่านั้น
Mr.Polywhirl

@ Mr.Polywhirl ขอบคุณที่เคลียร์คำตอบ ฉันได้แก้ไขปัญหาการตัดแต่งและเผยแพร่2.0.9เวอร์ชันแล้ว ไซต์สาธิตได้รับการแก้ไขโดยการอัปเดตเวอร์ชันแพ็กเกจ มีปัญหาเกี่ยวกับช่องว่างหลายช่อง ฉันไม่รู้ว่าจะดีกว่าถ้าใช้แพคเกจที่แสดงความคิดเห็นหรือเพิกเฉยต่อปัญหา ได้รับคำขอนี้จากหลายแห่ง ฉันไปข้างหน้าและเพิ่มการตัดแต่งต่อไป Lorem ipsum dolor, sit <many spaces> amet นี่เป็นเหตุผลว่าทำไมฉันถึงไม่ทำตั้งแต่แรก คุณคิดว่าฉันควรพิจารณาช่องว่างหลายช่องและลบออกหากมีเพียงช่องเดียวเท่านั้น
Geon George

แก้ไข: ดูเหมือนว่าบล็อกโค้ด StackOverflow จะละเว้นหลายช่องว่างเช่นกัน
Geon George

2

ฉันไม่คิดว่าสิ่งนี้จะเป็นไปได้ แต่วิธีแก้ปัญหาสำหรับสิ่งนี้คือการสร้าง<p>องค์ประกอบและวางตำแหน่งด้วย Javascript


ใช่นั่นคือสิ่งที่ฉันคิดจะทำ เพียงแค่มีfillText()และstrokeText()คุณสามารถทำสิ่งต่างๆนอกเหนือจากที่ CSS ทำได้
ทาวเวอร์

ฉันยังไม่ได้ทดสอบ แต่ฉันคิดว่านี่อาจเป็นทางออกที่ดีกว่า - วิธีแก้ปัญหาอื่น ๆ ที่นี่โดยใช้ fillText () ทำให้ไม่สามารถเลือกข้อความได้ (หรือน่าจะวาง)
Jerry Asher

2

ฉันเจอปัญหานี้เนื่องจากมีปัญหาเดียวกัน ฉันกำลังทำงานกับขนาดตัวอักษรที่เปลี่ยนแปลงได้ดังนั้นสิ่งนี้จึงคำนึงถึง:

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" (โปรดสังเกตว่าขนาดพิกเซลมาก่อน)


0

องค์ประกอบ Canvas ไม่รองรับอักขระเช่นแท็ก newline '\ 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); 
}  

0

โซลูชัน 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
  }
}

ข้อมูลเพิ่มเติมเกี่ยวกับปัญหาคือในบล็อกของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.