Типографские эффекты в элементе canvas

HTML5 Rocks

Предыстория

Я впервые столкнулся с элементом <canvas> в 2006 г., когда вышел браузер Firefox версии 2.0. Статья под названием Ajaxian, в которой описывалась матрица преобразования, подтолкнула меня к написанию первого веб-приложения с использованием элемента <canvas> – Color Sphere (2007), в котором я погрузился в мир цвета и простейших графических объектов, что, в свою очередь, привело к созданию программы Sketchpad (2007–2008). Я хотел создать браузерное приложение, которое имело бы большую функциональность по сравнению с Paint.

Эти эксперименты в конце концов привели к созданию начальной версии Mugtug в сотрудничестве с моим давним другом Чарльзом Причардом (Charles Pritchard). В настоящее время мы разрабатываем приложение Darkroom на базе элемента <canvas> в HTML5. Darkroom – это недеструктивное приложение для обмена фотографиями, в котором сочетаются возможности пиксельных фильтров и векторной графики, а также рисования.

Введение

pastedGraphic.png

Элементы <canvas> дают программистам JavaScript богатые возможности для управления цветом, векторами и пикселами на экране, а также визуальным макетом монитора.

В этом примере мы имеем дело со сферой применения <canvas>, которая пока не привлекала особого внимания, – созданием текстовых эффектов. Элемент <canvas> позволяет формировать большое количество разнообразных текстовых эффектов: наши демонстрационные примеры раскрывают лишь часть из доступных возможностей. Хотя в этой статье мы имеем дело с текстом, эти методы применимы к любому векторному объекту и позволяют создавать превосходные визуальные эффекты в играх и других приложениях.

Эффект текстовых теней с использованием элемента <canvas>.
Текстовые эффекты, подобные CSS, для элемента <canvas>: создание шаблонов обрезки, обнаружение параметров в <canvas> и использование свойства тени.
Неоново-радужное свечение, отражение "зебра", эффекты наложения.
Текстовые эффекты Photoshop на базе элемента <canvas>: примеры использования методов globalCompositeOperation, createLinearGradient, createPattern. 
Эффекты внутренней и внешней тени в <canvas>.
Используем малоизвестную функцию: вращение по часовой и против часовой стрелки для создания инверсии падающей тени (эффект внутренней тени).
Эффект имитации пространства.
Эффектсоздания текста на элементе <canvas> путем циклического перебора цветов hsl() и с помощью функции window.requestAnimationFrame для создания иллюзии движения.

Текстовые тени в canvas

Одна из моих любимых функций в спецификации CSS3 (наряду со скруглением границ, веб-градиентами и другими) – это создание теней. Важно понимать различие между созданием теней с помощью CSS и <canvas>.

В CSS используется два метода: box-shadow для box-элементов, например div, span и т. п., и text-shadow для текстовых компонентов.

В <canvas> есть один тип тени. Он используется для всех векторных объектов: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText и т. д. Чтобы создать тень в элементе <canvas>, нужно указать четыре свойства, перечисленных ниже.

ctx.shadowColor = “red” // string
Цвет тени. Поддерживаются стандарты RGB, RGBA, HSL, HEX и некоторые другие.
ctx.shadowOffsetX = 0; // integer
Горизонтальная длина тени относительно текста.
ctx.shadowOffsetY = 0; // integer
Высота тени относительно текста.
ctx.shadowBlur = 10; // integer
Эффект размытия тени; чем больше значение, тем сильнее эффект.

Для начала посмотрим, как с помощью элемента <canvas> можно имитировать эффекты CSS. В Картинках Google по запросу css text-shadow можно найти несколько прекрасных примеров имитации: Line25 и Stereoscopic, а также Shadow 3D.

pastedGraphic_1.png

Стереоскопический объемный эффект (более подробные сведения см. в статье, посвященной анаглифу) является прекрасным примером простого кода. С помощью приведенной ниже строки CSS можно создать иллюзию глубины при просмотре в красных или синих 3D-очках (наподобие тех, что выдаются в кинотеатрах).

text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;

При преобразовании этой строки с использованием элемента <canvas> нужно обратить внимание на два момента.

1. Эффект размытия тени (третий параметр) отсутствует, поэтому его не нужно применять, так как функция fillText выдаст тот же результат:

var text = “Hello world!”
ctx.fillStyle = “#000”
ctx.fillText(text, -7, 0);
ctx.fillStyle = “red”
ctx.fillText(text, 0, 0);
ctx.fillStyle = “cyan”
ctx.fillText(text, 7, 0);

2. Единица измерения EM не поддерживается элементом <canvas>, поэтому соответствующие значения нужно преобразовать в пикселы. Коэффициент преобразования для PT, PC, EM, EX, PX и т. д. можно узнать, создав элемент с теми же свойствами шрифтов в DOM и задав диапазон в формате измерения. Например, чтобы получить результаты преобразования из EM в пикселы, мы измерим элемент DOM высотой 1em: общая высота offsetHeight будет равна количеству пикселов в единице EM.

var font = “20px sans-serif”
var d = document.createElement(”span”);
d.style.cssText = “font: “ + font + “ height: 1em; display: block”
// the value to multiply PX’s by to convert to EM’s
var EM2PX = 1 / d.offsetHeight;

Как избежать появления нескольких альфа-каналов?

В более сложных примерах, например при добавлении эффекта неонового свечения в Line25, необходимо использовать параметр shadowBlur для правильной имитации эффекта. Так как эффект неона основан на множественной тени, мы остановимся на этом вопросе. В элементе <canvas> у каждого векторного объекта может быть только одна тень. Таким образом, чтобы создать множественную тень, нужно нарисовать несколько экземпляров текста друг над другом. Это приводит к умножению альфа-каналов и в итоге к появлению зубчатых краев.

pastedGraphic_2.png

Я пробовал использовать ctx.fillStyle = “rgba(0,0,0,0)” или “transparent”, чтобы скрыть текст во время отображения тени, однако это не дало никакого результата, поскольку тень является результатом умножения альфа-каналов fillStyle и не может быть менее прозрачной, чем заложено в этой функции.

К счастью, решение есть: можно применить смещение тени, отделив ее от текста (чтобы они не накладывались), и тем самым скрыть текст на краю экрана.

var text = “Hello world!”
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = “top”
ctx.shadowColor = “#000”
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);

Обрезка текстовых блоков

Чтобы сделать пример более понятным, запретим начальный вызов функции fillText (при этом сохранив рисование тени). Для этого нужно добавить путь обрезки. Чтобы создать путь обрезки вокруг текста, нужно знать высоту текста (величину в em – единице, исторически равной высоте буквы M на печатном прессе) и его ширину. Ширину можно узнать с помощью параметра ctx.measureText().width, однако аналогичного значения ctx.measureText().height для высоты не существует.

К счастью, с помощью CSS-приемов (см. другие способы исправления недоработок в более ранних версиях объекта <canvas> с помощью параметров CSS в статье Типографские метрики), можно найти высоту текста, измерив параметр offsetHeight тега <span> с такими же свойствами шрифта.

var d = document.createElement(”span”);
d.font = “20px arial”
d.textContent = “Hello world!”
var emHeight = d.offsetHeight;

Таким образом, можно создать прямоугольник в качестве пути обрезки, включив в него тень и удалив фигуру-заполнитель.

ctx.rect(0, 0, width, emHeight);
ctx.clip();

Сведем эти приемы воедино, немного оптимизируем алгоритм и получим приведенный ниже пример. Если тень используется без размытия, с помощью функции fillText можно достичь того же эффекта, не задавая шаблон обрезки.

var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
    var shadow = shadows[n];
    var totalWidth = width + shadow.blur * 2;
    ctx.save();
    ctx.beginPath();
    ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
    ctx.clip();
    if (shadow.blur) { // just run shadow (clip text)
        ctx.shadowColor = shadow.color;
        ctx.shadowOffsetX = shadow.x + totalWidth;
        ctx.shadowOffsetY = shadow.y;
        ctx.shadowBlur = shadow.blur;
        ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
    } else { // just run pseudo-shadow
        ctx.fillStyle = shadow.color;
        ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
    }
    ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
    ctx.fillStyle = style.color;
    ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
    

Чтобы не вводить все эти команды для работы с элементом <canvas> вручную, я добавил в демонстрационный код простой синтаксический анализатор для текстовых теней. Он принимает команды CSS и выдает команды для работы с <canvas>. Теперь элементам <canvas> можно применить целый ряд стилей. Аналогичные эффекты тени можно использовать с любыми векторными объектами: от WebFonts до сложных фигур, импортированных из SVG-файлов, генерируемых векторных фигур и т. п.

