суббота, 5 января 2013 г.

VML, такой VML

Немного предисловия
Я в своей работе очень часто имею дело с векторной графикой в web, позволяющей создавать графические изображения, которые легко поддаются масштабированию.
Собственно основным отличием векторной графики от растровой является то, что примитивы это не описания цветов пикселей, а описания графических объектов (линий, полигонов и т.д.) через их координаты. Эти координаты считаются относительно размеров холста. Соответственно при изменении размеров холста очень просто и легко изменяются координаты примитивов.
Для сравнения растровой и векторной графики можно открыть Photoshop (растр) и Illustrator (вектор).


На просторах веб-разработки для описания создания векторной графики была выработана официальная спецификация языка разметки SVG (Scalable Vector Graphics), которая в принципе проста, понятна и переносима (например, данные элемента path можно легко перенести в WPF).
И все бы было хорошо, и жизнь была бы сказкой, если бы не одно но...
Это "но" это Internet Explorer версий ранее 9-й. Эти исполины знать ничего не знают о каком-то там SVG.
А знают они о чуде под названием VML (Vector Markup Language). Это язык векторной разметки, разработанный Microsoft в 1998 году (тогда он был подан на рассмотрение W3C).
Соответственно если Вам нужна поддержка векторной графики для старых версий эксплорера, то нужно будет покопать и VML.

Собственно о чем это я
Я не буду вдаваться в описание того, как использовать SVG или VML и для чего. На просторах паутины достаточное количество материалов, позволяющих более или менее аналогично отрисовывать примитивы как на SVG, так и на VML. Например, можно попользовать и покопать библиотеку Raphael, дающую кроссбраузерную прослойку.
Так вот, это рассказывать не буду.
А расскажу про одну интересную особенность, замеченную при разработке под IE8 (что твориться в IE7 не проверял, поэтому наговаривать не буду).
Итак, задача, собственно простая: нарисовать кружок и применить к нему простую сплошную заливку.
Казалось бы, что может быть проще:

var circleElement = document.createElement('v:oval');
circleElement.fillcolor = "#ff0000";
if (parentNode)
   parentNode.appendChild(circleElement);


В моем случае код усложнялся ввиду необходимости использования общей прослойки над VML и SVG, поэтому создание и применение цвета было разбито на 2 отдельные функции и выглядело примерно так:
circle: function(parentNode)
{
  var circleElement = document.createElement('v:oval');
  if (parentNode)
   parentNode.appendChild(circleElement);

  return circleElement; 
}

setAttr: function(node, attrName, attrValue)
{
  if (attrName === 'fill' && !SVG)
    this.setAttrVml(node, { first: 'fill', second: 'color', fullName: "fillcolor', value; attrValue });
}

setAttrVml: function(node, attr)
{
  var firstProp = attr.first;
  var secProp = attr.second;
  var prop0 = node[firstProp];
  if (prop0)
  {
    prop0[secProp] = attr.value;
  }
  else
  {
    //при инициализации объекта, когда он не добавлен на страницу
    var fullAttrName = attr.fullName;

    if (attrValue)
      node.setAttribute(fullAttrName, attr.value);
    else
      node.removeAttribute(fullAttrName);
  }

}

Вызов:
var el = Workaround.circle(parentNode);
Workaround.setAttr(el, 'fill', "#ff0000');

Уже из этого кода становится понятно, что VML не хочет просто так нам помогать: для элемента, который был уже ранее создан и проинициализирован нужно обращаться к свойствам как к объектам, иначе - можно через setAttribute. Честно, не могу назвать причину. Эти выводы получены нами (нашей командой) скорее экспериментально.

Однако вроде как задача выполнена и все работает. Разработчики довольны собой, заказчики довольны разработчиками.

И тут как гром среди ясного неба запрос в суппорт от одного из заказчиков: при использовании вашего кружочка у нас на сайте он не закрашивается, да еще и граница почему-то черная.
Что? Как? Почему?
Первое, что стали проверять, это структуру нерабочей страницы и что на ней используется.
Оказалось, что наш компонент используется в панелях GWT. Однако исполняемый код и логика (прохождение кода по определенным ветвям условий и циклов) от этого не менялась.
Стали копать глубже.
И докопали до очень интересного факта: в случае использования в панелях GWT (на самом деле, случаи, когда описанная ситуация может происходить, наверняка не ограничиваются gwt, однако мы нашли именно в таком месте) при создании элемента кружка движок браузера добавляет 2 (!!!) одинаковых (!!!) атрибута coordsize="21600,21600" к создаваемому элементу. Почему два? Мистика. Почему один не переписывает второй? Тоже мистика. Однако это было единственным отличием при использовании вне GWT и внутри.
Однако даже локализация проблемы не дала нам ее решения. Ведь дело в движке браузера...
По идее, мы могли "забить" и бросить это дело. Однако это бы значило ухудшение репутации в глазах заказчика.
Поэтому мы принялись искать решение. И оно было найдено, причем довольно быстро и легко. Но, в очередной раз, экспериментально.
Решение ниже:
var circleElement;
var docFragm = document.createElement('div');
docFragm.innerHTML = "";
circleElement = docFragm.firstChild;
circleElement.outerHTML = circleElement.outerHTML;
var par = docFragm.parentNode;
par.removeChild(docFragm);

Т.е. мы переписали логику создания элемента на innerHTML.
Плюс еще была добавлена загадочная строка circleElement.outerHTML = circleElement.outerHTML. Ее добавили, т.к. нашли информацию о том, что в IE8 после создания элемента VML часто бывают проблемы при обращении к свойствам. И чтобы этой проблемы избежать, нужно переприсвоить outerHTML. Так ли это на самом деле, я не знаю, ибо с проблемой не сталкивался, но на всякий случай строчку добавил :)

Заключение
 Надеюсь, что этот пост спасет ваше время, т.к. мы убили на исследование проблемы и ее устранения два дня.

1 комментарий: