Как обернуть текст в HTML Canvas

Хотя добавление текста в HTML Canvas очень распространено, в нем нет встроенной функции переноса строки. Это означает, что если наш текст слишком длинный, он будет обрываться на конце. Возьмем пример ниже, где текст должен быть таким: «Здравствуйте, эта текстовая строка очень длинная. Она переполнится». Поскольку он слишком длинный, чтобы поместиться на холсте, он просто переполняется без переносов строк:

Код для этого примера:

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); 
Вход в полноэкранный режим Выйти из полноэкранного режима

Наш текст выше начинается на (85, 200)px, и продолжается без переносов строк. Как бы странно это ни было, нам нужно рассчитать, где должны быть переносы строк в HTML Canvas. Для этого мы можем использовать пользовательскую функцию и использовать данные из этой функции, чтобы расставить переносы строк по местам.

Как обернуть текст в HTML Canvas

Когда мы создаем нашу пользовательскую функцию для обертывания текста в HTML, нам нужно учитывать, когда происходит разрыв строки. Разрыв строки обычно происходит, когда следующее слово выходит за пределы ширины родительского элемента — в данном случае холста. Когда мы создаем нашу функцию для обертывания текста, нам нужно проверить, не приведет ли следующее слово в предложении к переполнению.

Для этого мы создадим функцию, которая принимает несколько различных переменных:

// @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;
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Эта функция работает на нескольких предпосылках:

  • Мы проверяем новую строку с помощью measureText(). Если она слишком длинная для контейнера, то мы начинаем новую строку. В противном случае мы остаемся на текущей.
  • Мы используем предопределенную высоту строки, чтобы иметь постоянную высоту строки.
  • Мы возвращаем массив [ lineText, x, y ] для каждой строки — где lineText — текст этой строки, а x/y — начальная позиция этой конкретной строки.
  • Если есть только одна строка, мы просто возвращаем ее в lineArray.
  • Чтобы применить его к нашему холсту, мы должны выполнить итерацию по каждому элементу из массива. Затем мы используем ctx.fillText, чтобы нарисовать каждую линию в координатах, рассчитанных нашей функцией wrapText() — которая в конечном итоге создаст для нас разрывы строк:
// 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]); 
})
Вход в полноэкранный режим Выход из полноэкранного режима

И в итоге мы получаем обернутый текст:

Теперь мы можем обернуть текст в холст. Окончательный код для приведенного выше примера показан ниже:

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]); 
})
Войти в полноэкранный режим Выход из полноэкранного режима

Заключение

Хотя нам придется написать пользовательскую функцию для обертывания текста в HTML canvas, это не так уж сложно, если понять, как это работает. Надеюсь, вам понравилось это руководство о том, как обернуть текст с помощью HTML canvas. Чтобы узнать больше о HTML canvas, ознакомьтесь с моим полным руководством здесь.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *