Типографические эффекты на холсте

Мой фон

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

Введение

Графический баннер Canvas.

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

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

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

Тени текстов в Canvas

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

В CSS есть два метода: box-shadow для блочных элементов, таких как 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.

3D-графика CSS

Стереоскопический эффект 3D (подробнее об анаглифических изображениях) – пример того, как можно использовать простую строку кода. С помощью следующей строки 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);</pre>
  1. Единицы EM не поддерживаются в <canvas>, поэтому их нужно преобразовать в пиксели. Мы можем найти коэффициент конверсии для преобразования между PT, PC, EM, EX, PX и так далее, создав элемент с теми же свойствами шрифта в DOM и установив ширину для измеряемого формата; например, чтобы зафиксировать преобразование EM -> PX, мы измерим элемент DOM с помощью "height: 1em", результирующий offsetHeight будет равен количеству PX в каждом 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;</pre>

Как предотвратить умножение альфа-канала

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

Неоновая графика

Я пытался использовать ctx.fillStyle = "rgba(0,0,0,0)" или "transparent", чтобы скрыть текст, но оставить тень. Однако это не помогло, поскольку тень является умножением альфа-канала fillStyle и не может быть более непрозрачной, чем 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> вручную, я включил в исходный код демо-версии простой синтаксический анализатор text-shadow. Таким образом, вы можете передавать ему команды CSS и генерировать команды <canvas>. Теперь у наших элементов <canvas> есть целый ряд стилей, с которыми они могут быть связаны. Эти же эффекты тени можно использовать для любого векторного объекта, от веб-шрифтов до сложных фигур, импортированных из SVG, сгенерированных векторных фигур и т. д.

Тень текста в эффектах Canvas

Перерыв (отступление о пикселях)

При написании этого раздела статьи меня заинтересовал пример со стереоскопическим изображением. Насколько сложно создать эффект 3D-экрана, используя <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;

Теперь кому-то нужно просто примотать скотчем два iPhone ко лбу, одновременно нажать "Записать видео", и мы сможем снимать собственные 3D-фильмы в HTML5. Есть добровольцы?

3D-очки

Неоновая радуга, зебра, отражение – эффекты цепочки

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

Графика эффектов цепочки

На диаграмме globalCompositeOperation показаны режимы GCO в действии. В ней используется большая часть цветового спектра и несколько уровней альфа-прозрачности, чтобы можно было подробно увидеть, чего ожидать. Рекомендую ознакомиться с справочными материалами Mozilla по globalCompositeOperation. Чтобы узнать больше о том, как работает эта операция, прочитайте статью Портера и Даффа Compositing Digital Images.

Мой любимый режим – globalCompositeOperation="lighter". Режим Lighter смешивает добавленные пиксели так же, как смешивается свет. Когда красный, зеленый и белый свет имеют полную интенсивность, мы видим белый свет. Это очень интересная функция, особенно если для <canvas> задано низкое значение globalAlpha. Это позволяет точнее управлять процессом и получать более плавные края. Lighter используется во многих проектах. Мой любимый – это генератор фоновых изображений для рабочего стола на HTML5, который можно найти на сайте http://weavesilk.com/. В одной из моих демо-версий, Breathing Galaxies (JS1k), также используется режим lighter. На основе этих двух примеров вы можете увидеть, какой эффект дает этот режим.

.

globalCompositeOperation browser handling.

Эффект "Неоновая радуга"

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

Радужное дрожание
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();
};

Эффект "Зебра"

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

Эффект &quot;Зебра&quot;
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> не рассматривается разница между внутренними и внешними тенями. На самом деле, на первый взгляд, вы можете ожидать, что "внутренняя" тень не поддерживается. Это не так. Просто ее немного сложнее включить. Как предлагается в недавней публикации от F1LT3R, вы можете создавать внутренние тени, используя уникальные свойства правил намотки по часовой стрелке и против часовой стрелки. Для этого создайте внутреннюю тень, нарисовав прямоугольник контейнера, а затем, используя противоположные правила намотки, нарисуйте вырезанную фигуру, создав обратную форму.

В следующем примере показано, как одновременно стилизовать внутреннюю тень и fillStyle с помощью цвета, градиента и узора. Вы можете задать поворот узора отдельно. Обратите внимание, что полосы зебры теперь перпендикулярны друг другу. Обтравочная маска размером с граничную рамку позволяет не использовать контейнер большого размера для вырезанного объекта. Это повышает скорость обработки, так как ненужные части тени не обрабатываются.

Внутренние и внешние тени
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:

Графика Unicode

…до этого затененного примера:

Пример затененного изображения

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

Сопоставив положение элементов по осям X и Y с синусоидой или косинусоидой и циклически меняя цвета с помощью свойства HSL, можно создавать более интересные эффекты, например знак биологической опасности:

Эффект циклического изменения HSL

HSL: оттенок, насыщенность, светлота (1978)

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

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

Графика HSL

Поскольку 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);
        };
})();

Если пойти дальше, то можно связать этот метод с полифилом, например requestAnimationFrame.js (есть несколько функций, которые нужно доработать), который будет поддерживать старые браузеры в большей степени, при этом переходя на этот новый стандарт.

(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);
})();
Графика размытия заметок
Значок анимации
Графика Matrix

Исходный код

Благодаря поддержке со стороны поставщиков браузеров, будущее <canvas> не вызывает сомнений. Его можно перенести в исполняемые файлы iPhone/Android/Desktop с помощью PhoneGap или

Titanium.