Хотя добавление текста в 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, ознакомьтесь с моим полным руководством здесь.