Jak zalomit text v HTML Canvas

I když je přidávání textu na HTML plátno velmi běžné, není zde zabudovaná žádná funkce zalamování řádků. To znamená, že je-li náš text příliš dlouhý, text se na konci spustí. Vezměte si příklad níže, kde má být text "Dobrý den, tento textový řádek je velmi dlouhý. Přeteče". Protože je příliš dlouhý na to, aby se vešel na plátno, přeteče bez zalomení řádků:

Kód pro tento příklad:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

let grd = ctx.createLinearGradient(0, 853, 1352, 0);
grd.addColorStop(0, '#00a0ff');
grd.addColorStop(1, '#12cba6');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 1342, 853);

// More text
ctx.font = '700 95px Helvetica';
ctx.fillStyle = 'white';
ctx.fillText("Hello, this text line is very long. It will overflow.", 85, 200); 

Náš text výše začíná na (85, 200) px a pokračuje bez zalomení řádků. Jakkoli je to zvláštní, musíme si sami spočítat, kde by měly být konce řádků v HTML Canvas. K tomu můžeme použít vlastní funkci a použít data z této funkce k umístění zalomení řádků.

Jak zalamovat text do HTML Canvas

Když vytváříme naši vlastní funkci pro zalamování textu do HTML, musíme vzít v úvahu, kdy dojde k zalomení řádku. K přerušení řádku obvykle dochází, když další slovo přesáhne šířku nadřazeného prvku – v tomto případě našeho plátna. Když vytváříme naši funkci pro zalamování textu, musíme zkontrolovat, zda další slovo ve větě nezpůsobí přetečení.

Proto vytvoříme funkci, která přijímá několik různých proměnných:

  • ctx - kontext pro plátno, na kterém chceme zalomit text.text - text, který chceme zalomit.
  • x - počáteční bod X textu na plátně.
  • y - počáteční bod Y textu na plátně.
  • maxWidth - šířka, na které chceme, aby zalomení řádků začínalo - tj. maximální šířka plátna.
  • lineHeight - výška každého řádku, abychom je mohli umístit pod sebe. Pojďme se podívat na funkci, kterou jsem pro to vytvořil:
// @description: wrapText wraps HTML canvas text onto a canvas of fixed width
// @param ctx - the context for the canvas we want to wrap text on
// @param text - the text we want to wrap.
// @param x - the X starting point of the text on the canvas.
// @param y - the Y starting point of the text on the canvas.
// @param maxWidth - the width at which we want line breaks to begin - i.e. the maximum width of the canvas.
// @param lineHeight - the height of each line, so we can space them below each other.
// @returns an array of [ lineText, x, y ] for all lines
const wrapText = function(ctx, text, x, y, maxWidth, lineHeight) {
    // First, start by splitting all of our text into words, but splitting it into an array split by spaces
    let words = text.split(' ');
    let line = ''; // This will store the text of the current line
    let testLine = ''; // This will store the text when we add a word, to test if it's too long
    let lineArray = []; // This is an array of lines, which the function will return

    // Lets iterate over each word
    for(var n = 0; n < words.length; n++) {
        // Create a test line, and measure it..
        testLine += `${words[n]} `;
        let metrics = ctx.measureText(testLine);
        let testWidth = metrics.width;
        // If the width of this test line is more than the max width
        if (testWidth > maxWidth && n > 0) {
            // Then the line is finished, push the current line into "lineArray"
            lineArray.push([line, x, y]);
            // Increase the line height, so a new line is started
            y += lineHeight;
            // Update line and test line to use this word as the first word on the next line
            line = `${words[n]} `;
            testLine = `${words[n]} `;
        }
        else {
            // If the test line is still less than the max width, then add the word to the current line
            line += `${words[n]} `;
        }
        // If we never reach the full max width, then there is only one line.. so push it into the lineArray so we return something
        if(n === words.length - 1) {
            lineArray.push([line, x, y]);
        }
    }
    // Return the line array
    return lineArray;
}