Просмотрите в действии эффекты тени на базе элемента <canvas>.

pastedGraphic_3.png

Эффект задержки (не связанный с пикселизацией)

К написанию этого раздела меня подтолкнул демонстрационный пример Stereoscopic. Насколько сложно создать объемный эффект с помощью элемента <canvas> и двух снимков, сделанных под разными углами? Думаю, не очень. В приведенном ниже ядре красный канал первого снимка (data) совмещается с синим каналом второго снимка (data2).

data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;

Зайдите на страницу примера Stereoscopic, чтобы узнать, как создавать изображения для показа в стереоочках (синий/красный). Теперь достаточно прикрепить два устройства iPhone на голову, включить на них одновременно запись видео, и можно создавать трехмерные фильмы с помощью HTML5. Хотите попробовать?

pastedGraphic_5.png
Стереоочки

Эффекты неоновой радуги и отражения "зебра" – наложение нескольких эффектов

Наложить несколько эффектов с помощью элемента <canvas> несложно, но для этого потребуются базовые знания globalCompositeOperation (GCO). Если сравнить эти операции с GIMP (или Photoshop), то в <canvas> есть 12 операций GCO. Затемнение и осветление можно считать режимами смешивания слоев, а остальные 10 операций применяются к слоям подобно альфа-маске (новый слой удаляет пикселы предыдущего). Функция globalCompositeOperation соединяет вместе несколько слоев (в нашем случае – строк программного кода), комбинируя их особым образом.

pastedGraphic_6.png

В таблицеglobalCompositeOperation показаны режимы работы GCO. Здесь используется основная часть спектра и несколько уровней прозрачности альфа-канала, что позволяет продемонстрировать результат в деталях. Текстовые описания можно найти в справочном руководстве Mozilla по globalCompositeOperation. О том, как работает эта операция, можно узнать в статье Портера Даффа (Porter Duff) Комбинирование цифровых изображений.

Больше всего мне нравится использовать режим осветления: globalCompositeOperation=”lighter”. Эта функция смешивает наложенные пикселы подобно смешению компонентов света: сочетание красного, зеленого и синего цветов при максимальной интенсивности дает белый. Это чрезвычайно интересная функция, особенно при низких значениях globalAlpha в элементе <canvas>: это улучшает управление и позволяет сгладить границы. Функция осветления широко используется. Один из моих любимых примеров – инструмент для создания фона рабочего стола на HTML5, который можно найти на сайте http://weavesilk.com/. Режим осветления также используется в одной из моих демонстрационных программ, Breathing Galaxies: текстуры из этих двух примеров позволяют оценить возможности этого режима.

Внимание! Необходимо учитывать, что не все режимы GCO поддерживаются разными браузерами. Только шесть из них работают везде (Chrome, Safari, Firefox и Opera): source-over, source-atop, destination-over, destination-out, lighter и xor. Надеюсь, эта проблема будет решена в будущих версиях. Дополнительную информацию можно найти на странице, посвященной использованию функции globalCompositeOperation в разных браузерах.

Эффект дрожания неоново-радужного контура

В следующем примере мы попробуем создать эффект неоново-радужного свечения с мерцающим контуром, как в Photoshop, наложив несколько эффектов с помощью режимов globalCompositeOperation (source-in, lighter и darker). Это демонстрационный пример является дальнейшим развитием приложения, использующего эффекты тени на элементе <canvas> с помощью уже известного нам метода отделения тени от текста (см. предыдущий раздел).

pastedGraphic_7.png

Просмотрите в действии эффект дрожания неоново-радужного контура.

