HTML5 INSIGHT


First letter

Продолжаем погружаться в искусство владения тенями в новых модулях CSS3. Прошлый раз мы рассматривали работу с box-shadow, сегодня мы перейдем к text-shadow.

Напомню, что оба правила, хотя и описаны в разных модулях, работают практически идентичным образом, поэтому, если вы уже знакомы с box-shadow по первой части, в этой, второй, части вы увидите много знакомого. И, прежде, чем переходить непосредственно к примерам, давайте сразу разберемся, какие есть различия, чтобы не останавливаться на них далее.

text-shadow vs box-shadow

Если вы вернетесь к разделу про синтаксис box-shadow в первой части, вы найдете такое описание:

box-shadow: <shadow> [ , <shadow> ]*;
<shadow> = inset? && [ <length>{2,4} && <color>? ] 

Где последнее разворачивается в полном виде в такую конструкцию:

box-shadow: inset? h-offset v-offset blur-radius spread-distance color;

Спецификация CSS3 Text, описывая text-shadow, говорит буквально следующее:

<shadow> is the same as defined for the ‘box-shadow’ property except that the ‘inset’ keyword is not allowed

В переводе на русский это означает, что для текста невозможны внутренние тени (inset) и синтаксис для text-shadow выглядит следующим образом:

text-shadow: <shadow> [ , <shadow> ]*;
<shadow> = [ <length>{2,4} && <color>? ] 

Аналогично box-shadow возможны множественные тени накладываемые поверх друг друга так, что первая тень оказывается наверху. Теперь давайте смотреть, как это все работает.

text-shadow

Сдвиги и цвет

Начнем со сдвигов и работы с цветом. За сдвиги тени отвечают первые два линейных параметра, указывающих длинну (1.1–1.4):

Simple Shadow

Если вы указываете положительные значения, тень сдвигается влево и вниз (1.1):

text-shadow:10px 10px; width:300px;

Отрицательные значения сдвигают вправо и вверх (1.2):

text-shadow:-5px -5px; width:300px; color:blue;

Аналогично box-shadow, все браузеры, кроме основанных на webkit, если цвет тени явно не задан, берут его из цвета текста (1.1–1.2). Это может быть полезным, например, если вы хотите автоматически делать тень цвета текста, но, например, размытой (см. примеры 2.3 и 2.4). Для явного задания цвета тени достаточно прописать нужное значение в правиле:

text-shadow:-1px -1px white; color:blue; background:#333; /* 1.3 */
text-shadow:1px 1px rgba(255,255,255, 0.5); color:blue; background:#eee; /* 1.4 */

Обратите внимание, что при указании цвета полностью применимы возможности CSS3 Color, включая указание уровня прозрачности цвета (alpha) через rgba или hsla.

Размытие

Третий линейный параметр описывает радиус размытия тени (2.1–2.4):

Blured Shadow

В полном согласии с определением box-shadow, размытие задается некоторым положительным числом — радиусом размытия. Само размытие может осуществляться UA по любому алгоритму с большой точностью аналогичному размытию по Гауссу с половинным радиусом относительно границы тени.

Text Shadow Blur Radius

В первых двух примерах (2.1 и 2.2) задан разный радиус размытия:

text-shadow:5px 5px 3px darkred; color:red; /* 2.1 */
text-shadow:4px -4px 10px red; color:azure;  background:#333; /* 2.2 */

Во второй паре примеров (2.3 и 2.4) различается только цвет текста и фона, а сами правила задания тени описаны через применяемый к блокам класс blured-shadow:

.blured-shadow {
    text-shadow:0px 0px 4px ; /* не зависит от цвета текста */
}
color:red; /* 2.3 */
color:lightgray; background:#333; /* 2.4 */

(Этот пример некорректно работает в Chrome из-за описанных выше нюансов, впрочем спецификация говорит, что пропущенный цвет остается на усмотрение UA.)

Растяжение и сжатие

Четвертый линейный параметр, если он присутствует, отвечает за растяжение или сжатие тени.

Sprayed Shadows

Для увеличения тени spread-distance должен быть положительным (3.1):

text-shadow:5px 5px 0px 3px lightgreen; color:green;

Для уменьшения — отрицательным (3.2):

text-shadow:8px 8px 2px -3px darkgreen; color:green; font-weight:900;

Если отступ тени нулевой, ее можно использовать для обводки текста (3.3):

text-shadow:0 0 0 3px rgba(128, 255, 0, 0.75); color:green;  background:#333;

Важная деталь. Насколько мне известно, в настоящий момент (сюрприз!) параметр spread-distance для text-shadow поддерживается только в Internet Explorer 10, а остальные браузеры его не поддерживают (см. например, bug 655590 “[css3-text] Support the spread radius in text-shadow” в Mozilla Bug tracker). Это же верно и в отношении большинства учебников и статей в интернете, которые своевременно не были обновлены вслед за изменениями в спецификации. Поэтому в большинстве примеров, которые вы найдете в сети, вы даже не увидите упоминания возможности растяжения или сжатия тени текста ;)

И еще одна важная деталь. Наличие четвертого параметра сегодня трактуется неподдерживающими его браузерами как неправильное задание тени — в результате эти правила просто игнорируются. Поэтому для обеспечения хоть какого-то уровня совместимости, если вы используете spread-distance, необходимо дублировать правила, например, так:

Sprayed Shadows

text-shadow: 0px 0px 10px lightgreen; /* 3.4 */
text-shadow: 0px 0px 10px 10px lightgreen; /* 3.5 */

Если вы все же хотите смоделировать увеличение тени, в определенных пределах это можно сделать через множественные тени, рассматриваемые в следующем разделе (см. примеры 4.6 и 4.7).

