图标对于现代 Web 界面至关重要。它们简化了导航、明确了功能并增强了视觉吸引力。然而,高效地实现图标涉及可伸缩性、动态加载和服务器端渲染 (SSR) 兼容性等挑战。
为了应对这些挑战,我们开发了 Nuxt Icon v1 — 一个为 Nuxt 项目量身定制的多功能、现代解决方案。通过构建在成熟的图标渲染技术之上并引入新的方法,Nuxt Icon 弥合了性能、可用性和灵活性之间的差距。
在本文中,我们将探讨图标渲染的挑战、图标解决方案的演变,以及 Nuxt Icon 如何结合这些方法的最佳方面,为开发人员提供无缝体验。
为什么图标具有挑战性?
乍一看,图标似乎很简单 - 它们本质上只是微小的图像元素,可以增强用户界面、提供视觉线索并提高可用性。
但是,从工程角度来看,它们带来了一些挑战。理想的图标应具备以下特点:
- 可着色:能够适应主题和配色方案。
- 可伸缩:在各种尺寸和分辨率下清晰渲染。
- 可管理:图标集可能包含数百或数千个图标。
- 高效打包:最大限度地减少网络请求。
- 优化加载:影响应用程序性能和用户体验。
- 动态:支持为用户生成或运行时定义的图标进行动态加载。
要满足所有这些需求,需要一个精心设计的解决方案,以平衡各种权衡。让我们探索图标解决方案的演变以及它们如何应对这些挑战。
图标解决方案之旅
多年来,开发人员尝试了各种技术来高效渲染图标。让我们探索这些解决方案的演变以及它们面临的挑战。
1. <img>
标签:早期
最直接的解决方案:使用 <img>
标签。这是早期 Web 中常用的方法。
您可以托管图像资源,并使用 <img>
标签链接到该图像,指定其宽度和高度。它很简单,不需要任何设置或运行时依赖项,并且可以在浏览器中原生工作。
但是,存在一些缺点。图像可能会变得像素化、缺乏颜色控制且无法很好地缩放。每个图标都是一个单独的图像文件会导致大量的网络请求,这可能会很慢,尤其是在 HTTP 1.1 时代。在下载图像之前,您可能会看到图标闪烁不可见,这可能会损害用户体验。最后,编写起来非常冗长,因为您需要指定图像的完整路径并管理相对路径。这解释了为什么这种方法在当今的现代网站上很少使用。
2. Web 字体:图标字体
作为图标演变的下一步,Web 字体成为一种流行的解决方案。字体本身是矢量化的并且可着色的,使其成为图标的自然选择。
图标集提供商通常会将其图标编译成一个特殊的字体文件,为每个图标分配一个唯一的 Unicode 字符。这还附带一个 CSS 文件,将这些 Unicode 值映射到特定的图标类。
这种方法的优点很明显:它易于使用、可着色、可缩放,并且只需要单个请求即可加载所有图标。
但是,也存在一些缺点。预先加载大型字体文件可能会很慢,并且自定义图标集具有挑战性。此外,您可能会在字体加载之前遇到图标闪烁不可见的情况,因为没有可用的后备字体。
3. 内联 SVG:基于组件的图标
随着现代前端框架的出现,重用 HTML 元素变得容易得多。这导致了直接将 SVG 标签内联为组件的想法。
为了支持这种方法,许多图标集提供了针对每个框架定制的包装器包。例如,MDI 图标使用一个共享组件并将图标数据作为 props 传递,而 Tabler 图标则为每个图标提供一个专用组件。
由于这些是 SVG,因此它们本身是可着色的、可缩放的,并保留了 SVG 的所有功能。通常,图标会捆绑到应用程序中,从而消除额外的网络请求并确保它们与 SSR 兼容并且在首次渲染时可见。
但是,这种方法也有其缺点。它会生成大量的 SVG DOM 元素,当使用许多图标时,这会影响性能。它还会增加捆绑包的大小,并且需要为每个图标集和框架组合提供特定的集成支持,从而导致一定程度的供应商锁定。这使得切换到不同的图标集或框架具有挑战性。
尽管存在这些权衡,但这种方法在今天被广泛采用,因为对于大多数项目来说,切换图标集或框架不是经常需要的。
4. Iconify 运行时:动态 API 访问
Iconify 通过聚合 100 多个集合中的 200,000 多个图标,彻底改变了图标的使用方式。它的运行时解决方案通过 API 动态获取图标,从而无需预先捆绑即可动态访问任何图标。
这非常适合渲染来自用户提供的内容或其他您在构建时不知道的动态内容的图标。而且它非常容易设置,因为您甚至可以在没有任何构建工具的情况下将其用作 CDN。
虽然这种方法提供了很大的灵活性,但它确实有一些权衡。它引入了运行时依赖项,这意味着图标仅在加载 JavaScript 并获取图标数据后才会渲染。这种方法还对服务器端渲染 (SSR) 和缓存层(例如在渐进式 Web 应用程序 (PWA) 中使用的层)提出了挑战。
5. 按需组件图标
凭借 Iconify 的统一界面和 Vite 的按需方法,我们开发了 unplugin-icons
。此工具允许您按需导入任何图标作为组件。
作为 unplugin
,它支持所有流行的构建工具,包括 Vite、webpack 和 rspack。我们为流行的框架(如 Vue、React、Svelte 和 Solid)提供编译器。借助 Iconify,您可以在任何框架中使用任何图标,从而最大限度地减少供应商锁定。
虽然这项技术与之前的组件图标解决方案具有相同的优缺点,但与构建工具的集成允许我们提供完整的 Iconify 集合,同时仅运送您实际使用的图标。但是,运行时问题(如 DOM 元素管理)仍然存在。
6. 纯 CSS 图标
作为使用 UnoCSS 的副作用,我们发现了将图标完全嵌入 CSS 的潜力,从而带来了 纯 CSS 图标 的创新解决方案。
此方法涉及将 SVG 图标内联为数据 URL 并提供单个类来显示图标。经过一些调整,这些图标变得可着色、可缩放,甚至能够显示 SVG 动画。
浏览器可以缓存 CSS 规则,并且每个图标仅需要 一个 DOM 元素 来渲染。这种方法将图标放在一个 CSS 文件中,没有额外的请求。由于它是纯 CSS,因此图标会与您的其余 UI 一起显示,无需运行时,并且可以自然地与 SSR 一起工作——您的服务器不需要在服务器端进行任何额外的工作。
唯一的缺点是缺少对 SVG 内元素的完整自定义以及需要在构建时捆绑图标,这不是动态的。
在 Nuxt 中集成的挑战
虽然我想说 纯 CSS 图标 和 按需组件图标 对于大多数静态用途来说已经足够了,但 Nuxt 作为一个功能齐全的框架,在高效集成图标方面有更高的要求
- SSR/CSR:Nuxt 在服务器端渲染 (SSR) 和客户端渲染 (CSR) 模式下工作。我们非常关心最终用户体验,并且我们希望确保图标能够立即渲染而不会出现闪烁。
- 动态图标:在 Nuxt Content 等集成中,内容可以在运行时提供或来自外部来源,我们不知道这些来源。我们希望确保我们有能力很好地与这些情况集成。
- 性能:我们希望确保图标被有效地捆绑,并且图标的加载针对最佳性能进行了优化。
- 自定义图标:虽然 Iconify 提供了广泛的图标供选择,但我们也知道项目拥有自己的图标集或想要使用 Iconify 中不可用的付费图标是很常见的。支持自定义图标对于我们的用户至关重要。
考虑到这些要求,让我们重新审视我们之前讨论的解决方案,看看它们是如何堆叠的。
对于动态图标,Iconify 运行时是一个可行的选择。它允许动态获取图标,使其适用于在构建时未知的内容。但是,它也有其缺点。对运行时依赖项的依赖意味着它不能与 SSR 无缝集成,并且它不支持自定义图标,因为请求被定向到 Iconify 的服务器,该服务器无法访问我们的本地图标设置。
相反,纯 CSS 图标提供出色的性能和 SSR 兼容性。它们确保图标能够立即渲染而不会出现闪烁,并且能够被有效地捆绑。但是,当涉及到动态图标时,它们会不足,因为它们需要在构建时捆绑并且缺乏适应运行时内容更改的灵活性。
平衡这些权衡确实具有挑战性。那么,为什么不利用这两种方法的优势呢?通过了解这些权衡,我们可以更好地理解 Nuxt Icon v1 提供的平衡解决方案。
Nuxt Icon v1 发布:兼顾两全
凭借 Nuxt 模块系统的灵活性,Nuxt Icon 结合了两种方法的最佳方面:CSS 图标的即时渲染和 Iconify 图标的动态获取。这种双重方法提供了一种多功能、现代且可定制的图标解决方案,可以无缝地适应您项目的需求。
双重渲染模式
为了解决渲染方法中的权衡,Nuxt Icon 引入了一个多功能的 <Icon>
组件,该组件同时支持 CSS 和 SVG 模式,并且这两种模式都与 SSR 兼容。根据您的自定义需求,您可以在每个图标的这些模式之间切换。
在 CSS 模式下,图标在服务端渲染(SSR)期间被包含在 CSS 中,确保它们可以立即渲染,而不会产生任何运行时开销。在 SVG 模式下,图标在 SSR 期间以内联 HTML 的形式插入,提供相同的即时渲染优势。这两种方法都确保图标在初始屏幕上立即显示,不会有任何延迟,从而提供无缝的用户体验。
图标捆绑
动态图标带来了独特的挑战,尤其是在高效加载它们方面。为了解决这个问题,我们利用了 Iconify 的 API,它允许我们通过网络请求按需提供任何图标。然而,仅仅依赖这个 API 可能会引入延迟,特别是当服务器在地理位置上离用户很远时。
为了缓解这个问题,我们引入了图标捆绑的概念。我们可以将常用的图标直接捆绑到 客户端捆绑包
中。这确保了这些图标可以立即渲染,而无需额外的网络请求。然而,由于可能增加捆绑包的大小,捆绑所有可能的图标是不可行的。
考虑到 Nuxt 是一个全栈框架,我们可以通过引入一个 服务端捆绑包
来取得平衡。在服务器端,捆绑包大小不是一个主要问题,这允许我们包含更广泛的图标集。在 SSR 期间,这些图标可以快速获取并按需发送到客户端。这种设置确保了常用图标的高性能,同时仍然提供了从 Iconify 作为后备方案提供任何图标的灵活性。
通过结合客户端静态图标捆绑和服务端动态图标捆绑,我们在性能和灵活性之间取得了最佳平衡。
数据流
以下是一个数据流图,说明了 Nuxt Icon 如何请求图标数据
- 您使用
<Icon>
组件并提供图标的name
。 - Nuxt Icon 将首先检查该图标是否在
客户端捆绑包
或 SSR 负载中可用(在 SSR 时已知的图标将出现在负载中)。如果是,则图标将立即渲染。 - 如果图标在客户端不可用,Nuxt Icon 将从随 Nuxt 应用程序一起提供的服务器 API 中获取图标数据。在服务器端点内部,它将从
服务端捆绑包
中查询以查看该图标是否可用。 - 在此期间,会涉及到多个缓存系统。服务器端点缓存、HTTP 缓存和客户端缓存,以确保高效快速地获取图标。由于图标数据不会频繁更改,我们使用硬缓存策略来确保最佳性能。
- 当客户端和服务器都不知道图标时(动态图标),服务器端点将回退到 Iconify API 以获取图标数据。由于服务器端点已缓存,因此无论有多少客户端请求它,Iconify API 对于每个唯一的图标都只会被调用一次,以节省双方的资源。
这种分层方法确保了高效的图标交付,平衡了速度和灵活性,同时尽可能地保持动态。并平衡了每种解决方案之间的权衡。
立即试用 Nuxt Icon
Nuxt Icon v1 代表了图标渲染领域多年创新的巅峰。无论您是构建动态应用程序、静态网站还是介于两者之间的任何内容,Nuxt Icon 都能适应您的需求。
通过运行以下命令,可以轻松地将 Nuxt Icon 添加到您的项目中
npx nuxi module add icon
然后,在您的 Vue 组件中导入 <Icon>
组件,并按照 Iconify 的约定提供图标 name
<template>
<Icon name="ph:arrow-down-duotone" />
</template>
通过文档了解更多信息,尝试其功能,并让我们知道您的想法。我们很高兴看到 Nuxt Icon 如何改变您的项目!
快乐的 Nuxting ✨