function neonLightEffect() {
  var text = "alert('"+String.fromCharCode(0x2665)+"')";
  var font = "120px Futura, Helvetica, sans-serif";
  var jitter = 25; // the distance of the maximum jitter
  var offsetX = 30;
  var offsetY = 70;
  var blur = getBlurValue(100);
  // save state
  ctx.save();
  ctx.font = font;
  // calculate width + height of text-block
  var metrics = getMetrics(text, font);
  // create clipping mask around text-effect
  ctx.rect(offsetX - blur/2, offsetY - blur/2,
           offsetX + metrics.width + blur, metrics.height + blur);
  ctx.clip();
  // create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
  ctx.save();
  ctx.fillStyle = "#fff";
  ctx.shadowColor = "rgba(0,0,0,1)";
  ctx.shadowOffsetX = metrics.width + blur;
  ctx.shadowOffsetY = 0;
  ctx.shadowBlur = blur;
  ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
  ctx.restore();
  // create the rainbow linear-gradient
  var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
  gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
  gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
  gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
  gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
  gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
  gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
  gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
  // change composite so source is applied within the shadow-blur
  ctx.globalCompositeOperation = "source-atop";
  // apply gradient to shadow-blur
  ctx.fillStyle = gradient;
  ctx.fillRect(offsetX - jitter/2, offsetY,
               metrics.width + offsetX, metrics.height + offsetY);
  // change composite to mix as light
  ctx.globalCompositeOperation = "lighter";
  // multiply the layer
  ctx.globalAlpha = 0.7
  ctx.drawImage(ctx.canvas, 0, 0);
  ctx.drawImage(ctx.canvas, 0, 0);
  ctx.globalAlpha = 1
  // draw white-text ontop of glow
  ctx.fillStyle = "rgba(255,255,255,0.95)";
  ctx.fillText(text, offsetX, offsetY + metrics.top);
  // created jittered stroke
  ctx.lineWidth = 0.80;
  ctx.strokeStyle = "rgba(255,255,255,0.25)";
  var i = 10; while(i--) { 
      var left = jitter / 2 - Math.random() * jitter;
      var top = jitter / 2 - Math.random() * jitter;
      ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
  }    
  ctx.strokeStyle = "rgba(0,0,0,0.20)";
  ctx.strokeText(text, offsetX, offsetY + metrics.top);
  ctx.restore();
};

Эффект отражения "зебра"

Этот эффект был разработан под впечатлением от статьи WebDesignerWall – прекрасного ресурса, посвященного использованию на веб-странице различных эффектов CSS. Это дальнейшее развитие идеи создания отражения для текста (как, например, в iTunes). Этот эффект сочетает в себе функции fillColor (белый цвет), createPattern (zebra.png) и linearGradient (блеск) и демонстрирует возможность применения нескольких видов заливки к одному векторному объекту.

pastedGraphic_8.png

Просмотрите в действии отражение "зебра".

function sleekZebraEffect() {
  // inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
  var text = "Sleek Zebra...";
  var font = "100px Futura, Helvetica, sans-serif";

  // save state
  ctx.save();
  ctx.font = font;

  // getMetrics calculates:
  // width + height of text-block
  // top + middle + bottom baseline
  var metrics = getMetrics(text, font);
  var offsetRefectionY = -20;
  var offsetY = 70;
  var offsetX = 60;

  // throwing a linear-gradient in to shine up the text
  var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
  gradient.addColorStop(0.1, '#000');
  gradient.addColorStop(0.35, '#fff');
  gradient.addColorStop(0.65, '#fff');
  gradient.addColorStop(1.0, '#000');
  ctx.fillStyle = gradient
  ctx.fillText(text, offsetX, offsetY + metrics.top);

  // draw reflected text
  ctx.save();
  ctx.globalCompositeOperation = "source-over";
  ctx.translate(0, metrics.height + offsetRefectionY)
  ctx.scale(1, -1);
  ctx.font = font;
  ctx.fillStyle = "#fff";
  ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
  ctx.scale(1, -1);

  // cut the gradient out of the reflected text 
  ctx.globalCompositeOperation = "destination-out";
  var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
  gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
  gradient.addColorStop(1.0, '#000');
  ctx.fillStyle = gradient;
  ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);

  // restore back to original transform state
  ctx.restore();

  // using source-atop to allow the transparent .png to show through to the gradient
  ctx.globalCompositeOperation = "source-atop";

  // creating pattern from <image> sourced.
  ctx.fillStyle = ctx.createPattern(image, 'repeat');

  // fill the height of two em-boxes, to encompass both normal and reflected state
  ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
  ctx.restore();
};

Эффект внутренней и внешней тени на элементе canvas

В спецификации элемента <canvas> не рассматривается вопрос внутренней и внешней теней. На первый взгляд может показаться, что внутренняя тень не поддерживается. На самом деле это не так: просто ее чуть сложнее реализовать. Как было предложено в одном из недавних сообщений F1LT3R, эффект внутренней тени можно создать с помощью уникальных свойств правил вращения по часовой и против часовой стрелки. Чтобы добавить внутреннюю тень, нужно нарисовать прямоугольный контейнер, а затем, вращая его в обратную сторону, создать профиль для обрезки, инвертировав фигуру.

