SVG и медиавыражения
Одно из достоинств 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">
…как указано выше…
</svg>
Что из перечисленного выше нарисует (потенциально обрезанный) синий круг в HTML-документе? Как и чей вьюпорт следует использовать? Что нужно брать в расчет:
- Размер родительского документа в CSS
<svg>
aтрибутыwidth
,height
,viewBox
<img>
атрибутыwidth
,height
- Размеры
<img>
указанные в CSS
Вот демо того что расписано выше:
В большинстве браузеров…
Для <img>
, SVG масштабируется до размеров элемента, а также вьюпорт для SVG в CSS до размеров <img>
. Таким образом, первый <img>
имеет ширину вьюпорта 50 пикселей, а второй — 100. Это означает, что только ко второму <img>
применится «синее» медиавыражение.
Для <iframe>
, размером вьюпорта SVG является вьюпорт фрейм-документа. Таким образом, в приведенном выше примере, ширина вьюпорта в 50 пикселей CSS, потому что это ширина фрейма.
Что касается <svg>
-разметки помещенной непосредственно в html, то SVG не имеет своего собственного вьюпорта, он становится частью родительского документа. Это означает, что <style>
находится в собственности родительского документа - он не ограничен областью видимости в SVG. Это привлекло мое внимание, когда я впервые использовал встроенный SVG, но это имеет смысл и хорошо определено в спецификации.
Как говорит лисичка?
У Firefox своя точка зрения. Он ведет себя так, как описано выше, за одним исключением:
Для <img>
, вьюпорт это отрендеренный размер элемента в физических пикселях, а это означает изменение отображения в зависимости от плотности пикселей. Первое изображение в примере будет зеленым на экранах с плотностью 1х, но станет синего цвета на экранах с плотностью пикселей 2x и выше. Это проблема, так как некоторые ноутбуки и большинство телефонов имеют плотность пикселей больше чем 1.
Это выглядит как ошибка, тем более, что Firefox не применяет ту же логику к iframe
, но нужно заметить, что спецификация не покрывает случаи когда SVG отображается в <img>
, непонятно как оно должно масштабироваться, не говоря уже, о том, как в этом случае должны вести себя медиавыражения.
Я создал ишью для спецификации, будем надеяться, что проблема будет решена.
Но все становится гораздо сложнее, когда вы начинаете…
Рисовать SVG в canvas
Можно рисовать <img>
в <canvas>
, вот так:
canvas2dContext.drawImage(img, x, y, width, height);
Но когда круг должен стать синим? На этот раз вьюпортов ещё больше. Какие из них следует принимать во внимание:
- Размер родительского окна
- Атрибуты
width
,height
иviewbox
в<svg>
- Атрибуты
width
иheight
тега<img>
- Заданные в CSS размеры
<img>
- Плотность пикселей
<canvas>
- Заданные в CSS размеры
<canvas>
- Ширина, высота указанные в
drawImage
- Ширина, высота указанные в
drawImage
, учитывая примененные к двухмерному контексту трансформации
Как вы думаете? Опять же, спецификация неясна, и на этот раз каждый браузер пошел по своему собственному пути. Давайте ознакомимся:
Насколько я могу судить, вот что браузеры делают:
Chrome
Chrome отталкивается от атрибутов width
и height
, указанных в SVG-документе. Это означает, что если в нём width="50"
, сработает медиавыражение для вьюпорта шириной 50px. Если вы хотите отобразить его так, что бы сработало медиавыражение для вьюпорта шириной 100px, незадача. Независимо от того, какой размер вы используйте в canvas
, он будет рисовать, используя медиавыражение для 50px ширины.
Однако, если в SVG задан атрибут viewBox
, а не фиксированная ширина, Chrome использует плотность пикселей <canvas>
в качестве ширины вьюпорта. Можно утверждать, что это напоминает то, как работает SVG, помещенный непосредственно в html, где окно вьюпорта это все окно браузера, но переключение поведения, основанное на viewBox
это очень странно.
Chrome выигрывает награду «странные-штанишки» за непоследовательное поведение.
Safari
Как и Chrome, Safari использует размер, указанный в документе SVG, с теми же недостатками. Но если SVG использует viewBox, а не фиксированную ширину, он вычисляет ширину на основе viewBox, так что SVG с viewBox = “50 50 200 200” будет иметь ширину 150.
Не так странно как в Chrome, но всё равно ограничивает.
Firefox
Firefox использует ширину и высоту, указанную в вызове drawImage
, с учетом любых трансформаций. Это означает, что если вы рисуете вашу SVG, так, что размер холста 300 пикселей в ширину, он будет иметь ширину вьюпорта в 300px.
Что своего рода отражает странное поведение <img>
— оно основано на отрисовке пикселей. Это означает, что вы получите те же несоответствия плотности, если умножите ширину и высоту вашего холста на devicePixelRatio
(и затем уменьшите с помощью CSS), вы должны это сделать, что бы избежать размытости на экранах с высокой плотностью:
В том, что делает Firefox есть смысл, но это означает, что медиавыражения привязаны к пикселям.
Microsoft Edge
Egde использует размер элемента <img>
для определения размеров вьюпорта. Если <img>
не имеет размеров (display: none
или элемент вне документа), то он возвращается к атрибутам width
и height
, если их нет, тогда он использует внутренние размеры <img>
.
Это означает, что вы можете сделать SVG размером 1000x1000, но изображение <img width= "100">
, вьюпорт будет иметь ширину 100px.
На мой взгляд, это идеальный вариант. Это означает, что вы можете активировать медиавыражения и не зависеть от ширины русинка. Это, кроме того, соответствует поведению адаптивных изображений. Когда вы рисуете <img srcset= "…" width="…">
на холсте, все браузеры соглашаются, что изображение должно содержать ресурс из <img>
.
Фух!
Я создал ишью с предложением принять поведение Edge и предложил дополнение к createImageBitmap
, которое позволит задать вьюпорт из скрипта. Надеюсь, мы сможем добиться большей кроссбраузерности!
Для полноты картины, вот как я собрал данные, а вот полные результаты.