Множественные тени

Наконец, аналогично теням для блоков, к тексту также можно применять несколько теней одновременно, добиваясь при этом различных эффектов (4.1–4.5):

Multiple Shadows

Начиная с простейшей дублированной обводки (4.1):

text-shadow: 0 0 0 3px white, 0 0 0 4px gray; color:magenta;

И возможности смещения теней в разные стороны (4.2):

text-shadow: 3px 3px 4px 2px rgba(255,255,255,0.35), 
             6px -6px 4px 2px rgba(255,255,255,0.25), 
             -3px -3px 4px 6px rgba(255,0,255,0.15); 

Продолжая эффектами типа неона (4.3):

text-shadow: 0 0 0 3px white, 
             0 0 2px 6px magenta,
             0 0 1px 9px white,
             0 0 6px 12px magenta;

И немного более изощренным вариантом (4.4)

text-shadow: 0 0 2px #fff,
             0 0 4px 2px rgba(255,255,255,0.5),
             0 0 6px 6px #f0f,
             0 0 4px 7px #fff,
             0 0 3px 15px #222,
             -4px 0 2px 9px #f0f,
             4px 0 2px 9px #f0f,
             0 -4px 2px 9px #f0f,
             0 4px 2px 9px #f0f;

Или же с ограниченным использованием предыдущего приема перекрытия смещенных теней — небольшое подчеркивание (4.5):

text-shadow: 0 -3px 3px 15px white, 0 1px 2px 9px;
color:magenta;

Эмуляция растяжения

Emulating Sprayed Shadows

Как было сказано в предыдущем разделе, технически множественные тени можно применять для эмулирования увеличения тени. Например, чтобы сделать что-то похожее на (4.6):

text-shadow: 0px 0px 0px 4px magenta;

Можно было бы применить одновременно несколько теней, сдвинутых в разных направлениях (4.7):

text-shadow: magenta 0px 2px, 
             magenta 2px 0px, 
             magenta -2px 0px, 
             magenta 0px-2px, 
             magenta -1.4px -1.4px, 
             magenta 1.4px 1.4px, 
             magenta 1.4px -1.4px, 
             magenta -1.4px 1.4px;

Однако, если приглядеться, между ними заметна разница. Также важно понимать, что такой прием ограничен в применимости: он не только менее точен, но и отрицательно сказывается на производительности отрисовки страницы.

Интересные примеры

Теперь давайте рассмотрим еще несколько примеров использования теней для реализации различных интересных эффектов.

Начнем с классической радуги (5.1):

Rainbow shadow

text-shadow: 0 0 2px 3px yellow,
             0 0 2px 6px orange,
             0 0 2px 9px red,
             0 0 2px 12px lime,
             0 0 2px 15px blue,
             0 0 2px 18px violet;

Двойная тень для стрелки (5.2):

Double arrow

text-shadow: 0 0 2px 2px white,
             2px 0 2px 5px #222,
             3px 0 3px 6px #933,
             5px 0 2px 14px #222,
             6px 0 5px 16px #533;

Традиционная огненная тень (5.3):

Fire Shadow

text-shadow: 0 0 2px #eee,
             0 0 4px 2px #fff,
             0 -2px 4px 2px #ff3,
             2px -4px 6px 4px #fd3,
             -2px -6px 11px 6px #f80,
             2px -8px 18px 8px #f20;

Традиционный “letter-press”, — здесь также важен контраст с фоном (5.4):

Letter press

text-shadow: 0px 2px 3px #555;

Не менее традиционный 3d-text (5.5):

3d text

text-shadow: 0 0 1px #999,
             1px 1px 1px #888,
             2px 2px 1px #777,
             3px 3px 1px #666,
             4px 4px 1px #555,
             5px 5px 1px #444;

Двойная тень для винтажного эффекта (5.6)

Vintage text

text-shadow: 2px 2px #fff,
             3px 3px #666;

Проступающая надпись с прозрачным текстом и сжатой тенью, — также зависит от размера и гарнитуры шрифта (5.7)

Light text

text-shadow: 0 0 2px -3px rgba(196,255,0,0.3),
             0 0 6px -5px #9c6;
color:transparent;

Применение отдельной буквы для псевдо-класса ::first-letter (5.8)

First letter

.text {
    text-shadow:0 0 5px;
}
            
.text::first-letter {
    color:azure;
    text-shadow:0 0 5px, 0 0px 6px 3px blue, 0 -2px 6px 6px cyan, 0 -4px 9px 9px lightblue ;
}

Интерактив

Hands-on: text-shadow

Если вы хотите просто поиграться с тенями в интерактивном режиме, наши коллеги к прошедшей в сентябре конференции Build подготовили демонстрационную страницу: “Hands-on: text-shadow”.

Internet Explorer

text-shadow поддерживается в IE10+.

И повторю свой совет относительно использования фильтров: не используйте фильтры вообще, либо продумывайте верстку и стили так, чтобы для IE9+ они не применялись. Стандартные css-эффекты в IE в отличие от нестандартных старых фильтров, начиная с 9й версии, работают с использованием аппаратного ускорения, к тому же фильтры часто оказываются несочетаемыми с новыми стандартными свойствами CSS.



На HTML5 Camp в рамках открытия мы показывали несколько демок с использованием новых веб-технологий. Там были как сторонние проекты и решения вроде Disney Tron Legacy и Santa’s Media Queries, так и ряд примеров, подготовленных специально под мероприятие.

chat++

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

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

Веб-сокеты

Все началось с того, что нужно было наладить работу веб-сокетов. В нашем случае мы обошлись малой кровью, взяв в качестве основы готовый чат-клиент с html5labs.com. Обратите внимание, что там есть реализация как через нативные веб-сокеты, если они поддерживаются браузером, так и с fallback через Silverlight в противном случае.

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

