Skip to content

Отрисовка цветной консоли в JavaScript

01/03/2010

Для очередного время-убивательного проекта мне потребовалось динамически отрисовывать на JavaScript цветную консоль 80 на 22 символа (меняя цветовую схему), на манер PuTTY. Я делал редактор цветовых схем для Dwarf Fortress. Больше всего меня удивила скорость отрисовки: на моём рабочем компьютере не получалось выжать больше 3–4 кадров в секунду в Chrome.

Итак, есть <div>, в нём 22 строки односимвольных span-ов с Unicode-символами, каждая строка длиной 80 символов. Каждому span’у назначены два класса — один задаёт цвет фона, второй — цвет символа. Причём каждый из этих классов может принимать всего 16 значений (полная палитра — 16 цветов, именно столько использует Dwarf Fortress, в основе — оригинальные 16 цветов для web). С помощью jQuery-плагина Color Picker предполагалось менять по одному цвету за раз.

Сперва я решил делать очень просто и в лоб — выбираются все span’ы с нужным классом, и им меняются background-color и color:

    var set_color = function(name, rgb) {
        $(".back_" + name).css('background-color', 'rgb(' + rgb.join(',') + ')');
        $(".fore_" + name).css('color', 'rgb(' + rgb.join(',') + ')');
    };

Это установка одного цвета. Эта функция принимает название цвета и новые значения RGB с палитры. При вызове set_color('BLACK', {'r':10, 'g':10. 'b':10}); она найдёт все span’ы у которых есть класс back_BLACK и установит им background-color в значение rgb(10, 10, 10). После этого она найдёт все span’ы с классом fore_BLACK и установит color в rgb(10, 10, 10). Звучит просто. На деле некоторые цвета могут затрагивать до 900 span’ов, и установка одного цвета может занимать до 100 миллисекунд. В среднем установка всей палитры (16 цветов) занимала примерно 300 миллисекунд, что и давало в итоге 3–4 FPS.

Но мне хотелось чтобы цвет менялся плавно. Я заметил что при таком подходе сами правила не меняются — jQuery просто находит нужные DOM-элементы и навешивает на них стили через javascript-функции. Получаются практически inline–стили вроде <span style="background-color: rgb(10, 10, 10); color: rbg(192, 192, 192)">. Но зачем нам проходить по всем элементам, если можно менять сами правила CSS? Я смутно помнил что такая возможность в JavaScript есть, и решил попробовать. Так как книги Флэнагана под рукой не оказалось, я задал вопрос на StackOverflow.

В результате оказалось что хоть такая возможность и есть, она далека от кроссбраузерности. Ладно, подумал я, для пробы сойдёт и один Хром. Взяв код, которым со мной поделился SO-user kennebec, я форкнул проект и переписал смену цвета. В результате получилось ещё хуже чем было. Если операции на DOM занимали 300 миллисекунд, то добавление нового правила в конец таблицы стилей занимало ~1000 миллисекунд. YUI Stylesheet Utility работала примерно так же. Стало понятно, что так я ничего не добьюсь. Скорость смены цветов в 1 FPS меня не устраивала тем более.

    var newRule = function(selector, csstext) {
        var SS = document.styleSheets, S = SS[SS.length-1];
        // this example assumes at least one style or link element
        if (S.rules) {
            S.addRule(selector,csstext, S.rules.length);
        } else if(S.cssRules) {
            S.insertRule(selector+'{'+csstext+'}', S.cssRules.length);
        }
    };

    var set_color = function(name, rgb) {
        // $(".back_" + name).css('background-color', 'rgb(' + rgb.join(',') + ')');
        newRule(".back_" + name, 'background-color: rgb(' + rgb.join(',') + ')');
        // $(".fore_" + name).css('color', 'rgb(' + rgb.join(',') + ')');
        newRule(".fore_" + name, 'color: rgb(' + rgb.join(',') + ')');
    };

В том же топике на SO один из участников предложил переписать графическую часть на canvas или SVG. Хотя с canvas я не работал (если не считать рисования debug-линий в HabraWars), идея показалась мне интересной. Зарывшись в руководство, я сделал консоль на canvas. Особенность canvas в том что изображение должно перерисовываться заново либо поверх старого. Я пошёл лёгким путём: перерисовывал всю консоль каждый шаг, помня о том что в Habrawars canvas действовали довольно быстро. В итоге получилось какое–то среднее решение — быстрее чем добавление правила, но медленее чем операции с DOM. Перерисовка canvas целиком занимала ~700 миллисекунд, что в итоге давало менее 2 FPS. Думаю, если оптимизировать этот подход (отрисовывать только те «знакоместа», цвет которых действительно поменялся), удастся добиться той же скорости что в DOM.

Кстати, изначально я считал что вывод текста на canvas — дело нехитрое, но оказалось что эта возможность поддерживается даже не всеми браузерами. Для «отстающих» есть решение, выводящее символы как формы, но его производительность оставляет желать лучшего. Ради эксперимента я выводил консоль в canvas не посимвольно, а построчно (естественно, строка выводится одним цветом). Это оказалось значительно быстрее — перерисовка занимала примерно 90 миллисекунд. Чуть позже я попробую склеивать символы одинакового цвета, чтобы отрисовывать их вместе, возможно это ускорит процесс.

Итог: неутешительный. Оперативно перерисовывать консоль пока не получается. Хотя пользователи StackOverflow с хорошими компьютерами пишут что скорость отрисовки DOM-методами вполне приемлима, я хочу всё таки довести до ума canvas–подход. Заодно изучу API и может быть узнаю какие нибудь «фишки». Чтобы редактором схем можно было нормально пользоваться, хорошо бы перерисовывать не одну, а сразу 3-4 таких консоли. Для этого скорость отрисовки одной хорошо бы свести хотя бы к 50 мс. В любом случае, проект в нынешнем состоянии отправляется на Github и получает имя Cinnabar (Киноварь). В планах: ускоренный рендеринг консоли и парсер RTF для лёгкого добавления скриншотов — первый пришлось отрисовывать практически вручную🙂

5 комментариев
  1. qwe permalink

    попробовал сделать с помощью массивов (в хэше по цветовым схемам) span’ов с именением стилей — получилось 12fps в хроме, 15 в опере.

    • Интересно🙂 А изменение DOM с помощью jQuery? Здесь же интересна относительная производительность разных методов, конкретные цифры будут зависеть от производительности компьютера. У меня на работе он не очень быстрый.

      • qwe permalink

        node.style.color = foreColor;
        node.style.background = backColor;

        комп у меня тоже не особо и тоже на работе, можете сами мой способ попробовать, сколько выдаст?
        http://dpaste.com/167282/ — скрипт
        от верстки нужны pre[@id=»q»] и div[@id=»test-res»]

        • qwe permalink

          поправочка на 27й строке…
          var s = byGroups(group.name);
          круглые скобки надобно заменить на квадратные, т.к. byGroups массив, а не функция. соотв., работало оно из-за изменения символов.
          получается (у меня) 10-11fps в фоксе, 14 в опере

        • Жесть. 3 FPS, 322 мс в среднем. И это при перерисовке консоли. Это быстрее чем DOM method🙂 Сейчас буду вникать в код )

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: