13 January 2011

Проблемы использования SVG-кнопок в браузерах

Vector graphics
Данная статья является продолжением статьи Рисуем кнопку в SVG, в которой рассматривались проблемы создания SVG-изображений, предназначенных для использования в качестве кнопок на веб-страницах. Здесь я перейду непосредственно к внедрению полученных картинок в HTML-код и расскажу, с какими проблемами столкнётся при этом разработчик, как эти проблемы можно решать и что делать с проблемами, которые решить не удаётся.

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

Вставка кнопки на страницу

Для добавления SVG-изображения в HTML-страницу существует пять способов:
  1. тег <object>;
  2. тег <iframe>;
  3. тег <embed>;
  4. тег <img>;
  5. CSS-стиль background.
Роясь в интернетах в поисках информации про SVG, я наткнулся на страничку, где все эти способы собраны воедино для проверки их работы в разных браузерах. Я проводил тестирование в Opera 10.63 и 11.00, Firefox 3.6.13, Chrome 8.0.552.224. IE я не стал включать в этот список, поскольку в последней стабильной версии SVG не поддерживается. Я планировал сначала разобраться с остальными браузерами, и если бы всё прошло нормально, установить в IE дополнительный плагин для поддержки SVG и проверить его поведение, а также поиграться с разрабатываемой сейчас девятой версией. Но до этого дело не дошло, поскольку (как уже было сказано) даже с браузерами, официально поддерживающими SVG, вылезло слишком много проблем. Тратить же теперь дополнительное время на проверку IE только ради включения результатов в статью я считаю нерациональным. Прошу прощения у поклонников данного браузера, ежели таковые встретятся среди читателей. Равно как и у поклонников остальных браузеров, не попавших в мой список.

Итак, в результате проверки получилась следующая картина:
  • Теги <object>, <iframe>, <embed> поддерживаются всеми из протестированных браузеров.
  • Тег <img> отказался работать в FF, а в Хроме и Опере работает исключительно в режиме картинки: внедрённые скрипты и события не обрабатываются.
  • CSS-стиль background же оказался самым разносторонним:
    • в Firefox он не поддерживается совсем;
    • в Хроме отображается корректно, но, как и в <img>, не поддерживается обработка событий и скриптов, а вдобавок к этому ещё и анимация не работает;
    • в Опере он то работает, то нет; похоже, что стиль не срабатывает, если в коде страницы присутствует ещё и тег <img> с той же SVG-картинкой, причём этот тег должен располагается до элемента с картинкой-фоном. Во всех остальных случаях фон отрабатывает корректно, но, как и в Хроме, выводится статической картинкой, без обработки событий, скриптов и анимаций. (Кстати, спасибо хабралюдям Xpeh и tenshi за их уточнения по поводу этой функциональности в комментариях к предыдущей статье.)

Получается, что Firefox портит нам всю малину. Для наших целей не нужны ни внедрённые скрипты, ни внутренняя обработка событий в SVG: всё необходимое можно сделать и в самом HTML-файле (более того, в некоторых аспектах это было бы значительно удобнее), так что саму картинку вполне можно было бы выводить тегом <img>. Но увы, приходится выбирать между <object>, <iframe> и <embed>. Существенной разницы между ними нет, но тег embed исключён из стандартов HTML 4.0 и XHTML 1.0, а тег iframe несовместим со Strict-спецификациями. Поэтому лучше всего оставновиться на варианте object как наиболее универсальном.

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

<object data="./button.svg" type="image/svg+xml" style="width: 7em; height: 1.5em;"></object>

Внутри тега можно вписать любой HTML-код: он будет использоваться на браузерах, не поддерживающих SVG. Что ж, это нам пригодится, так как рано или поздно всё равно пришлось бы вспомнить про IE (да и другие браузеры без SVG могут встретиться). Для них мы сможем добавить внутрь тега обычный <input type="button">.

Модификация текстовой надписи

Итак, мы вставили нашу кнопку. Собираемся добавить вторую… и тут же понимаем, что для этого нам придётся делать новый SVG-файл, так как надпись хранится внутри SVG-кода. Но количество файлов-картинок хотелось бы минимизировать, ведь в частности именно для этого и задумывалось использование вектора! Как быть? Здесь возможны несколько путей.

1. Наложить надпись поверх кнопки средствами HTML.
Сразу скажу, что я этот способ детально не исследовал, но нутром чую, что проблем он принесёт немало. Типичное решение подобных задач состоит в расположении текста после кнопки и сдвиге его влево с помощью CSS. Но не уверен, что таким способом удастся добиться корректного центрирования текста по кнопке. Значит — JS с подстройкой позиционирования, значит — постоянное слежение за положением объектов на странице (например, при изменении размеров окна или динамическом показе/скрытии блоков)… А ещё стоит учитывать, что дизайн кнопки может подразумевать сдвиг этого текста вниз-вправо при обработке нажатия (для эффекта трёхмерности)… В общем, при одной только мысли обо всём этом мне стало не по себе, поэтому я стал искать другие способы.

2. Вставка текста в SVG.
Этот вариант предполагает, что у нас в коде страницы есть скрипт, который пробегает по всем SVG-кнопкам и вставляет в них нужный текст (например, вызывая заранее составленную для этого функцию в самом SVG). Это уже лучше, но неприятным моментом остаётся то, что код кнопки и текст этой кнопки оказываются сильно разнесёнными. Можно, конечно, вписывать после каждого тега <object> ещё и короткую скриптовую строчку с добавлением текста кнопки в какой-нибудь массив, но всё равно это как-то длинновато. Есть третий способ, на мой взгляд, более красивый.

3. Вытаскивание текста из тега.
У тега <object> есть атрибут standby, который содержит текст, выводимый браузерами вместо содержимого объекта, пока этот объект грузится. Оказалось, что SVG-кнопка вполне может получить значение этого атрибута и использовать его для модификации надписи своими силами. И получается, что одним махом мы убиваем двух зайцев: с одной стороны, у нас есть подстраховка на случай каких-то залипаний загрузки — пользователь увидит не пустое место, а осмысленный текст (мне, правда, так и не удалось спровоцировать такое поведение); с другой стороны, текстовая метка теперь реализована так же удобно и наглядно, как классический value в теге <input>.

Обработка нажатий

Следующая проблема заключается в том, что у нас не получится навесить обработчик мышиного щелчка на тег <object>: щелчок пересылается в SVG-объект и обрабатывается там. Естественно, мы могли бы загнать обработку события внутрь кнопки, но это неразумно: ведь кнопок много, а SVG-файл один. Вместо этого лучше делегировать обработку скриптам из родительской страницы. Здесь, опять-таки, варианты реализации могут разниться, я остановился на следующем: в библиотечном JS-файле определил функцию doClickAction, которая вызывает обработчик конкретной кнопки, а из «кнопочного» скрипта вызываю эту функцию, передавая ей идентификатор кнопки. Примерный код выглядит следующим образом.

Код HTML-страницы:

<object id="my-button1" data="./button.svg" standby="My Button1" type="image/svg+xml">
  <input type="button" id="my-button1-ie" value="My Button1"
         onclick="javascript:doClickAction(parentNode.name);" />
</object>
<object id="my-button2" data="./button.svg" standby="My Button2" type="image/svg+xml">
  <input type="button" id="my-button2-ie" value="My Button2"
         onclick="javascript:doClickAction(parentNode.name);" />
</object>

<script type="text/javascript">
// <![CDATA[
var signal_handlers = new Object();

function registerSvgAction(signal_id, handler) {
	signal_handlers[signal_id] = handler;
}