Несколько важных деталей:

  • Internet Explorer. В IE веб-сокеты поддерживаются нативно, начиная с 10й версии, мы использовали IE10 PP4.
  • Firefox. В Firefox веб-сокеты доступны через вендорный префикс, поэтому вместо объекта WebSocket нужно использовать MozWebSocket.
  • Opera. В Opera веб-сокеты потребовалось включить вручную через настройки: opera:config -> websockets -> enable.

Чтобы разобраться, как все работает, давайте начнем с простого текстового чата:

<form id="myform">
    <input type="text" id="chat" placeholder="type and press enter to chat" />
</form>
<ul id="log"></ul>

На отправку формы вешаем событие, которое будет перехватывать сообщение и отправлять его через веб-сокет:

$("#myform").submit(function (event) {
    event.preventDefault();
                        
    // if we're connected
    // conn -- opened websocket connection
    if (conn.readyState === 1) {
        conn.send(JSON.stringify({
            sender:sender, // sender ID
            type:'chat', 
            chat:$('#chat').val()
        }));
                            
        log.html('<li class="you">' + $('#chat').val().replace(/[<>&]/g, function (m) { return entities[m]; }) + '</li>' + log[0].innerHTML);
        $('#chat').value = '';
    }
});

В коде выше мы проверяем, что соединение готово для использования и отправляем объект в виде json-строки, одновременно добавляя сообщение в лог чата. (Страшгая функция с регулярными выражениями просто экранирует “&”, “<” и “>”.

Теперь давайте разбираться, откуда берется соединение с веб-сокетом. Чтобы начать работать с веб-сокетами, нужно создать соответствующий объект:

 if (conn.readyState === undefined || conn.readyState > 1) {
    // ws -- WebSocket or MozWebSocket
    conn = new ws('ws://yousite.com:port/chat');
    ...
}

Далее (внутри) необходимо повесить несколько обработчиков событий от открытого сокета – как минимум на открытие, закрытие и получение сообщения (и, желательно, на возникновение ошибки):

conn.onopen = function () {
    state.toggleClass('success');
    state.text('Socket open');
};

conn.onmessage = function (event) {
    var message = JSON.parse(event.data);
                       
    if (message.type == 'chat')  {
        // filter own messages
        if (message.sender != sender) {
            log.html('<li class="them">' + message.chat.replace(/[<>&]/g, function (m) { return entities[m]; }) + '</li>' + log[0].innerHTML);
        }
    } else {
        $('#connected').text(message);
    }
};

conn.onclose = function (event) {
    state.toggleClass('fail');
    state.text('Socket closed');
};

Так как новые сообщения рассылаются всем массово, то хорошо бы отфильтровывать собственные, для этого в коде проверяется sender (случайно генерируемый id).

text chat

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

Рисование

Останавливаться на том, как рисовать поверх canvas, пожалуй, будет излишним, так как примеров в сети и так пруд пруди. Остановлюсь только на трех интересных моментах.

Во-первых, довольно неожиданно выяснилось, что Firefox все еще не поддерживает offsetX/offsetY в MouseEvent для определения координат мыши относительно блока, над которым находится курсор. См. также мою статью “T&P. Canvas и Offset”. Это не то, чтобы очень большая проблема, но код, конечно, усложняет, если необходимо сделать его полностью кроссбраузерным.

Во-вторых, тоже неожиданно, оказалось, что Chrome при отрисовке в Canvas не включает Shadow и в целом в разных браузерах используется разное сглаживание и чуть разные алгоритмы.

sun and tree
(кликабельно)

И, в-третьих, для моделирования игры в примере используется два холста, соответственно, при передаче сообщений нужно было понимать, на каком именно canvas и что именно нужно будет нарисовать.

Теперь, собственно, к передаче рисования через веб-сокеты. Если в текстовом чате отправка сообщения осуществлялась при отправке формы, то при рисовании разумной представляется отправка непосредственно в процессе отрисовки, поэтому добавляем необходимый код в событие onmousemove, повешенное на canvas:

// canvas - "canvas" object
// canvas.source - jquery object for canvas
// canvas.context - canvas.source[0].getContext("2d")
canvas.source.bind("mousemove", function(e) {
    if (canvas.isPainting) {
        var line = {x1:canvas.lastPoint.x, y1:canvas.lastPoint.y, x2: e.offsetX, y2: e.offsetY};
        drawLine(canvas.part, line);

        if (conn.readyState === 1) {
            conn.send(JSON.stringify({
                sender:sender, 
                type:"canvas", 
                part:canvas.part, 
                line:line
            }));
        }

        canvas.lastPoint = {x: e.offsetX, y: e.offsetY};
    }
});

Обратите внимание на код отправки сообщения – по структуре он ничем не отличается от кода для текста.

Осталось обновить прием сообщений:

conn.onmessage = function (event) {
    var message = JSON.parse(event.data);
                       
    if (message.type == 'chat')  {
        ...
    } else if (message.type == 'canvas') {
        if (message.sender != sender) {
            drawLine(message.part, message.line);
        }
    } else {
        ...
    }
};

Здесь тоже все идентично с той лишь разницей, что вместо добавления текста в лог, мы просто отрисовываем линию из сокета тем же механизмом, что и собственные линии (при желании вы можете поменять цвет):

// c1 and c2 - global vars
function drawLine(part, line) {
    var ctx = (part == 1) ? c1.context : c2.context;

    ctx.beginPath();
    ctx.moveTo(line.x1, line.y1);
    ctx.lineTo(line.x2, line.y2);
                        
    ctx.closePath();
    ctx.stroke();
};

Еще немного лоска добавляется через box-shadow и скрытие второй половинки при рисовании, чтобы не видеть, что рисует другой игрок:

hiding canvas

Готовый пример можно скачать c Я.Диска.



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

Думаю, любой, кто хоть как-то занимался программированием решал похожую задачу и в ней нет ничего сложного: вешаем события на нажатие, движение и отпускание кнопки мыши, добавляем флаг, рисуем или не рисуем, запоминаем последние координаты и собственно отрисовываем фрагмент траектории.

canvas.bind("mousedown", function(e) {
    isPainting = true;
    lastPoint = {x: e.offsetX, y: e.offsetY};
});

canvas.bind("mousemove", function(e) {
    if (isPainting) {
        var line = {x1:lastPoint.x, y1: lastPoint.y, x2: e.offsetX, y2: e.offsetY};
        drawLine(...);

        lastPoint = {x: e.offsetX, y: e.offsetY};
    }
});

canvas.bind("mouseup", function(e) {
    isPainting = false;
});

Запускаем. IE — ок, Chrome — ok, Opera — ok, Firefox — fail. Ууупс..

Если вы внимательно прочитали заголовок, то уже должны были догадаться, в чем проблема. Проблема в том, что Firefox не поддерживает свойства offsetX/offsetY у события MouseEvent, хотя все остальные браузеры это делают.

Казалось бы, чего тут обсуждать… но проблема имеет давние корни и интересную историю.

Надо начать с того, что offsetX/offsetY изначально появились именно в IE, причем не будучи никак стандартизованными. В Firefox (Netscape), тем временем, был реализован схожий по задачам, но другой механизм — layerX/layerY (и тоже ни разу не стандартный).

Интересно, что наиболее частое использование этих свойств как раз заключается в моделировании offsetX/offsetY. Например, в багтрекере jQuery можно найти такой пример кода:

var offX, offY;
if (!(e.offsetX || e.offsetY)) {
	offX = e.layerX - $(e.target).position().left;
	offY = e.layerY - $(e.target).position().top;
}
else {
	offX = e.offsetX;
	offY = e.offsetY;
}

Со временем многие другие браузеры начали адаптировать удобный механизм из Internet Explorer, который сегодня уже описывается в стандарте CSSOM View Module (черновик). Но, нельзя не сказать, что и layerX/layerY также до недавних пор присутствовал в Webkit (это важно для понимания последующей истории).

Ну и качество реализации на сегодняшний день — тоже отдельный вопрос. Так в IE отступ считается в соответствии со спецификацией от начала “padding edge”, а в Chrome вы сегодня еще получите добавленной величину границы.

История

Отматываем на далекий 2001 год. В строгом согласии с истиной, в багтрекере Mozilla появляется баг 69787 “DOM Spec for events is lacking offset Support (as it is/was lacking offsetParent etc)”. В комментариях поднимается вопрос о поддержке в Netscape событий из IE4+ вроде offsetTop, offsetLeft и offsetParent, но это кажется не прописано явно в спецификации DOM, поэтому в IE одна реализация, а в NS другая — layerX/layerY. Обе реализации не предусмотрены существующим на тот момент стандартом, хотя и явно востребованы.

В 2005 Gérard Talbot возвращается к обсуждению этого вопроса (а баг переименовывается в “Implement MSIE’s event.offsetX, event.offsetY as mouse coordinates inside target element”):

OTOH, offsetX and offsetY are MSIE event properties and useful properties for, say, easily and quickly determining mouse coordinates inside the target element: eg. mouse (x, y) coordinates in an <img> in a click event.
// Gérard Talbot, 2005
We still don’t provide an equivalent to offsetX/offsetY on Gecko events.
// Boris Zbarsky, 2005
Mozilla-based browsers can provide the equivalent to MSIE’s evt.offsetX/evt.offsetY as explained in bug 122665 comment 3. It is not a direct, immediate and straightforward equivalent though.
If Mozilla should provide an equivalent to offsetX/offsetY, then it should be with the same name and same functioning/implementation as in MSIE. Already Opera 7+ has bugs with that implementation.
// Gérard Talbot, 2005

На этом дискуссия замолкает, а у разработчиков отсутствие поддержки offsetX/offsetY по-прежнему “вызывает боль”, заставляя писать примерно такие куски кода:

var x=0, y=0, fatalerror=0;

if (typeof event.offsetX != 'undefined' && typeof event.offsetY != 'undefined') {	// Browser provides the co-ords for us easily (zero-indexed)
    x = event.offsetX;
    y = event.offsetY;
}
else if (event.target) {		// If we have the 'target' of the (click) event - in this case, the image
    var elem = event.target;
    do {						// Calc x and y of 'target' element (ie. the image)
        x += elem.offsetLeft;
        y += elem.offsetTop;
    } while (elem = elem.offsetParent);
    x = (window.pageXOffset + event.clientX) - x;
    y = (window.pageYOffset + event.clientY) - y;
}
else {	// Fatal error trying to determine click co-ords!
    fatalerror = 1;
}

// x and y are still zero-indexed...
if (!fatalerror) {x++; y++;}

alert('We think x:y co-ords are: ' + x + ':' + y);

Идет время. В 2008 John Resig снова поднимает вопрос о реализации offsetX/offsetY:

We should strongly consider this. Currently we’re the only browser that doesn’t support this: http://www.quirksmode.org/dom/w3c_cssom.html#elementviewm
Also, this is part of the W3C CSSOM working draft: http://www.w3.org/TR/cssom-view/
// John Resig, 2008
I just noticed that http://www.w3.org/TR/cssom-view/#the-mouseeventview-interface defines offsetX and offsetY. So, we may need to resummarize this bug again… so that we can meet the CSSOM View specification instead of MSIE’s model.
// Gérard Talbot, 2008

Дело пошло? Никак нет.

Спустя несколько месяцев, в ноябре того же 2008г., шевеление началось в стане Webkit. В багтрекере появился баг 21868 “layerX and layerY on mouse events are very probably wrong”. Simon Fraser из Apple пытается понять, как должны вести себя layerX/layerY.

Наши дни

Спустя почти три года (в июле 2011), к багу в багтрекере Webkit возвращаются:

Proposed fix: Match Firefox’s behavior.
// Julien Chaffraix, 2011
I’d prefer to just remove layerX/layerY. Can you mail mozilla folks and see if they’d agree with this?
// Simon Fraser, 2011

Мячик снова возвращается в лагерь Firefox.

Per discussion on #content, this is a request to drop support for MouseEvent’s layerX and layerY. It is currently supported only by Gecko and WebKit (context for WebKit -> https://bugs.webkit.org/show_bug.cgi?id=21868).
WebKit is considering removing those 2 properties as our code looks extremely old and we had zero bug about that.
// Julien Chaffraix, 2011
Simon, you suggested dropping layerX/Y. May I ask why? Just because they are rarely used, or do you see something badly wrong with them?
// Olli Pettay, 2011
They are not specified anywhere, the ‘layer’ name seems to be a holdover from Netscape 4 “layers”, and WebKit’s implementation was very probably wrong.
// Simon Fraser, 2011

В конечном счете, все соглашаются, что хорошо бы реализовать поддержку offsetX/offsetY в соответствии со спецификацией, хотя выкидывать layerX/layerY тоже так просто нельзя.

Впрочем, в Webkit именно так и поступили: в свежем Chrome свойства layerX/layerY уже не доступны.

Заключение

Вы наверное, уже подумали, что я тут взялся поругать разработчиков Firefox? Да! Дайте мне уже наконец Firefox c поддержкой offsetX/offsetY! Но не только…

Самое интересное в этой истории — это то, что такое, вроде бы, небольшое своейство может иметь более чем десятилетнюю историю прихода в реальную жизнь. Это действительность, в которой развиваются браузеры и веб-стандарты, применимая, в общем-то, к любому браузеру.

Наконец, интересный момент заключается в легкости, с которой иногда расстаются со старыми свойствами, хотя и нестандартными, но давно поддерживаемыми. Я надеюсь, все сайты, которые рассчитывают на layerX/layerY, правильно определяют его наличие до использования и не поломаются, когда его окончательно не станет.

Да, удивительно, но это история почти даже не про IE.



Работа с бинарными данными с использованием типизированных массивов

Source: Working with Binary Data using Typed Arrays, Luke Hoban

Вместе с HTML5 в веб-разработку приходят новые API, расширяющие UX, привнося новые мультимедийные возможности и возможности взаимодействия в реальном времени. Зачастую этот функционал завязан на использование бинарных форматов файлов вроде MP3-аудио, PNG-изображений или MP4-видео. Использование бинарных файлов крайне важно в данном контексте, так как позволяет уменьшить требования к ширине канала, добиться необходимой производительности и вместе с этим оставаться совместимым с имеющимися технологиями. Еще недавно у веб-разработчиков не было прямого доступа к содержимому этих бинарных файлов или любых других бинарных форматов файлов.

В этой статье мы рассмотрим, как веб-разработчики могут снять этот барьер, используя Typed Arrays API для JavaScript, и использование нового API в демонстрационном примере Binary File Inspector на IE Test Drive.

Типизированные массивы, доступные в IE10 Platform Preview 4, позволяют веб-приложениями работать с широким спектром бинарных файлов и напрямую работать с двоичным контентом поддерживаемых браузером файлов. Поддержка Typed Arrays была добавлена по всему IE10: в JavaScript, в XMLHttpRequest, в File API и в Stream API.

Binary File Inspector

Пример Binary File Inspector показывает некоторые из новых возможностей, работающих при сочетании этих функций. Вы можете посмотреть ID3-заголовки музыкальных файлов, понять, как выглядит сырые данные в видео-файлах, а также посмотреть файлы других форматов вроде PCX-изображений, которые могут поддерживаться браузером с помощью JavaScript и Canvas.

Binary File Inspector

На примере выше .mp4-видео рендерится с помощью <video>-элемента слева, а бинарное содержимое файла отображается справа в шестнадцатеричном (HEX) формате и в виде соответствующих ASCII-символов. В этом примере вы можете увидеть несколько характерных для MPEG-файлов элементов вроде “ftyp” для “mp4”.

Typed Arrays и ArrayBuffers

Типизированные массивы предоставляют возможность взгялнуть на сырое бинарное содержимое через то или иное типизированное представление. Например, если мы хотим смотреть на бинарный поток данных как байтовый массив, мы можем использовать Uint8Array (Uint8 описывает 8-битовое беззнаковое целое значение, обычно называемое байтом). Если мы хотим считывать сырые данные как массив чисел с плавающей точкой, мы можем использовать Float32Array (Float32 описывает 32-битное число с плавающей точкой в соответствии со стандартом IEE754). Поддерживаются следующие типы:

Тип массиваРазмер элемента и описаниеInt8Array8-bit signed integerUint8Array8-bit unsigned integerInt16Array16-bit signed integerUint16Array16-bit unsigned integerInt32Array32-bit signed integerUint32Array32-bit unsigned integerFloat32Array32-bit IEEE754 floating point numberFloat64Array64-bit IEEE754 floating point number

Каждый тип массива — это представление для ArrayBuffer. ArrayBuffer — это ссылка на поток бинарных данных, но он не представляет никакого прямого способа для взаимодействия с данными. Создание TypedArray-представления для ArrayBuffer предоставляет доступ к чтению и записи бинарного содержимого.

Пример ниже создает новый ArrayBuffer с нуля и интерпретирует его содержимое различными способами:

// Create an 8 byte buffer
var buffer = new ArrayBuffer(8);
 
// View as an array of Uint8s and put 0x05 in each byte
var uint8s = new Uint8Array(buffer);
for (var i = 0; i < 8; i++) {
    uint8s[i] = 5; // fill each byte with 0x05
}
 
// Inspect the resulting array
uint8s[0] === 5; // true - each byte has value 5
uint8s.byteLength === 8; // true - there are 8 Uint8s
 
// View the same buffer as an array of Uint32s 
var uint32s = new Uint32Array(buffer);
 
// The same raw bytes are now interpreted differently
uint32s[0] === 84215045 // true - 0x05050505 == 84215045

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

Typed Arrays для чтения бинарных файлов

Важный новый сценарий, ставший возможным благодаря типизированным массивам, это чтение и отображение содержимого бинарных файлов, неподдерживаемых напрямую браузером. Вместе с различными типизированными массивами, описанными выше, Typed Arrays также предоставляет специальный объект DataView, который можно использовать для чтения и записи содержимого ArrayBuffer в неструктурированном виде. Это хорошо подходит для чтения новых форматов файлов, которые обычно построены поверх смешанных типов данных.

Binary File Inspector использует DataView для чтения PCX-файлов и их рендеринга с использованием <canvas>-элемента. Ниже приведена упрощенная версия того, что делается в демонстрационном примере для чтения заголовка файла, включая получения информации о ширине и высоте, DPI и глубине цвета (bits-per-pixel).

var buffer = getPCXFileContents();
 var reader = new DataView(buffer);
 
// Read the header of the PCX file 
var header = {};
 
// The first section is single bytes 
header.manufacturer = reader.getUint8(0); 
header.version = reader.getUint8(1); 
header.encoding = reader.getUint8(2); 
header.bitsPerPixel = reader.getUint8(3); 
 
// The next section is Int16 values, each in little-endian 
header.xmin = reader.getInt16(4, true); 
header.ymin = reader.getInt16(6, true); 
header.xmax = reader.getInt16(8, true); 
header.ymax = reader.getInt16(10, true); 
header.hdpi = reader.getInt16(12, true); 
header.vdpi = reader.getInt16(14, true);

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

Получение двоичных данных через XHR и File API

Прежде, чем мы сможем использовать Typed Arrays API для работы с содержимым файлов, нам необходимо использовать соответствующие API браузера для получения доступа к сырым данным. Для доятупа к файлам с сервера XMLHttpRequest API был расширен с добавлением поддержки различных типов ответа (responseType). Так ответ в виде “arraybuffer” представляет содержимое ресурса, запрошенного с сервера, в виде ArrayBuffer-объекта для JavaScript. Также поддерживаются “blob”, “text” и “document” ответы.

function getServerFileToArrayBufffer(url, successCallback) {
    // Create an XHR object
    var xhr = new XMLHttpRequest();
 
    xhr.onreadystatechange = function () {
        if (xhr.readyState == xhr.DONE) {
            if (xhr.status == 200 && xhr.response) {
                // The 'response' property returns an ArrayBuffer
                successCallback(xhr.response);
            } else {
                alert("Failed to download:" + xhr.status + " " + xhr.statusText);
            }
        }
    }
    // Open the request for the provided url
    xhr.open("GET", url, true);
    
    // Set the responseType to 'arraybuffer' for ArrayBuffer response
    xhr.responseType = "arraybuffer";
 
    xhr.send();
}

Во многих случаях файлы могут выбираться пользователем, например, в виде приложения к письму в веб-клиенте для почты. File API предоставляет веб-разработчикам возможность чтения содержимого файлов, указанных через <input>-элемент, при перетаскивании (drag and drop) или из другого источника (Blob, File). Для чтения содержимого файла в ArrayBuffer используется объект FileReader и, подобно XHR-объекту, он используется асинхронно, чтобы убедиться, что чтение с диска не блокирует пользовательский интерфейс.

function readFileToArrayBuffer(file, successCallback) {
    // Create a FileReader 
    var reader = new FileReader();
 
    // Register for 'load' and 'error' events
    reader.onload = function () {
        // The 'result' property returns an ArrayBuffer for readAsArrayBuffer 
        var buffer = reader.result;
        successCallback(buffer);
    }
 
    reader.onerror = function (evt) {
        // The error code indicates the reason for failure
        if (evt.target.error.code == evt.target.error.NOT_READABLE_ERR) {
            alert("Failed to read file: " + file.name);
        }
    }
 
    // Begin a read of the file contents into an ArrayBuffer
    reader.readAsArrayBuffer(file);
}

Заключение

Бинарные данных активно используются веб-браузерами. С добавлением поддержки Typed Arrays, XHR2 и File API в IE10 веб-приложения теперь могут тоже напрямую работать с бинарными данными, манипулировать данными на по-байтовом уровне, отображать дополнительные бинырные форматы данных и извлекать дополнительные данные из существующих медиа-форматов. Попробуйте пример Binary File Inspector на IE Test Drive, и реализацию Typed Arrays в IE10.



/Пост Владимира Юнева о свежевышедшем IE10 Platform Preview 4./

Вчера была представлена новая превью-версия Internet Explorer 10, в которой разработчиками было добавлено значительное количество новых возможностей. Среди них:

  • поддержка субтитров в HTML5 Video (сразу нескольких языков);
  • добавлена поддержка CORS (cross origin resource sharing) для безопасных кроссдоменных запросов;
  • добавлена поддержка File API: Writer для работы в браузере с большими бинарными объектами (файлами, блобами);
  • добавлена поддержка JavaScript typed arrays для эффективного хранения и манипулирования типизированными данными;
  • добавлена поддержка свойств CSS user select для возможности задать на странице блоки, которые доступны для выделения пользователем;
  • улучшения в CSS3 Positioned Floats, Flexbox, Grid, поддержка Web Worker Thread Pooling, улучшенная поддержка XHR2.


Современные тенденции таковы, что платформа HTML5 становится все более функциональной, веб-приложения становятся все более богатыми и сценарии, которые требуют взаимодействие с серверной стороной для выполнения тривиальных задач становятся все более нудными. Если мы взглянем на клиентский код, то можем заметить некоторый дефицит возможностей JavaScript…



Сегодня мы попробуем разобраться в том, как работают тени в новых модулях CSS3. С практической точки зрения, мы рассмотрим два правила: box-shadow и text-shadow, определенные соответственно в модулях CSS3 Backgrounds and Borders и CSS3 Text.

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

Первая часть посвещена работе с box-shadow, во второй мы пройдемся по теням для текста.

box-shadow

Сдвиги и цвет

Simple Shadow

В самом простом варианте для задания тени достаточно указать два параметра, задающие соответственно горизонтальный и вертикальный сдвиги тени (1.1):

box-shadow: 3px 3px;

Положительные значения сдвига смещают тень вправо и вниз, отрицательные — влево и вверх.

По умолчанию, если цвет тени не задан, в большинстве браузеров (все, кроме webkit-based) он берется из цвета текста (color) в текущем контексте (1.2), впрочем, похоже, этот момент спецификацией не обговаривается:

box-shadow: 3px 3px; color:blue;

Чтобы задать цвет тени, достаточно указать его дополнительным параметром (1.3):

box-shadow: 3px 3px darkgreen;

Очевидно, цвет можно указывать любым из доступных способов: от прямого указания названия и шестнадцатеричного кода, до rgb или rgba и hsla с прозрачностью (мы рассмотрим такие примеры чуть позже).

Размытие

Blured Shadow

Третий “линейный” параметр, который можно задать при описании тени — это радиус размытия (blur), положительная величина, указывающая насколько сильно нужно размывать тень по пространству (2.1–2.3):

box-shadow:3px 3px 3px darkgrey;

По умолчанию радиус размытия равен 0 и в этом случае тень получается четкой.

В сочетании с разными сдвигами тени, можно получить разные эффекты, например, на (2.3) оба сдвига тени равны нулю, но за счет размытия тень выступает с разных сторон:

box-shadow:0 0 9px black;

Сам алгоритм размытия спецификацией не описывается, кроме указания того, что это должен быть эффект, аналогичный размытию по Гауссу (Gaussian blur) с половинным радиусом в обе стороны от границы тени (2.4):

Gaussian blur

Растяжение

Sprayed Shadow

Еще один интересный параметр — это растяжение или распространие тени (spread), позволяющее увеличить или уменьшить ее размеры (по умолчанию размеры тени соответствуют размеру исходного объекта). Надо отметить, что этот параметр не сразу появился в спецификации, поэтому во множестве примеров в интернете он просто не рассматривается.

Для увеличения тени нужно указать положительный spread-параметр (3.1, 3.2):

box-shadow:6px 6px 0px 4px darkred;

Для уменьшения — отрицательный (3.3):

box-shadow:12px 12px 8px -4px darkred;

Растяжение или сжатие тени можно расценивать как операцию масштабирования, но спецификация описывает это несколько более хитро (через аналогию с размытием и удаление прозрачных или непрозрачных пикселей), что, впрочем, не меняет сути дела:

Sprayed Shadow

На примере выше (3.4) тень смещена на 6px вниз и влево и увеличена на 8px с каждой стороны:

box-shadow:6px 6px 0 8px grey;

Если в вашем блоке используются скругленные уголки, будьте готовы к тому, что в расширенной тени радиус скругления также будет пропорционально смаштабирован (3.5):

Sprayed Shadow with border-radius

Внутренняя тень

Inner Shadow

Наконец, еще один хитрый параметр — это возможность применения тени внутри блока. Для этого используется специальное ключевое слово inset (4.1-4.4):

box-shadow:inset 4px 4px rgba(66,66,66,0.5); /* (4.1) */
box-shadow:inset 4px 4px 0 8px rgba(198,198,198,1); /* (4.2) */
box-shadow:inset -2px -2px 8px 0px black; /* (4.3) */
box-shadow:inset 0 0 4px 0px black; /* (4.4) */

Обратите внимание, что внутренняя тень отрисовывается только внутри блока, к которому применено соответствующее правило, причем применение spray-параметра для внутренней тени (4.2) в отличие от внешней приводит к уменьшению внутреннего перимерта тени.

Множественные тени

Ну и теперь еще один нюанс: на самом деле, к блокам можно применять любое количество теней одновременно, для этого достаточно их перечислить через запятую при описании box-shadow.

Rainbow Shadows

Например, чтобы получить радужную тень (5.1) достаточно последовательно указать 7 теней с увеличивающимся растяжением:

box-shadow: 0 0 2px 1px red,
            0 0 2px 2px orange,
            0 0 2px 3px yellow,
            0 0 2px 4px green,
            0 0 2px 5px lightblue,
            0 0 2px 6px blue,
            0 0 2px 7px violet;

Обратите внимание, что фактически тени выстраиваются в стек в обратном порядке и отрисовываются, начиная с последней, причем каждая из них применяется к исходному объекту так, как будто есть только она.

Colour Shadows

Так как тени независимы, вы легко можете сочетать тени, выстроенные в разных направлениях (5.2):

box-shadow: -6px -6px 8px -4px rgba(255,0,0,0.75),
            6px -6px 8px -4px rgba(0,255,0,0.75),
            6px 6px 8px -4px rgba(255,255,0,0.75),
            -6px 6px 8px -4px rgba(0,0,255,0.75);

Inner Shadows

Аналогично, можно сразу задавать внутренние и внешние тени (5.3):

box-shadow: inset 0 0 8px lightgray,
            1px 1px 3px darkgray;

Underline Shadows

Или “продвинутое подчеркивание” (5.4):

box-shadow: 0 1px red,
            0 3px 3px -2px black

Slick Shadows

Или, если проявить еще немного фантазии и дополнительных спецэффектов, сделать slick-box, описанный, например, у Matt Hamm (5.5):

.slick-box {
    position: relative;
    height: 50px;
    border: 1px solid #efefef;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.06) inset;
}

.slick-box:before, .slick-box:after {
    content: '';
    z-index: -1;
    position: absolute;
    left: 10px;
    bottom: 10px;
    width: 70%;
    max-width: 300px; /* avoid rotation causing ugly appearance at large container widths */
    height: 55%;
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
    transform: skew(-15deg) rotate(-6deg);                 
}
            
.slick-box:after {
    left: auto;
    right: 10px;
    transform: skew(15deg) rotate(6deg);                 
}

(Для упрощения, я убрал код с вендорными префиксами, но вам нужно будет добавить -ms-transform, -webkit-transform и т.д.)

Общий синтаксис

Резюмируя, синтаксис для описания теней выглядит следующим образом:

box-shadow: <shadow> [ , <shadow> ]*;
<shadow> = inset? && [ <length>{2,4} && <color>? ] 

Последнее в полном виде разворачивается в следующую схему:

box-shadow: inset? h-offset v-offset blur-radius spread-distance color;

Радиус размытия и растяжение не являются обязательными. inset переключает режим отображения тени с внешней на внутреннюю.

Интерактив

Hands-on: box-shadow

Если вы хотите просто поиграться с тенями в интерактивном режиме, наши коллеги к прошедшей в сентябре конференции Build подготовили демонстрационную страницу: “Hands-on: box-shadow”.

Internet Explorer

Насущный для многих вопрос: box-shadow поддерживается в IE9+.

И еще одна важная деталь: стандартные css-эффекты в IE в отличие от нестандартных старых фильтров, начиная с 9й версии, работают с использованием аппаратного ускорения.

Мой совет: не использовать фильтры вообще, либо продумывать верстку и стили так, чтобы для IE9+ они не применялись.



Сразу две хорошие новости из разных частей света для тех, кто хочет больше погрузиться в современные веб-стандарты и лучше понимать, куда в своем развитии движется веб.

Во-первых, выложены записи конференции W3Conf. Во-вторых, доступны записи докладов Fronteers'2011.

Просвещайтесь! :)

Кстати, у вас еще есть время попасть на наш HTML5 Camp (30 ноября, Санкт-Петербург).



Сегодня (16.11) продолжается конференция W3Conf, посвященная практическим вопросам использования новых веб-стандартов и вообще развитию веб-стандартов.

Прямую трансляцию можно смотреть прямо сейчас здесь: http://www.w3.org/conf/live.html. Кстати, обратите внимание, что видео транслируется не через HTML5, как могли бы ожидать многие поклонники последнего, а через Silverlight с fallback на Flash и далее потоком для Windows Media Player.

И не потому, что Silverlight и Flash такие замечательные (хотя они такие), а потому что делать кроссплатформенно и кроссбраузерно трансляции в HTML5, особенно с поддержкой адаптивного стриминга и прочих плюшек, сегодня просто напросто нереально. Технологии это не позволяют (Apple Live Streaming на iOS-девайсы не в счет.) – и это кажется весьма ироничным :)

Архив записей первого дня выложен уже как надо – в HTML5.

===

Кстати, мы в трансляции своих мероприятий тоже сталкиваемся с аналогичной дилеммой.



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

Стоит немного приподняться над разворачивающимся действом, и вы увидите, как огромная махина веб-документов, распространяемых по сети на множество устройств, внутри многих из которых крутились виртуальные машины для Java-апплетов, Flash- и Silverlight-приложений, сама трансформируется в сеть виртуальных машин, выполняющих веб-приложения.

Вчерашние странички учатся общаться с файловой системой, рисовать на лету, подключаться к камере и микрофону, локально хранить данные, поддерживать соединение с сервером и работать в offline-режиме.

Это грандиозное перевоплощение.

Браузер, который вы знали вчера, подгружающий странички из интернетов, из (относительно) простого приложения для просмотра гипертекстовых документов сегодня сам превращается в среду для выполнения приложений, – да, построенную из других кирпичиков нежели традиционные приложения, да, не всегда, в силу своей природы, спроектированную именно для этих целей (и именно поэтому до сих пор многие вещи проще делать в заточенных под это средах вроде Silverlight или Flash), да, да, да и еще много таких да.

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

Мы, конечно, будем долго называть это браузером, но это, определенно, уже совсем другая история.

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

И если сегодня та же википедия все еще говорит, что браузер – это

программное обеспечение для просмотра веб-сайтов, то есть для запроса веб-страниц, их обработки, вывода и перехода от одной страницы к другой.

То браузер, который активно строится сегодня, – это виртуальная машина для выполнения веб-приложений, построенных на базе веб-стандартов. Если думать о нем именно так, многое происходящее сегодня, действительно, становится на свои места.