Глассморфизм и SVG

Всем привет. Я Андрей Осипов, фронтендер из Контура. Почти три года назад, когда у компании был еще старый фирменный стиль, мы столкнулись с проблемой экспорта из фигмы изображений в формате SVG. Сложность была с изображениями, где был эффект глассморфизма, он же эффект матового стекла (frosted glass).

Недавно чисто из любопытства я решил проверить, починили ли в Figma эту проблему. Проблема осталась. Плагинов так и нет, а гугл не предлагает хороших решений. Поэтому решил выложить эту статью сюда, может, кому пригодится.

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

e5a6328ba47774a01a94d0cf9f56d628.png

Эффект просто напросто пропадает. Согласитесь, без него уже как-то что-то не то.

При этом экспорт в растровые форматы (PNG, JPEG) работает корректно. Не буду здесь расписывать все плюсы использования svg перед растровыми форматами, могу лишь сказать, что этих плюсов на мой взгляд достаточно много, чтобы заморочиться и постараться разобраться в проблеме.

Ищем больное место

Я был уверен, что эффект размытия в SVG есть и работает, так как встречал изображения с этим эффектом. Давайте разбираться. Для примера я взял изображение из старого брендбука с женщиной, держащей стеклянное программное окошко.

9146e246eeab075b1aff0075e0c66857.png

В фигме этот эффект делается просто применением эффекта Background blur.

bc3d0f13c475634cfdff9d3019b9cc9f.png

Однако после экспорта в SVG оно выглядит примерно так.

0a1ee8f3c8cabf5c69c8f93afbc95c7e.png

SVG удобен тем, что это текстовый формат на основе XML, а значит увлеченному фронтендеру не так сложно с ним разобраться. Давайте попробуем заглянуть внутрь и посмотрим что там случилось. (см ill-01-people-prize.svg)

Внутри находится множество команд, описывающих форму и отображение элементов иллюстрации. В данном изображении используется множество тегов , которые позволяют нарисовать произвольную форму различными методами, как правило кривыми Безье. А также могут встречаться отдельный команды, описывающие примитивные формы. В данном примере в самом начале используется , чтобы задать прямоугольный фон с серой подложкой. Формы могут объединятся в группы тегом .

Однако нас больше интересует специальная секция , где записаны команды, которые не отображают конкретные элементы, однако служат для описания фильтров, масок и черт знает чего ещё. Они могут быть применены через атрибуты к элементам форм и групп.

Давайте попробуем отыскать нужный нам эффект. Найти нужный элемент нам помогут dev tools браузера:

f2b0255d3134f9037ef6b8d7789c410f.png

Элемент со стекляшкой обернут в группу, к которой применен фильтр filter1_bd_4171_33259.

Команды фильтров применяются последовательно и могут принимать на вход с помощью атрибута in различные варианты изображения элемента благодаря ключевым словам. Промежуточный результат может сохранятся с помощью атрибута result и также может быть передан в атрибут in следующих фильтров. Если у команды явно не указан in, она берет на вход результат предыдущего фильтра. Давайте теперь разберем этот фильтр.


    
        feFlood заливает область фильтра цветом, 
        но в данном случае он заливает прозрачным
        Я так и не понял для чего нужно это преобразование. 
    
    
 
    
        Берем фоновое изображение, блюрим и потом используем альфаканал элемента в качестве маски,
        чтобы обрезать заблюренный фон по форме элемента. Потом сохраняем результат в переменную
        "effect1_backgroundBlur_4171_33259"
    
       
    
 
    
        Далее ещё несколько хитрых манипуляция, чтобы сделать тень
    
    
    
    
    
 
    
        Накладываем размытый фон на тень
    
    
 
    
        Накладываем получившийся результат на оригинальный элемент
    
    

