Выводим текст на HTML5 Canvas

    HTML5 Canvas – очень обширная тема со многими “вкусностями”, о многих из которых уже писали и ещё будут писать. Поэтому, я хочу немного рассказать в этой статье, только об одной маленькой, и как на первый взгляд может показаться банальной темой – работа с текстом. Я хочу показать, что с ним почти также можно работать, как с обычным текстом в вебе, т.е. позиционировать, накладывать стили и градиенты, а также писать многострочные предложения легко и без проблем.


    Простой текст


    Чтобы вывести обычный текст, можно воспользоваться двумя функциями контекста fillText() и strokeText().
    Эти функции принимают три обязательных параметра: сам текст и координаты X и Y, т.е. место его расположения, и последний – необязательный: максимальная ширина текста, если вы зададите максимальную ширину текста меньше, чем фактическая ширина, то текст сожмется до указанной вами ширины. Не советую задавать максимальную ширину, так как при сжатии и у него становится плохой вид. Вместо такого сжатия рекомендую переносить текст на новую строку, стандартной функции для этого нет, но я приведу пример, как это легко реализовывается.
    Если просто вывести текст при помощи таких функций, то такой текст будет мелкий и невзрачный. Это мы можем исправить задав ему шрифт в переменную контекста font. Шрифт задается, также как и в CSS ([font style][font weight][font size][font face]). Пример:

        ctx.fillStyle = "#00F";
        ctx.strokeStyle = "#F00";
        ctx.font = "italic 30pt Arial";
        ctx.fillText("Fill text", 20, 50);
        ctx.font = 'bold 30px sans-serif';
        ctx.strokeText("Stroke text", 20, 100);
    


    image

    Demo

    Расположение текста


    Есть два стандартных способа позиционирования текста, относительно своего расположения: вертикальное и горизонтальное.
    Вертикальное позиционирование задается при помощи textBaseline, в неё устанавливается один из возможных вариантом: top, hanging, middle, alphabetic, ideographic и bottom.

        ctx.textBaseline = "bottom";
        ctx.fillText("bottom", 400, 75);
    


    image

    Demo

    А горизонтально при помощи textAlign, может быть один из следующих параметров: center, start, end, left, right.

        context.textAlign = "center";
        ctx.textBaseline = "bottom";
        context.fillText("center", 250, 20);
    


    image

    Demo

    Эти способы далеко не идеальны и при помощи них иногда проблематично спозиционировать текст, так как хочется именно вам. Ниже я опишу способ, как можно использовать своё позиционирование, даже многострочного текста.

    Стилизация
    Самое простое, как мы можем стилизовать текст, это задать ему цвет. Цвет задается при помощи fillStyle – для задания цвета заливки и strokeStyle – для задания цвета обводки.
    Так же как и в CSS3 можно накладывать тени и на текст в канвасе. Это делается при помощи: shadowColor – задание цвета тени, shadowOffsetX и shadowOffsetY – задание отступа и shadowBlur – задание размытия тени.

        ctx.shadowColor = "#F00";
        ctx.shadowOffsetX = 5;
        ctx.shadowOffsetY = 5;
        ctx.shadowBlur = 5;
        ctx.strokeText("Shadow text", 20, 100);
    


    image

    Поддерживаются также и градиенты для текста. Сам градиент создается при помощи функции createLinearGradient(). А при помощи функции addColorStop() задаются цвета и позиции их в нём. Текст заливается градиентом также как и сплошным цветом, при помощи fillStyle и strokeStyle.

        var gradient = ctx.createLinearGradient(0, 0, 0, 60);
        gradient.addColorStop(0.0, 'rgba(0, 0, 255, 1)');
        gradient.addColorStop(0.3, 'rgba(128, 0, 255, 0.6)');
        gradient.addColorStop(0.6, 'rgba(0, 0, 255, 0.4)');
        gradient.addColorStop(1.0, 'rgba(0, 255, 0, 0.2)');
        ctx.fillStyle = gradient;
    


    image

    Кроме обычной заливки и заливки градиентом можно также залить какой-либо текстурой. Для этого необходимо до начала вывода текста загрузить изображение-текстуру. А затем при помощи createPattern() создать текстуру на его основе. Можно задать, чтобы изображение повторялось либо нет.

        var pattern = ctx.createPattern(img, 'repeat');
        ctx.fillStyle = pattern;
    


    image

    Demo

    Ширина текста и многострочный текст



    Допустим, вам необходимо написать текст на канвасе неограниченной длинны, будь то одно слово, или несколько предложений. Если вы будете его писать как есть, т.е. весь текст вставите в fillText(), то на экране будет видна только та часть, которая по своей ширине не превышает ширину канваса. Т.е. если ширина канваса 400 пикселей, то вы не уведете текст, который выходит за его ширину. Для того, чтобы сделать многострочный текст необходимо исхитрится. Но алгоритм прост. Сначала высчитываем ширину текста, сверяем её с максимальной шириной области, куда мы хотим вывести этот текст. Если ширина текста не превышает ту ширину, то выводим текст, а если превышает, то разбиваем текст самым удобным для вас способом, я предпочитаю разбивать по пробелам.
    Для того чтобы высчитать ширину текста можно воспользоваться функцией measureText(), которая отдает в объектной форме ширину текста (к сожалению данная функция не отдает высоту текста, надеюсь эта возможность будет добавлена, но а пока придется высчитывать иными способами, как именно далее в статье).
    Пример:

    function wrapText(context, text, marginLeft, marginTop, maxWidth, lineHeight)
        {
            var words = text.split(" ");
            var countWords = words.length;
            var line = "";
            for (var n = 0; n < countWords; n++) {
                var testLine = line + words[n] + " ";
                var testWidth = context.measureText(testLine).width;
                if (testWidth > maxWidth) {
                    context.fillText(line, marginLeft, marginTop);
                    line = words[n] + " ";
                    marginTop += lineHeight;
                }
                else {
                    line = testLine;
                }
            }
            context.fillText(line, marginLeft, marginTop);
        }
        var canvas = document.getElementById("canvas");
        var context = canvas.getContext("2d");
        var maxWidth = 400; //размер поле, где выводится текст
        var lineHeight = 25;
        /*если мы знаем высоту текста, то мы можем
         предположить, что высота строки должна быть именно такой*/
        var marginLeft = 20;
        var marginTop = 40;
        var text = "Сначала мы разбиваем текст на слова по пробелам, а потом обходим эти слова в цикле, " +
                "объединяя их по одному в строку. Если при последнем объединении ширина этой строки меньше максимальной, " +
                "то продолжаем, а если больше, то выводим строку без последнего слова, а его записываем в новую строку." +
                "И так продолжаем, пока не обработаем весь текст.";
        context.font = "16pt Calibri";
        context.fillStyle = "#000";
        wrapText(context, text, marginLeft, marginTop, maxWidth, lineHeight);
    


    image

    Demo

    Вроде как всё хорошо, не нужно задавать координаты текста, самому вручную вычислять влезет ли текст по ширине или нет. Почти всё автоматизировано и выглядит неплохо, но остается всё-таки маленький нюанс, высота текста у нас захардкоджена, по этому при смени шрифта или размера придется каждый раз подправлять вручную высоту текста (в примере переменная lineHeight).

    Определяем высоту текста



    При работе с текстом на канвасе ни функция measureText(), ни одна другая не дают нам определить высоту текста. Всё же можно справиться и с этой бедой, вариант решения этой проблемы, который я хочу предложить вам, не очень красив, но всё же оно работает.
    Когда мы задаем стиль текста (размер и стиль шрифта) в font, то мы его задаем в таком же стиле, так как бы мы его задавали в CSS. Шрифты и их размеры, конечно если доступен такой шрифт в канвасе, то также он будет доступен и в обычных стилях.
    И так, идея вот в чем, у нас есть размер и тип шрифта в переменной font, мы создаем в DOM новый элемент, туда помещаем текст, и устанавливаем стили из той переменной. Далее при помощи offsetHeight получаем высоту элемента, которая и будет высотой нашего текста. После этого можем благополучно удалить этот элемент из DOM.
    Пример реализации этой идеи:

    var text = "Этот текст должен быть написан справа в самом низу.";
        var marginLeft = canvas.width - context.measureText(text).width;
        var marginTop = canvas.height - getFontHeight(context.font);
        context.fillText(text, marginLeft, marginTop);
        function getFontHeight(font) {
            var parent = document.createElement("span");
            parent.appendChild(document.createTextNode("height"));
            document.body.appendChild(parent);
            parent.style.cssText = "font: " + font + "; white-space: nowrap; display: inline;";
            var height = parent.offsetHeight;
            document.body.removeChild(parent);
            return height;
        }
    


    Результат:

    image

    Demo

    Буду рад, если кто-то предложит более красивый или правильный вариант получения высоты текста.
    Спасибо.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 21

      +1
      Спасибо! Через лет пять пригодится!
        +6
        Пользуетесь IE?
          +6
          Посетители сайта пользуются IE
            0
            excanas не спасает?
              0
              может ExCannabis?
              Так он же из VML сделан — заметут.
            0
            Никак нет
            0

            Ура, начали пользоваться! Как раз 5 лет прошло!

            0
            Все хорошо, спасибо. Но если требуется динамическое обновление контента, то лучше не пользоваться текстом на канвасе — уж больно он прожорливый
              +1
              Помоему в первую очередь минус в том, что поисковые боты не хавают этот текст. Хотя, я уверен, что применение есть этой библиотеке.
                0
                Есть такая проблема: при Zoom-In в браузерах текст отрендеренный в canvas теряет антиалиасинг и выглядит как увеличенный битмап. Интересно, можно ли с этим что-то сделать?
                • UFO just landed and posted this here
                    0
                    Так, получается, мы забьем на subpixel rendering. Но если я правильно понимаю, то размер canvas-а фиксируется и он не перерисовывается при zoom-е (пока мы сами не начнем слушать изменение zoom level-а, что не совсем тривиально). Т.е. проблема остается.
                    • UFO just landed and posted this here
                        0
                        Не, я просто пытаюсь понять применимость. Подписывать фотки да, строить что-то сложнее — нет. И антиалиасинг, имхо, проблема номер 1. Просто с современными ноутами 15" и 1920 в ширину без зума никуда.
                        • UFO just landed and posted this here
                  +2
                  Я так понимаю что основной плюс в том, что текст виден в source code и есть надежда что будет индексироваться лучше чем текст на картинке или flash. Но он же не выделяется :)
                    0
                    Выделение без особых проблем эмулируется
                    0
                    Реально интересны только два последних примера. Остальное уже где только не обмусолено. Давайте больше интересностей ;)
                      0
                      Вот, пожалуйте, вам интересности. TeamLab Document Editor — первый 100% HTML5 редактор документов, в полной мере использует возможности Canvas. Скоро опубликуем подробный пост по технологии и разработке.
                        0
                        Спасибо за код для многострочного текста, пригодился!
                          0
                          Картинки к выравниванию горизонтальному/вертикальному перепутаны местами. (Странно, что никто не написал)

                          Only users with full accounts can post comments. Log in, please.