В приведенном ниже примере можно менять параметры цвета, градиентности и текстуры одновременно для внутренней тени и функции fillStyle. Параметры вращения текстуры можно задавать по отдельности: обратите внимание на полосы зебры, расположенные перпендикулярно. Шаблон обрезки по размеру ограничивающей рамки избавляет от потребности в сверхбольших контейнерах для профиля обрезки: обрабатывается только нужная часть тени, что экономит время.

pastedGraphic_9.png

Просмотрите в действии эффект внутренней тени.

function innerShadow() {

  function drawShape() { // draw anti-clockwise
    ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
    ctx.moveTo(70, 0);
    ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
    ctx.moveTo(-20, -20);
    ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
    ctx.moveTo(140, 70);
    ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
  };

  var width = 200;
  var offset = width + 50;
  var innerColor = "rgba(0,0,0,1)";
  var outerColor = "rgba(0,0,0,1)";

  ctx.translate(150, 170);

  // apply inner-shadow
  ctx.save();
  ctx.fillStyle = "#000";
  ctx.shadowColor = innerColor;
  ctx.shadowBlur = getBlurValue(120);
  ctx.shadowOffsetX = -15;
  ctx.shadowOffsetY = 15;

  // create clipping path (around blur + shape, preventing outer-rect blurring)
  ctx.beginPath();
  ctx.rect(-offset/2, -offset/2, offset, offset);
  ctx.clip();

  // apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
  ctx.beginPath();
  ctx.rect(-offset/2, -offset/2, offset, offset);
  drawShape();
  ctx.fill();
  ctx.restore();

  // cutout temporary rectangle used to create inner-shadow
  ctx.globalCompositeOperation = "destination-out";
  ctx.fill();

  // prepare vector paths
  ctx.beginPath();
  drawShape();

  // apply fill-gradient to inner-shadow
  ctx.save();
  ctx.globalCompositeOperation = "source-in";
  var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
  gradient.addColorStop(0.3, '#ff0');
  gradient.addColorStop(0.7, '#f00');
  ctx.fillStyle = gradient;
  ctx.fill();

  // apply fill-pattern to inner-shadow
  ctx.globalCompositeOperation = "source-atop";
  ctx.globalAlpha = 1;
  ctx.rotate(0.9);
  ctx.fillStyle = ctx.createPattern(image, 'repeat');
  ctx.fill();
  ctx.restore();

  // apply fill-gradient
  ctx.save();
  ctx.globalCompositeOperation = "destination-over";
  var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
  gradient.addColorStop(0.1, '#f00');
  gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
  gradient.addColorStop(1.0, '#00f');
  ctx.fillStyle = gradient
  ctx.fill();

  // apply fill-pattern
  ctx.globalCompositeOperation = "source-atop";
  ctx.globalAlpha = 0.2;
  ctx.rotate(-0.4);
  ctx.fillStyle = ctx.createPattern(image, 'repeat');
  ctx.fill();
  ctx.restore();

  // apply outer-shadow (color-only without temporary layer)
  ctx.globalCompositeOperation = "destination-over";
  ctx.shadowColor = outerColor;
  ctx.shadowBlur = 40;
  ctx.shadowOffsetX = 15;
  ctx.shadowOffsetY = 10;
  ctx.fillStyle = "#fff";
  ctx.fill();
};

На этих примерах видно, что с помощью globalCompositeOperation можно накладывать несколько простых эффектов друг на друга и получать в результате более сложные, используя маскирование и смешение. Все в ваших руках!

Эффекты имитации пространства

С помощью элемента <canvas> перейти от символа Юникода 0x2708...

pastedGraphic_10.png

...до приведенного ниже примера с тенью...

pastedGraphic_11.png

... можно с помощью множественных обращений к функции ctx.strokeText() с небольшим значением lineWidth (0,25) при параллельном уменьшении горизонтального смещения и прозрачности, что создает иллюзию движения векторного элемента.

Путем синусоидального и косинусоидального смещения координат элементов и циклического перебора цветов с помощью свойства HSL можно создавать и более интересные эффекты, как в приведенном ниже примере “Биологическая опасность”.

pastedGraphic_12.png

Модель HSL: тон, насыщенность, яркость (1978)

Модель HSL – это новый формат, поддерживаемый спецификацией CSS3. В отличие от схемы HEX, разработанной для компьютеров, модель HSL предназначена для использования людьми.