function doClickAction(signal_id) {
	if (signal_handlers[signal_id])
		signal_handlers[signal_id]();
	else
		alert('Internal error: unknown SVG signal (' + signal_id + ')!<br />' +
		      'Please contact the developers.');
}

registerSvgAction('my-button1', function () { alert('First button clicked!'); });
registerSvgAction('my-button2', function () { alert('Second button clicked!'); });
// ]]>
</script>

Код SVG-кнопки:

<svg version="1.1" onload="init()" onclick="doAction()">

<script type="text/ecmascript">
// <![CDATA[
function init() {
	// btnLabel и btnLabelShadow - элементы <text> для надписи и тени
	var btnLabel = document.getElementById('btnLabel');
	var btnLabelShadow = document.getElementById('btnLabelShadow');
	var newText = document.createTextNode(frameElement.standby);
	btnLabel.appendChild(newText);
	btnLabelShadow.appendChild(newText.cloneNode(true));
}

function doAction() {
	parent.doClickAction(name);
}
// ]]>
</script>


В этом коде я вставил дополнительные теги <input> для отображения кнопок в не-SVG-браузерах, а в качестве обработчика нажатия назначил для них вызов той же самой doClickAction, передав ей идентификатор родительского object'а (т.е., то же самое, что передаст обработчик из SVG). Учитывая такую однородность, можно было бы попробовать автоматизировать вставку этих кнопок скриптом, но доступ к содержимому объектов в IE попросту блокируется. Впрочем, если делать скриптовый проход, то уж проще сразу заменить все объекты кнопками, не заморачиваясь с вложенностью.

Также здесь необходимо учитывать один неочевидный аспект: реально скрипт хоть и вызывает функцию родительской страницы, текущим адресом будет считаться адрес SVG-файла. Как следствие, все относительные адреса будут отсчитываться от каталога, где лежит файл кнопки, а не от реального текущего адреса страницы. Этот момент придётся учитывать, например, при открытии новых окон через window.open(). Решением может быть использование абсолютной адресации либо предварительное сохранение текущего адреса в переменной с последующим использованием её для вычисления нового адреса.

Размер шрифта

Всё это время я аккуратно обходил стороной вопрос размера шрифта для текстовой надписи на кнопке. Но теперь пришла пора заняться этой задачей. Что хотелось бы получить? В идеале — такой же шрифт, как на основной странице. К сожалению, здесь нормального решения найти не удалось. Видимо, правильнее будет в каждом конкретном случае оценивать, в каких пределах могут меняться стили кнопочной надписи, и в зависимости от этого выбирать наименее трудоёмкий путь реализации. Например, в моём проекте шрифт везде одинаковый. Соответственно, свойство font-family я прописал прямо в SVG, а размер высчитываю в инициализационной функции, масштабируя текст по реальной высоте кнопки (которую получаю, обращаясь к элементу родительской страницы). В более сложных случаях может потребоваться менять цвет надписи, толщину или сам вид шрифта. Для таких ситуаций можно воспользоваться, например, навешиванием стиля на тег <object> с последующим вытаскиванием его внутри SVG-функции init(), разбором и присваиванием полученных стилей текстовому элементу. Также вариантом может быть глобальный обработчик в HTML-коде, который после загрузки страницы пробегает по всем кнопкам, вызывая для каждой из них какую-нибудь «подстроечную функцию». Повторюсь, здесь всё слишком индивидуально, чтобы можно было давать какие-то универсальные рекомендации.

К сожалению, даже в таком простом решении, как мой первый вариант, нашлись свои заморочки. А именно: если кнопка изначально скрыта, то Firefox в качестве её размеров возвращает ноль, и размер шрифта вычислить не удаётся. Корректного решения этой проблемы я пока придумать не смог, все варианты выглядят слишком некрасиво и/или неэффективно. Отловить событие показа кнопки не получилось, т.к. его попросту не существует. Делать явный довесок в виде переинициализации кнопки при показе ранее скрытого блока — можно, но таких мест может быть много, и об этом легко забыть. Также можно попробовать каким-то образом вычислить текущие параметры шрифта на странице и использовать в кнопке полученный размер.