Tato funkce funguje na několika místech:

  • Nový řádek testujeme pomocí measureText() . Pokud je pro kontejner příliš dlouhý, zahájíme nový řádek. Jinak zůstaneme na té aktuální.
  • Používáme předdefinovanou výšku čáry, abychom mohli mít konzistentní výšky čar.
  • Vrátíme pole [ lineText, x, y ] pro každý řádek - kde lineText je text pro tento řádek a x /y je počáteční pozice této konkrétní čáry.
  • Pokud existuje pouze jeden řádek, vrátíme tento řádek v lineArray .
  • Chceme-li jej použít na naše plátno, musíme iterovat každý prvek z pole. Pak použijeme ctx.fillText nakreslit každou čáru na souřadnicích vypočítaných naším wrapText() funkce – která nám nakonec vytvoří zalomení řádků:
// Set up our font and fill style
ctx.font = '700 95px Helvetica';
ctx.fillStyle = 'white';
// we pass in ctx, text, x, y, maxWidth, lineHeight to wrapText()
// I am using a slightly smaller maxWidth than the canvas width, since I wanted to add padding to either side of the canvas.
let wrappedText = wrapText(ctx, "This line is way too long. It's going to overflow - but it should line break.", 85, 200, 1050, 140);
// wrappedTe
wrappedText.forEach(function(item) {
    // item[0] is the text
    // item[1] is the x coordinate to fill the text at
    // item[2] is the y coordinate to fill the text at
    ctx.fillText(item[0], item[1], item[2]); 
})

A skončíme u zalomeného textu:

Nyní můžeme zalomit text na plátno. Konečný kód pro výše uvedený příklad je uveden níže:

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

canvas.width = 1200;
canvas.height = 800;

// @description: wrapText wraps HTML canvas text onto a canvas of fixed width
// @param ctx - the context for the canvas we want to wrap text on
// @param text - the text we want to wrap.
// @param x - the X starting point of the text on the canvas.
// @param y - the Y starting point of the text on the canvas.
// @param maxWidth - the width at which we want line breaks to begin - i.e. the maximum width of the canvas.
// @param lineHeight - the height of each line, so we can space them below each other.
// @returns an array of [ lineText, x, y ] for all lines
const wrapText = function(ctx, text, x, y, maxWidth, lineHeight) {
    // First, start by splitting all of our text into words, but splitting it into an array split by spaces
    let words = text.split(' ');
    let line = ''; // This will store the text of the current line
    let testLine = ''; // This will store the text when we add a word, to test if it's too long
    let lineArray = []; // This is an array of lines, which the function will return

    // Lets iterate over each word
    for(var n = 0; n < words.length; n++) {
        // Create a test line, and measure it..
        testLine += `${words[n]} `;
        let metrics = ctx.measureText(testLine);
        let testWidth = metrics.width;
        // If the width of this test line is more than the max width
        if (testWidth > maxWidth && n > 0) {
            // Then the line is finished, push the current line into "lineArray"
            lineArray.push([line, x, y]);
            // Increase the line height, so a new line is started
            y += lineHeight;
            // Update line and test line to use this word as the first word on the next line
            line = `${words[n]} `;
            testLine = `${words[n]} `;
        }
        else {
            // If the test line is still less than the max width, then add the word to the current line
            line += `${words[n]} `;
        }
        // If we never reach the full max width, then there is only one line.. so push it into the lineArray so we return something
        if(n === words.length - 1) {
            lineArray.push([line, x, y]);
        }
    }
    // Return the line array
    return lineArray;
}


// Add gradient
let grd = ctx.createLinearGradient(0, 1200, 800, 0);
grd.addColorStop(0, '#00a0ff');
grd.addColorStop(1, '#12cba6');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 1200, 800);

// More text
ctx.font = '700 95px Helvetica';
ctx.fillStyle = 'white';
let wrappedText = wrapText(ctx, "This line is way too long. It's going to overflow - but it should line break.", 85, 200, 1050, 140);
wrappedText.forEach(function(item) {
    ctx.fillText(item[0], item[1], item[2]); 
})

Závěr

Ačkoli musíme napsat vlastní funkci pro zalomení textu na plátno HTML, není to příliš těžké, když pochopíte, jak to funguje. Doufám, že se vám tento návod, jak zalomit text pomocí HTML canvas, líbil. Pro více informací o HTML canvas se podívejte na mého kompletního průvodce zde.