Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(preset-icons): Add CSS SVG Sprite support #2675

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

userquin
Copy link
Member

@userquin userquin commented May 27, 2023

This PR includes:

  • Vite plugin to generate the SVG Sprite in dev (middleware)
  • Add Preset Icons rule to handle sprite- icons
  • Added a working example in vite-vue3

There is an SVG Sprite example in Vue 3 example to play with it, it is quite sensible, we cannot use same approach when adding SVG to the DOM via <use href="..." />.

The PR is in draft, we need to finish build in Vite Plugin, add some tests and update docs: if looks good for you I'll try to finish it.

We need to fix:

  • resolve base path when generating the icon: rn using the path relative to the css
  • emit or generate the svg file for the sprite (build)
  • move some logic in Vite Plugin to @iconify/utils repo and update the logic here

TODO list:

  • move/refactor logic to generator (svg sprite generation)?
  • include logic for cli
  • add support for other bundlers
  • add support to export sprites in icones?
  • add docs

/cc @antfu

@netlify
Copy link

netlify bot commented May 27, 2023

Deploy Preview for unocss ready!

Name Link
🔨 Latest commit 28b2878
🔍 Latest deploy log https://app.netlify.com/sites/unocss/deploys/64d1071481c2aa0008eb5c15
😎 Deploy Preview https://deploy-preview-2675--unocss.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@cyberalien
Copy link
Contributor

Issues:

  • If icon contains <style>, it can be applied to all icons in sprite, which is not intended behavior.
  • If icon contains CSS animation (added via style), it seems to be broken in Safari.
  • If icon contains SVG animation (added via <animate> tag), timing is broken in Safari.

Therefore, icons that contain <style and <animate should not be used in sprites, unless developer knows for sure that it is safe (test without > because it might include attributes).

@stale
Copy link

stale bot commented Jul 26, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jul 26, 2023
@zyyv zyyv removed the stale label Jul 27, 2023
@userquin
Copy link
Member Author

userquin commented Aug 2, 2023

iconify/iconify#235

iconify/iconify#236

@userquin
Copy link
Member Author

userquin commented Aug 6, 2023

Right now a few modules have been copied from iconify PR, later we can remove them:

  • the new logic is in the Preset
  • the integration only for Vite (later we can include others): there is a new plugin for build
    • included dev server middleware to handle 304 and reload properly
    • included build plugin to emit configured css svg sprites and update the css stylesheets to change icon url with the new one assets names (unocsss-<collection>-sprite.svg => unocsss-<collection>-sprite-<hash>.svg )
  • we need to review icon customizations, rn only applied in the preset to include the usedProps applied from the customizations: maybe we need to provide the context

@userquin
Copy link
Member Author

userquin commented Aug 6, 2023

In dev, refreshing the page will return 304 for CSS SVG Sprites, unless there is a config reload:

imagen

On build:

imagen

and the index.css:

imagen

# Conflicts:
#	examples/vite-vue3/src/App.vue
#	playground/src/auto-imports.d.ts
@cyberalien
Copy link
Contributor

cyberalien commented Aug 9, 2023

After testing sprites, I'm not sure adding support for CSS sprites is a good idea because of massive amount of bugs.

TLDR

CSS sprites are rendered correctly in all browsers only if:

  • Icons contain no CSS and no animations
  • Icon has same aspect ratio as element displaying it (basically works only with square icons)
  • Icons contain no overflow elements

Bugs

  1. Scaling bug: icon and span must have same aspect ratio, otherwise it will render incorrectly in Chrome and Safari. See below.
  2. Icons cannot include animations, they fail in Safari.
  3. Icons cannot include CSS because it affects all icons in sprite.
  4. Some icons have shapes outside of viewBox (Noto Emoji set is the biggest offender), in Safari if their placement overlays another icon, that icon will be bugged. While it should be possible to find/delete shapes outside of viewBox, there are no reliable tools to do that (SVGO has multiple bugs related to this), then some shapes can be partially outside of viewBox so they cannot be removed.

Short demo

This is a screenshot of test page where I've been testing using various icons in CSS sprite:

sprites-bug

It contains screenshots of same page rendered in 3 browsers (from left to right): Safari, Chrome, Firefox. That page contains bunch of icons rendered as sprites (last 2 rows are tests for DOM sprites)

