CSS-live.ru

Маленькая неочевидность в SVG-паттернах: по следам трансформации-невидимки

Взрыв популярности SVG-графики приносит не только новые дизайнерские удачи, но и неожиданные «открытия» неочевидных тонкостей, малоизвестных особенностей и «белых пятен», разбросанных по обширной SVG-спецификации. Даже признанные гуру фронтенда не застрахованы от таких сюрпризов. Вот и наше расследование началось с недоуменного твита Аны Тюдор, знаменитой множеством потрясающих демо-примеров (в т.ч. с SVG):

«Дурацкий» вопрос по #svg: почему первое работает, а второе нет? Единственное различие — width=150 против width=100

Речь шла о следующем примере:

See the Pen SVG Firefox WTF by Ana Tudor (@thebabydino) on CodePen.

В обсуждении выяснилось, что проблема возникает в Firefox, причем по-разному: у кого-то, как у самой Аны, красная линия посередине отобразилась только в левом (меньшем) блоке, у кого-то оба блока отобразились без линии. Где-то паттерн пропадал из виду при width больше 133px, где-то — уже после 80, у кого-то (под Linux) отображались оба, но с огромной загрузкой процессора. Большинство ответивших склонялось к мысли, что это баг Firefox.

Но если присмотреться, то и в Chrome тут можно заметить странность: края линии получились слегка «замыленными», причем в правом (большем) блоке — сильнее (отчего линия там выглядит шире и тусклее). И вместе это наводит на мысль, что это не просто баг — дело пахнет очередной загадкой стандарта!

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

See the Pen SVG Firefox WTF by Ilya Streltsyn (@SelenIT) on CodePen.

Он, в отличие от первого, работает как ожидалось, но… обратите внимание на значения width и height у элементов pattern!

Как всегда, разгадку приходится искать в спецификации, но и там найти ее удается не сразу.

Спецификация SVG1.1 говорит следующее:

Атрибуты ‘x’, ‘y’, ‘width’, ‘height’ и ‘patternUnits’ определяют эталонный прямоугольник где-то на бесконечном холсте. Верхний левый угол эталонного прямоугольника — в точке (xy), а нижний правый — в точке (x + widthy + height).

Вроде всё логично: width и height, как следует из названия, задают размер паттерна. Чтобы заполнить прямоугольник одним «куском» паттерна, логично задать прямоугольнику и паттерну одинаковые размеры (как в примере Аны), не так ли? Подвох может таиться разве что в атрибуте patternUnits, о котором чуть ниже сказано вот что:

Определяет систему координат для атрибутов ‘x’, ‘y’, ‘width’ и ‘height’.

Если patternUnits="objectBoundingBox", действующая система координат для атрибутов ‘x’, ‘y’, ‘width’ и ‘height’ устанавливается по боксу, описанному вокруг элемента, которому задан паттерн (см. Единицы окаймляющего бокса для объекта), после чего к ней применяется трансформация, указанная в атрибуте ‘patternTransform’.

Если атрибут  ‘patternUnits’ не задан, то результат будет как при значении 'objectBoundingBox'.

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

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

И там же сказано, что с этим ключевым словом значения координат и ширины/высоты выражаются в долях размеров прямоугольника, описанного вокруг элемента, к которому применяется паттерн. В наших примерах сам этот элемент — прямоугольник, т.е. доли отсчитываются от его собственных размеров. И чтобы сделать паттерн равным элементу, в качестве размеров нужно задать единицу. Пазл сложился!

Ну а что же происходило в исходном примере? Получается, что «где-то на бесконечном холсте» мы рисовали гигантский — в 100–150 раз больше самого элемента — паттерн, его содержимое тоже масштабировалось до огромных размеров, а потом всё это опять уменьшалось до размеров бокса, чтобы вписаться в него. На мой взгляд, неудивительно, что при этой операции накапливались ошибки округления, и где-то могло произойти переполнение или превышение лимита. И причина загрузки процессора при рендеринге стала понятнее.

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

P.S. Это тоже может быть интересно:

3 комментария

  1. где же вы раньше были с этими примерами… Намедни довелось делать верстку плейсхолдеру с плавающим градиентом, как у фейсбука до загрузки контента, так проблема крылась как раз в значении patternUnits=»userSpaceOnUse»

  2. При objectBoundingBox указание width/height в виде значений 0.1 — 1 не помогает. Хорошо видно, что графика из symbol, используемая через use более четкая чем через pattern в одних и тех же размерах. Что в Chrome на Win, что на мак, что на Safari.

  3. Проблема решена. Как только в заголовке svg указываем width/height в измерениях, которые не потребуют округлений, браузер начинает рендерить символы идентично — как в паттерне, так и отдельный символ.

Добавить комментарий для html-верстальщик Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *

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