Изменить цветовую палитру в модели HSL очень просто: значение тона увеличивается от 360 и отображается на цилиндрическом спектре. Параметр яркости определяет, насколько светлым или темным будет цвет: значение 0% соответствует черному пикселу, тогда как 100% – белому. Параметр насыщенности определяет, насколько ярким и живым будет цвет: серый оттенок соответствует насыщенности 0%, а яркие живые цвета – насыщенности 100%.

pastedGraphic_13.png

Так как стандарт HSL появился недавно, рекомендуется продолжать поддерживать более ранние версии браузеров (например, путем преобразования цветового пространства). В приведенном ниже коде объект HSL с параметрами { H: 360, S: 100, L: 100} преобразуется в объект RGB вида { R: 255, G: 255, B: 255 }. Эти значения можно использовать для создания собственных строк rgb и rgba. Исчерпывающую информацию можно найти в статье Википедии об HSL.

// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
  var H = o.H / 360,
      S = o.S / 100,
      L = o.L / 100,
      R, G, B, _1, _2;

  function Hue_2_RGB(v1, v2, vH) {
    if (vH < 0) vH += 1;
    if (vH > 1) vH -= 1;
    if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
    if ((2 * vH) < 1) return v2;
    if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
    return v1;
  }

  if (S == 0) { // HSL from 0 to 1
    R = L * 255;
    G = L * 255;
    B = L * 255;
  } else {
    if (L < 0.5) {
      _2 = L * (1 + S);
    } else {
      _2 = (L + S) - (S * L);
    }
    _1 = 2 * L - _2;

    R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
    G = 255 * Hue_2_RGB(_1, _2, H);
    B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
  }

  return {
    R: R,
    G: G,
    B: B
  };
};

Создание анимации с помощью метода requestAnimationFrame

Ранее для создания анимационных эффектов в JavaScript использовались методы setTimeout и setInterval.

window.requestAnimationFrame – это новый стандарт, призванный заменить оба этих способа, а также способствовать экономии электроэнергии и продлению срока службы компьютеров за счет управления анимацией в браузере в зависимости от доступных ресурсов. Вот некоторые из его возможностей.

  • Когда пользователь покидает фрейм, анимацию можно замедлить и даже полностью остановить, чтобы сэкономить ресурсы системы.
  • Скорость обновления ограничена 60 кадрами в секунду. Это обусловлено особенностями восприятия человека (для большинства людей ощущение плавного перехода возникает на скорости 30 кадров в секунду).

На момент написания этой статьи для использования метода requestAnimationFrame необходимы префиксы для конкретных браузеров. Пол Айриш (Paul Irish) предлагает свое решение для поддержки различных браузеров: интеллектуальная анимация с помощью requestAnimationFrame.

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
          window.webkitRequestAnimationFrame || 
          window.mozRequestAnimationFrame    || 
          window.oRequestAnimationFrame      || 
          window.msRequestAnimationFrame     || 
          function(/* function */ callback, /* DOMElement */ element){
            window.setTimeout(callback, 1000 / 60);
          };
})();

Более амбициозные разработчики могут пойти дальше и использовать также технологию poly-fill, пример которой приведен в скрипте requestAnimationFrame.js (некоторые функции требуют доработки) и которая будет поддерживать как предыдущие версии браузеров, так и новый стандарт.

В приведенном ниже примере показано, как создать анимацию путем многократного вызова метода strokeText с низким уровнем альфа-канала без "зависания" браузера. Элементы управления могут показаться необычными, однако результат очень интересен.

Просмотрите в действии эффект имитации пространства.

(function animate() {
  var i = 50;
  while(i--) {
      if (n > endpos) return;

      n += definition;
      ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
      if (doColorCycle) {
          hue = n + color;
          ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
      }
      var x = cos(n / cosdiv) * n * cosmult; // cosine
      var y = sin(n / sindiv) * n * sinmult; // sin
      ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
  }
  timeout = window.requestAnimationFrame(animate, 0);
})();
pastedGraphic_14.png
pastedGraphic_16.png
pastedGraphic_17.png

Исходный код

С поддержкой разработчиков браузеров технология <canvas> имеет хорошие перспективы: ее можно перенести в исполняемые файлы для устройств iPhone, Android и обычных компьютеров средствами PhoneGap или Titanium.

Исходный код приведен в файле CanvasTextEffects.zip.

Comments

0