Вроде всё чётко и должно работать. Но после танцев с бубнов вокруг этих фильтров можно заметить, что не работает атрибут in="BackgroundImage", при этом он описан в спецификации. Также там написано, что надо навешивать атрибут enable-background на элемент для работы этого фильтра. Однако даже в примерах из спецификации оно не работает. Я попробовав всяко разно, тоже результата не добился. Получается, браузеры по какой-то причине не поддерживают эту фичу, а значит мы не можем использовать фон в фильтрах.

По сути in="BackgroundImage" берет копию фоновой части изображения. И мы ведь можем сделать тоже самое, просто скопировав все элементы, находящиеся под стеклом. Объединить их в группу, применить к ним размытие и обрезать по форме стекла. Однако копировать столько элементов вручную выглядит как-то не рационально.

Режем стеклом

Каюсь, дальнейшее решение мне помог найти stack overflow.

Тег  позволяет нам копировать элементы по ссылке, в том числе и группы. Если мы объединим в группу все находящиеся под стеклом элементы, то сможем скопировать фон. Группируем всё, что находилось до стекла, в слой backlayer, копируем и применяем к нему простой фильтр с размытием.

...


...

    
        
    
    ...

1d58a454407713e3243e511a53650709.png

Далее надо обрезать фон по форме стекла. В этом поможет атрибут clip-path. Ему нужно передать ссылку на элемент , который должен содержать нужную форму. Создадим такой элемент, а внутрь скопируем форму стекла, используя тот же .


    ...
    
    
    ...
    
        
        
            
        
        
            
        
        ...
    

4efef0cb118ed1a2bdd6c0a46bc7ed69.png

Итоговый SVG получился такой (см ill-01-people-prize-result1.svg)

Перфекционизм

Отлично, но есть ещё проблемка. У этой картинки был непрозрачный серый фон. И вот что будет, если его удалить и поместить иллюстрацию в место с произвольным фоном.

a788c9968c212c128c71f359a6358cf0.png

В большинстве случаев на этом можно остановится и забить на этот кейс, так как чаще всего у иллюстраций предсказуемый статичный фон, который можно захардкодить в сам SVG и всё будет работать. Однако я ради интереса пошел дальше.

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

В приведенной выше иллюстрации на зеленом фоне проблема заключается в том, что сквозь размытую копию фона просвечивает оригинальный фон, и они накладываются друг на друга. Решением тут является вырезать из оригинального фона прямоугольник по форме стекла.

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


        ...
                    
                        
            
        
        ...

В таком виде маска не заработает, так как у нашего стеклышка #glass уже есть заливка и переопределить её в  не получится. Решить это можно тем, что перенести оригинальную форму стелышка в секцию  и оттуда уже использовать в нужном месте через , применяя заливку и фильтр. В таком случае заливка применяется корректно.

...


...

        
                    
                        
            
        

Далее нам надо применить маску к фону. Если тупо применить её к группе #backlayer, то вырежется дырка. А в месте, где мы используем размытый фон, этого не надо. Поэтому можно обернуть #backlayer в ещё одну группу и применить маску к ней.


        
            ...
        
 

75f76fe627487f22f4d9d0daf57e6cab.png

Отлично, теперь этот SVG работает не хуже экспортированного PNG. (см. ill-01-people-prize-result2.svg)

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

2995bf07dda72fd24305c9097329e08a.png

Дополнение

Тег  вроде как полноценно может работать не везде, так как относится к спецификации SVG 2. В моем случае, я столкнулся c тем, что такие SVG с аргументом href не может открыть WebStorm. Чтобы это починить, нужен фоллбэк к старой спецификации . И чтобы он заработал, нужно добавить ссылку на спецификацию в корневой элемент 

Выводы

Починить глассморфизм в иллюстрациях после экспорта фигмы возможно, и я рекомендую попробовать это делать там, где хотелось бы сохранить плюшки SVG. Если наловчиться, это делается достаточно быстро. Однако если хочется сохранить прозрачность в иллюстрациях, это может по-прежнему работать косячно в местах с этим эффектом. В идеале хотелось бы чтобы или в фигме починили экспорт или автоматизировать с помощью какого-нибудь плагина.

© Habrahabr.ru