Firefox (last part on the right) is the only browser that renders sprites correctly. Chrome and Safari have bugs and they are different.

Scaling

The biggest issue by far is scaling.

I've made a bug report to Chromium about this: https://bugs.chromium.org/p/chromium/issues/detail?id=1471377

Users want to resize and position icons. That means aspect ratio must be maintained when icon is resized. Expecting users to calculate aspect ratio for each icon is unreasonable, so the only icons usable in sprites are square icons.

While vast majority of icons in Iconify icon sets are square, there will always be edge cases and users will likely want to use it with custom icons too.

Overflow

This is related to scaling. If icon has different aspect ratio, Safari shows parts of nearby icons.

However, this is not an issue because Chrome, which is the most used browser, doesn't even get to this point because it absolutely fails at scaling icon and ignores background-size.

WebKit bug report: https://bugs.webkit.org/show_bug.cgi?id=259970

I suspect that when Chrome team fixes scaling bug, it will face the same overflow bug.

Additionally, initial value for background-position is wrong in Safari (and possibly Chrome, but again scaling bug prevents from testing), which is another bug to consider.

Conclusion

I think SVG sprites in CSS is a bad idea.

When using SVG sprites in DOM, it reduces duplication. But in CSS? I don't see any advantages, other than reducing number of requests to server, which can be solved by embedding icons in CSS using data: URI.

@userquin
Copy link
Member Author

userquin commented Aug 9, 2023

When using SVG sprites in DOM, it reduces duplication. But in CSS? I don't see any advantages, other than reducing number of requests to server, which can be solved by embedding icons in CSS using data: URI.

I only test with a few square icons, no idea how bad sprites work in browsers.

It is about reducing css size, if you use a set of icons in your app having a Sprite or a set of sprites will reduce css with a few requests to the server (for example, the unocss-mdi icon set in my vuetify nuxt module).

@cyberalien
Copy link
Contributor

It doesn't reduce CSS size.

Standard SVG: <svg viewBox="..." xmlns="...">content</svg>

Entry for one icon in sprite: <symbold id="..." viewBox="...">content</symbol><use href="..." y="..." /><view id="..." viewBox="..." />

So entry for one icon in sprite is actually bigger than icon when its not in sprite.

@userquin
Copy link
Member Author

userquin commented Aug 9, 2023

@cyberalien we're removing encoded svg from the url and moving the svg content to the Sprite, we're reducing a lot the css size.

The full bundle will be bigger.

@cyberalien
Copy link
Contributor

While CSS size is smaller, sprite is loaded anyway, so it is just splitting content.

Alternative solution to splitting content is to reference a second CSS, which assigns encoded SVG to variables, which are then used in main CSS:

:root {
  --icon-home: url(data: ...);
  --icon-arrow-left: url(data: ...);
}

then import that CSS file separately and use variables.

Icons will be treated as separate SVGs, so all bugs I've mentioned above do not apply.

@cyberalien
Copy link
Contributor

After a lot of consideration, I think CSS sprites is not a good idea.

Bugs mentioned above are big issues, the biggest one by far is aspect ratio in Chrome. Both Chrome and Safari bugs have been reported, confirmed, but it will take a while to get fixed.

Until those bugs are fixed, CSS SVG sprites are not usable.

Then, after bugs are fixed, it will take time for them to make it into latest versions of browsers. Then it will take even more time for users to update their browsers.

While project is aimed at developers, who usually use the latest versions of browsers, actual websites that will display those icons are often aimed at regular folks, who are not so fast to update browsers.

I think CSS SVG sprites will not be usable in next few years, therefore I'm against adding support for it.

@dominikg
Copy link

@cyberalien This is a bit unfortunate. I was looking forward to this feature and intended to use it with monochrome square icons (many popular icon libraries do meet the criteria for rendering correctly you listed above)

Using a sprite reduces total css size, the sprite can also be loaded in parallel and changes less frequently which improves immutable cache hit rate when you deploy a new version. I hope this will be revisited some day, maybe with checks in place that just throw if safe conditions aren't met by an icon that goes to a sprite.

@cyberalien
Copy link
Contributor

You can achieve the same using separate CSS file with variables, as shown in example above, without triggering any bugs.

@stale
Copy link

stale bot commented Oct 19, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants