Одним из достоинств SVG является то, что вы можете использовать медазапросы для добавления изображениям отзывчивости:

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
  <style>
    circle {
      fill: green;
    }
    @media (min-width: 100px) {
      circle {
        fill: blue;
      }
    }
  </style>
  <circle cx="50" cy="50" r="50"/>
</svg>

Но когда круг станет синим? Спецификация говорит, что свойство min-width должно соответствовать ширине области видимости, но…

Какой именно области видимости?

<img src="circle.svg" width="50" height="50">
<img src="circle.svg" width="100" height="100">
<iframe src="circle.svg" width="50" height="50"></iframe>
<svg width="50" height="50">
  …as above…
</svg>

Какой код из приведенного примера нарисует синий круг (возможно, обрезанный) в документе? И какая область видимости будет использована? Есть следующие варианты:

  • размеры основного документа
  • размеры, заданные атрибутами ширина/высота/viewBox <svg>
  • размеры, заданные атрибутами ширина/высота <img>
  • заданный раскладкой CSS размер <img>

Вот результат этого кода:

width50 width100

Область видимости в большинстве браузеров

В <img> SVG масштабируется до заполнения размеров всего элемента, соответственно, областью видимости SVG являются CSS размеры <img>. Так у первого <img> ширина задана 50, а у второго 100. Это значит, что второй элемент <img> подхватит медиазапрос для синего цвета, а первый — нет.

Для <iframe> областью видимости SVG является область видимости <iframe>. Поэтому в примере выше область видимости равна 50 пикселям, так как это ширина элемента <iframe>.

В строчном <svg> у SVG нет собственной области видимости, это часть родительского документа, а, значит, <style> берется из родительского документа, а не из SVG. Это показалось мне странным, когда я впервые использовал строчный SVG, но это разумно и это прекрасно описано в спецификации.

А что с Firefox?

У этого браузера другой подход. Поведение идентично, за исключением следующего:

У элемента <img> область видимости это полученный в пикселях размер изображения , то есть область видимости зависит от плотности пикселей на устройстве. Первое изображение в демо раскрашивается в зеленый цвет на экранах с плотностью 1x и в синий на экранах 2x. Проблема в том, что у некоторых компьютеров и у многих телефонов плотность пикселей больше 1.

Это похоже на баг, особенно с учетом того, что Firefox не применяет ту же логику к iframe, но мы простим этот косяк, так как спецификации нет точного указания, как должен масштабироваться SVG внутри тега <img>, не говоря уже об обработке медиазапросов.

Я сообщил об этой проблеме в спецификации и, надеюсь, что это будет решено.

Но в следующей теме все становится еще более запутанным.

Рисование SVG в canvas

У вас также есть возможность вывести <img> внутри холста <canvas>:

canvas2dContext.drawImage(img, x, y, width, height);

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

  • Размеры окна документа
  • Атрибуты ширины/высоты/viewBox в <svg>
  • Атрибуты ширины/высоты <img>
  • CSS размеры <img>
  • Пиксельные размеры <canvas>
  • CSS размеры <canvas>
  • Ширина/высота, заданные в drawImage
  • Ширина/высота, заданные в drawImage с учетом трансформаций 2d context

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

SVG size
<img> size
<img> CSS size
<canvas> size
drawImage size
Context transform

Попробую описать, что делают браузеры.

Chrome

Chrome использует атрибуты ширины и высоты, заданные в SVG. Это значит, что если в документе SVG задано width="50", медиазапросы дадут область видимости в 50 пикселей. Если вы хотели вывести такой SVG с медиазапросом для ширины в 100 пикселей, то вам не повезло — независимо от размеров холста, он всегда будет выводится, исходя из ширины в 50 пикселей.

Однако, если в SVG также задан атрибут viewBox, а не фиксированные размеры, Chrome использует пиксельную ширину <canvas> в качестве ширины области видимости. Вы можете поспорить, что это похоже на то, как работают медиазапросы в строчном SVG, когда областью видимости является все окно, но такое изменение поведения на основе viewBox достаточно странно.

В общем, Chrome выиграл первый приз по части причудливости.

Safari

Как и Chrome, Safari использует размер, заданный в документе SVG со всеми его минусами. Но если SVG использует viewBox вместо фиксированной ширины, то ширина рассчитывается на основе viewBox, поэтому у SVG с viewBox="50 50 200 200" ширина будет равна 150.

Менее причудливое поведение, чем у Chrome, но не менее ограничивающее.

Firefox

Firefox использует ширину/высоту, заданные в вызове drawImage, с учетом всех трансформаций контекста. Это значит, что если вы нарисуете SVG с шириной холста 300 пикселей, область видимости будет равна этим 300 пикселям.

Это в какой-то степени продолжает странное поведение <img> — оно основано на количестве задействованных пикселей. Это значит, что поведение будет различаться, если вы будете манипулировать холстом с помощью devicePixelRatio и масштабировать обратно с помощью CSS — это делается, чтобы избежать “замыливания” на экранах с высокой плотностью пикселей:

helloworld
<img>, <canvas>, <canvas> с учетом плотности пикселей (без этого canvas будет замыленным на retina-дисплеях)

В поведении Firefox есть логика, но все ваши медиазапросы привязаны к задействованным пикселям.

Microsoft Edge

В Edge для определения области видимости используется размер <img>, заданный раскладкой. Если у <img> нет раскладки (отсутствует в дереве документа или применено свойство display:none), тогда используются атрибуты width/height, при их отсутствии используются внутренние размеры <img>.

Это значит, что вы можете нарисовать SVG размером 1000x1000, но если у изображения задано <img width="100">, область видимости будет равна 100px.

На мой взгляд, это идеально. Это значит, что вы можете активировать медиазапросы для ширины независимо от нарисованной ширины. Когда вы рисуете на холсте <img srcset="…" sizes="…">, все браузеры согласятся, что в качестве нарисованного изображения должно быть то, которое выбрано <img>.

Заключение

Итак, при написании статьи я добавил сообщение о проблеме в спецификации для адаптации поведения Edge и предложил дополнение к createImageBitMap, чтобы область видимости можно было задавать в скрипте. Надеюсь, что поведение браузеров станет более единообразным в этом вопросе.

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