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

Мой фон

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

Введение

Баннерная графика на холсте.

<canvas> дает Javascript-программистам полный контроль над цветами , векторами и пикселями на экранах — визуальным составом монитора.

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

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

Текстовые тени на холсте

Одно из моих любимых дополнений к спецификациям CSS3 (наряду с border-radius, веб-градиентами и другими) — возможность создавать тени. Важно понимать различия между тенями 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 = "красный" // строка
Цвет тени; Допустимы RGB, RGBA, HSL, HEX и другие входы.
ctx.shadowOffsetX = 0; // целое число
Горизонтальное расстояние тени относительно текста.
ctx.shadowOffsetY = 0; // целое число
Расстояние тени по вертикали относительно текста.
ctx.shadowBlur = 10; // целое число
Эффект размытия тени: чем больше значение, тем сильнее размытие.

Для начала давайте посмотрим, как <canvas> может эмулировать эффекты CSS. Поиск в изображениях Google по запросу «css text-shadow» привел к появлению нескольких отличных демонстраций, которые мы могли подражать; Line25 , Стереоскопический и Тень 3D .

CSS 3D-графика

Стереоскопический 3D-эффект (подробнее см. в разделе «Анаглифное изображение ») — это пример простой строки кода, которая нашла широкое применение. С помощью следующей строки CSS мы можем создать иллюзию глубины при просмотре в 3D-красно-голубых очках (таких, которые дают при просмотре 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> , поэтому их необходимо преобразовать в PX. Мы можем найти коэффициент преобразования для преобразования между PT, PC, EM, EX, PX и т. д., создав элемент с теми же свойствами шрифта в DOM и установив ширину в соответствии с измеряемым форматом; Или, например, чтобы захватить преобразование EM -> PX, мы измерим элемент DOM с «высотой: 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-высотой» — высоту буквы «М» на печатной машине) и ширину текста. Мы можем получить ширину, используя 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> имеют целый ряд стилей, к которым они могут быть привязаны. Эти же теневые эффекты можно использовать для любого векторного объекта: от веб-шрифтов до сложных фигур, импортированных из SVG, до генеративных векторных фигур и так далее!

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

Антракт (касательная при нажатии пикселя)

При написании этого раздела статьи меня заинтересовал стереоскопический пример. Насколько сложно было бы создать эффект 3D-киноэкрана, используя <canvas> и два изображения, снятые с немного разных точек зрения? Судя по всему, не слишком сложно. Следующее ядро ​​объединяет красный канал первого изображения (данные) с голубым каналом второго изображения (данные2):

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): есть 12 GCO в <canvas> darker , а более светлый можно рассматривать как режимы наложения слоев; остальные 10 операций применяются к слоям как альфа-маски (один слой удаляет пиксели другого слоя). GlobalCompositeOperation связывает «слои» (или, в нашем случае, строки кода) вместе, комбинируя их новыми и интересными способами:

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

Диаграмма globalCompositeOperation показывает рабочие режимы GCO; эта диаграмма использует большую часть цветового спектра и несколько уровней альфа-прозрачности, чтобы детально увидеть, чего ожидать. Я бы порекомендовал проверить ссылку на globalCompositeOperation Mozilla для текстовых описаний. Для дальнейшего исследования вы можете узнать, как работает эта операция, в книге Porter Duff’s Compositing Digital Images .

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

Обработка браузером globalCompositeOperation .

Неоново-радужный эффект джиттера

В следующей демонстрации мы собираемся добиться неонового радужного свечения в стиле Photoshop с дрожащим контуром, объединяя эффекты вместе с помощью globalCompositeOperation (источник, светлее и темнее). Эта демонстрация является развитием демонстрации «Text-Shadows in <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();
};

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

Эффект «Отражение зебры» был создан на основе превосходного ресурса WebDesignerWall о том, как оживить вашу страницу с помощью CSS. Это развивает идею немного дальше, создавая «отражение» текста — например, то, что вы можете увидеть в iTunes. Эффект сочетает в себе fillColor (белый), createPattern (zebra.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 , вы можете создавать внутренние тени, используя уникальные свойства правил намотки по часовой стрелке и против часовой стрелки. Для этого вы создаете «внутреннюю тень», рисуя прямоугольник контейнера, а затем, используя противоположные правила намотки, рисуете фигуру выреза, создавая обратную форму.

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

Внутренние/внешние тени
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, мы можем объединять эффекты в цепочку, создавая более сложные эффекты (используя маскирование и смешивание). Экран - твоя устрица ;)

Spaceage — генеративные эффекты

В <canvas> начиная с символа Юникода 0x2708:

Графический формат Юникод

… к этому затененному примеру:

Заштрихованный пример

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

Сопоставляя положение элементов XY с синусоидальной/косинусоидальной волной и циклически меняя цвета с помощью свойства 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 — новый стандарт, заменяющий оба; экономия мирового электричества (и вашего компьютера на несколько ударов сердца), позволяя браузеру регулировать анимацию на основе доступных ресурсов. Некоторые важные функции включают в себя:

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

На момент написания для использования requestAnimationFrame требовались префиксы, специфичные для поставщика. Пол Айриш создал в 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);
})();
Примечания Размытие графики
Анимационная графика
Матричная графика

Исходный код

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

Титан .