Прочие проблемы

Как видите, говорить о простоте работы с SVG не приходится. Но, видимо, этого оказалось мало, и после всех вышеупомянутых проблем полезли ещё и новые. Их сложно разбить на какие-то категории, поэтому я просто перечислю всё то, на что мне «повезло» нарваться.
  • Opera: Не работает текстовый стиль dominant-baseline для изменения положения базы шрифта. В результате становится невозможным выровнять текст по вертикали, приходится подгадывать положение, а то и подстраивать его динамически в зависимости от размеров кнопки и шрифта, чтобы текстовая метка выглядела пристойно.
  • Chrome: При использовании масштабирования страницы SVG-элементы масштабируются, а вот размеры областей, выделенных под них — нет. В результате кнопки вылезают за пределы областей и обрезаются, да ещё и полосы прокрутки появляются, закрывая собой бо́льшую часть изображения. Главное преимущество SVG — масштабируемость — оказывается попросту выброшенным на помойку!
  • Opera: Активно глючит при переиспользовании изображений из кэша. В частности, если перейти по ссылке, а потом нажать в Опере кнопку Back для возврата на предыдущую страницу, все SVG-изображения не отображаются, а «зависают» во внутреннем SVG-скрипте инициализации. Также иногда наблюдаются проблемы при динамическом создании SVG-кнопки через createElement: если используется изображение, присутствующее на странице, оно подвешивает загрузку страницы и не отображается, пока не нажмёшь Esc (да и после этого периодически наблюдаются разного рода неприятные эффекты).
  • Общая проблема: Размер шрифта в вышеописанном решении оказывается фиксированным и не меняется при масштабировании страницы, размеры кнопки и надписи перестают соответствовать друг другу. Соответственно, после изменения масштаба страницу необходимо перезагрузить, чтобы кнопки переинициализировались правильным размером шрифта. Не исключаю, что это я недоразобрался с единицами измерения, но решить эту проблему у меня не получилось. Возможно, подстройку удастся реализовать каким-нибудь глобальным JavaScript-обработчиком, каким-то образом следящим за масштабом и вызывающим принудительное обновление кнопок.
  • Firefox: В режиме масштабирования «Только текст» с размером шрифта происходит что-то жуткое. Я сходу не смог понять, по каким правилам выдаются размеры элементов в этом режиме, а задерживаться на этой проблеме уже не видел смысла.
  • IE: Как было сказано, SVG в нём не поддерживается, и вместо картинок отображаются обычные кнопки, которые я поместил внутрь тегов <object>. Проблема в том, что до IE очень туго доходит, что он не умеет работать с SVG. Поэтому, встретив кнопку, он выводит пустой блок, задумывается на некоторое время, потом рисует вместо блока кнопку. Потом то же самое со второй кнопкой, с третьей… В общем, отрисовка шести кнопок занимает в нём секунды полторы–две.


Итоги

Что остаётся в сухом остатке? «Ну не шмогла я, не шмогла!» Я для себя сделал вывод, что нынешняя инфраструктура пока не готова к масштабному внедрению SVG (прошу прощения за невольный каламбур). Бодаться со всеми вышеперечисленными (и, вполне возможно, не только с ними) проблемами, ошибками, недоделками — всё это занимает слишком много времени и усилий, при том, что ограничения текущих реализаций не позволяют даже воспользоваться в полной мере теми преимуществами, которые должно было бы принести использование SVG.

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

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

Засим откланиваюсь и благодарю за внимание.

UPD: Добавил в список проблему замедленной отрисовки в IE.
UPD2: В одном месте перепутал браузер, исправлено.
Tags:svgкнопкибраузерыпроблемы совместимости
Hubs: Vector graphics
+17
15.4k 43
Comments 43