SVG и медиазапросы
Оригинал статьи: SVG & media queries
Оглавление:Одним из достоинств 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>
Вот результат этого кода:
Область видимости в большинстве браузеров
В <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
Так на что же рассчитывать? Опять, в спецификации нет ясности по этому поводу и на данный момент каждый браузер определяет это по-своему.
Попробую описать, что делают браузеры.
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 — это делается, чтобы избежать “замыливания” на экранах с высокой плотностью пикселей:
В поведении Firefox есть логика, но все ваши медиазапросы привязаны к задействованным пикселям.
Microsoft Edge
В Edge для определения области видимости используется размер <img>
, заданный раскладкой. Если у <img>
нет раскладки (отсутствует в дереве документа или применено свойство display:none
), тогда используются атрибуты width
/height
, при их отсутствии используются внутренние размеры <img>
.
Это значит, что вы можете нарисовать SVG размером 1000x1000, но если у изображения задано <img width="100">
, область видимости будет равна 100px.
На мой взгляд, это идеально. Это значит, что вы можете активировать медиазапросы для ширины независимо от нарисованной ширины. Когда вы рисуете на холсте <img srcset="…" sizes="…">
, все браузеры согласятся, что в качестве нарисованного изображения должно быть то, которое выбрано <img>
.
Заключение
Итак, при написании статьи я добавил сообщение о проблеме в спецификации для адаптации поведения Edge и предложил дополнение к createImageBitMap, чтобы область видимости можно было задавать в скрипте. Надеюсь, что поведение браузеров станет более единообразным в этом вопросе.
Ну и для полноты материала — вот тест, использованный для получения материала и таблица со всеми итоговыми данными.