在主题中用自定义图标替换 Discourse 的默认 SVG 图标

您可以单独或整体使用自己的自定义 SVG 替换 Discourse 的默认 SVG 图标,并在主题或主题组件中覆盖它们。

第 1 步 - 创建 SVG 雪碧图

首先,您需要创建一个 SVG 雪碧图(Spritesheet)。它可以包含从单个额外的自定义 SVG 图标到数百个图标的完整替换集。

雪碧图应保存为 SVG 文件。原则上,您需要将原始 SVG 图标文件中 <svg> 标签的内容嵌套到 <symbol> 标签中,并为其指定一个清晰的标识符。

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="my-theme-icon-1">
    <!--
      源 SVG 图标文件中 <svg> 标签内的代码
      这通常是 <svg> 标签之间的所有内容
      (但不包括 SVG 标签本身,它由上面的 <symbol> 替换)
      您可以将任何属性(例如 ViewBox="0 0 0 0")转移到 <symbol> 标签上
      -->
  </symbol>

  <symbol id="my-theme-icon-2">
    <!-- 在此处添加 SVG 代码。根据需要添加更多 <symbol> 块。
      -->
  </symbol>
</svg>
  • 请务必为雪碧图中的每个 symbol 添加自定义 ID。为了便于管理,建议将您的主题名称(例如 my-theme-icon)作为 ID 的前缀。

  • 要使图标颜色像现有图标一样动态变化,请将 fill 设置为 currentColor,而不是硬编码的颜色(如 #333)。

  • 要缩放或正确居中您的图标,请在 <symbol> 标签上使用 viewBox 属性。更多信息请参阅 How to Scale SVG | CSS-Tricks 。

  • 请注意 SVG 内部的样式冲突。例如,SVG 通常会定义内联样式,如 .st0{fill:#FF0000;}。如果您有多个 SVG 使用相同的类,这可能会导致问题(要解决这些问题,请编辑类名使其对每个图标唯一)。

  • 如果您有很多图标,有自动化的方法。 https://www.npmjs.com/package/svg-sprite-generator 是一个简单的命令行工具,用于将 SVG 合并到雪碧图中。

示例 - 单个自定义图标雪碧图

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="bat-icon" viewBox="6 6 36 36">
    <path
      fill="currentColor"
      d="M24,18.2c0.7,0,0.9,0.2,0.9,0.2l0.4-1.7c0,0,0.4,1.5,0.4,2.8c0.2,1.1,2.2,0.4,3.9,0C31.4,19.1,32,16,32,16h16c0,0-9.4,3.5-7,10c0,0-14.8-2-17,7l0,0c-2.2-9-17-7-17-7c2.4-6.5-7-10-7-10h16c0,0,0.6,3.1,2.3,3.5c1.7,0.4,3.9,1.1,3.9,0c0.2-1.1,0.4-2.8,0.4-2.8l0.4,1.7C23.1,18.4,23.4,18.2,24,18.2L24,18.2L24,18.2z"
    />
  </symbol>
</svg>

第 2 步 - 将雪碧图添加到您的主题

构建好雪碧图后,您需要将 SVG 文件添加到您的组件/主题中。通过 UI 操作很简单,您也可以将其硬编码到组件/主题中。

:information_source: 一旦上传到任何已安装的组件/主题中,您就可以在整个实例中使用 <symbol> 标签中的 ID 来访问它。

通过 UI

转到主题/组件设置的“上传”部分,添加您的雪碧图文件,并将 SCSS 变量名称设为 icons-sprite:

硬编码到主题 / 组件中

将雪碧图文件添加到主题的 /assets 文件夹中。然后更新根目录中的 assets.json 文件。
对于一个名为 my-icons.svg 的 SVG 雪碧图,您的 about.json 应包含以下内容:

"assets": {
  "icons-sprite": "/assets/my-icons.svg"
}

第 3 步(可选) - 覆盖默认图标

现在雪碧图已设置好,您可以告诉 Discourse 替换图标。这是从 api-initializer 中执行此操作的方法:

// {theme}/javascripts/discourse/api-initializers/init-theme.gjs

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  api.replaceIcon("bars", "my-theme-icon-bars");
  api.replaceIcon("link", "my-theme-icon-link");
  // 等等。
});

第一个 ID bars 是 Discourse 中的默认图标 ID,第二个是您的替换图标的 ID。查找我们其中一个图标 ID 的最简单方法是在浏览器中检查该图标。

在这里,图标名称遵循 d-icon- 前缀。因此,在这个例子中它是 d-unliked

我们的大多数图标遵循 https://fontawesome.com/ 的图标名称,但有一些例外(这就是为什么在检查器中检查 ID 是最可靠的方法)。您可以在 github 上的这里 的 const REPLACEMENTS 块中看到所有例外。

就这样。现在您可以使用自己的自定义图标来美化 Discourse 了!


本文档受版本控制 - 请在 github 上建议更改。

58 个赞

如何定位特定元素中的特定图标?在我的例子中,我想用另一个 FA 图标替换侧边栏菜单中的文档图标。

1 个赞

我将使用 CSS 隐藏它,并为其添加一个新按钮。

Common / CSS

.sidebar-section-wrapper {
  li[data-list-item-name=docs] {
    display: none !important;
  }
}

在 More > Customize this section 中添加一个新按钮

4 个赞

这不起作用。我尝试了:

<script type="text/discourse-plugin" version="0.8">
    api.replaceIcon("shield-halved", "hat-wizard");
</script>

来自这里,但似乎不起作用。我认为脚本标签方法已损坏,因为这个在预览链接中不起作用。老实说,我不确定。

1 个赞

对我来说是有效的 :woman_shrugging:t2:

你是否将其放入了 head 标签?我也替换了我 header 中的 robot:

你可能需要将图标添加到 admin 的 SVG icon subset 设置中。

2 个赞

是的,head 标签。还有 header 标签,因为指南是这么说的。

完成了。现在可以用了。谢谢!

1 个赞

@NateDhaliwal 请私信我好吗?我需要一些帮助,但在您的个人资料中没有看到聊天选项。谢谢!

1 个赞
/* 对于左侧菜单,我们重新定义类别 Audi */ 中的前缀 span 类元素的背景 */
.navigation-category [data-category-id="6"] .prefix-span {
  background: url("https://raw.githubusercontent.com/tima4502/car-icons/bb0d0fae3e5b66c512a27a130b219ec0ee342ada/audi.svg") center/contain no-repeat !important;

当我点击主页时,方形图标又出现了!你能告诉我我哪里做错了什么吗?在类别页面本身上,它可以正常工作。

你好,有人能澄清主题/组件名称、文件名、SCSS 变量名和符号 ID 之间的关系吗?

我正试图用我们自己的图标替换版主 shield-halved 图标,但说明有点不清楚。

在第 2 步:

  • “通过 UI” 截图显示的文件名是 baticonsprite.svg,SCSS 变量名是 icons-sprite
  • 但在“硬编码到主题”中,它会告诉你将其硬编码到主题/组件中
    • 但如何操作?我在编辑器中看不到 assets.json 文件。如果我导出组件,我会看到一个 about.json,其中显示了我通过 UI 上传的 sprite
    • 但这个例子也显示了一个不同的文件名 /assets/my-icons.svg — 这是指与 baticonsprite.svg 相同的文件吗?
    • 这两种方法是做同一件事的替代方法,你只需要做其中一种或另一种,而不是两者都做……?

在第 3 步:

  • 但现在,在 api.replaceIcon() 中,第二个参数没有使用任何前面的 ID,不是 icons-sprite 或 bat-icon 或 baticonsprite.svg 或 my-icons.svg。相反,我们得到了一个全新的 my-theme-icon-bars……感到困惑。
    • my-theme 前缀是必需的吗?如果是,那么“主题名称”字符串来自哪里?比如它应该是 my-theme-bat-icon 吗?如果它是一个组件而不是一个主题呢?
    • 对于 icon-bars 部分,它应该是:
      • SVG 精灵表 XML 中的符号 ID
      • SVG 文件的文件名
      • 你给它的 SCSS 变量名
      • 以上某个组合(例如 icons-sprite-bat-icon?)

你实际在哪里放置 api.replaceIcon() 调用?将其放入自定义组件的“JS”选项卡中是否可以,该选项卡已包含样板代码:

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
   // your code here
});

或者是否有必要创建一个自定义 <script type=”discourse/plugin”> 标签并将其放在 <head> 选项卡中?


抱歉我的困惑。

我尝试了上述几种组合,但无论如何都无法显示我的 sprite……

我的 sprite XML 看起来像:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">


<symbol id="my-logo" viewBox="0 0 94.652 95.261"><defs><linearGradient id="a" y1="47.631" x2="94.652" y2="47.631" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff593d"/><stop offset="1" stop-color="#ff7751"/></linearGradient></defs><title>d_only</title><path d="M47.326,0H0V95.261H47.326c23.67,0,47.326-21.326,47.326-47.624S71,0,47.326,0Zm0,69.274a21.644,21.644,0,1,1,21.65-21.637A21.635,21.635,0,0,1,47.326,69.274Z" fill="url(#a)"/></symbol>

</svg>

文件名是 my-logo.svg,SCSS 变量名也是 my-logo

在自定义组件的“JS”选项卡中,我有:

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
    api.replaceIcon("shield-halved", "my-logo")
});

但似乎什么都没有显示。我是否遗漏了某个步骤,或者是我误解了某种魔术字符串插值……?

1 个赞

有人能弄明白这个问题吗?我还在为此苦恼……

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
    api.replaceIcon("shield-halved", "my-logo")
});

不确定第二个 ID(“my-logo”)是从哪里来的,如果你是通过用户界面上传文件的话:

$my-logo、 $test、my-logo.svg、SVG 在磁盘上的绝对 URL 等都不起作用。盾牌确实被替换了,但被替换成了空。SVG 只是变成了一个空的 \u003cuse href=”#my-logo”\u003e,里面没有任何内容。

1 个赞

终于搞明白了(感谢 Claude)!

所以:

  1. SVG 文件名无关紧要。

  2. 在 SVG 精灵图中,决定最终图标名称的是 symbol 的 id,例如 <symbol id=”my-logo” …>

  3. 但是 SCSS 变量名必须是完全相同的 icons-sprite,与最终的精灵表 ID 没有任何关系:

  4. 上传后,它看起来会像这样:


    (请注意,精灵表/变量名在任何地方都找不到。它必须是完全的 $icons-sprite,$ 会自动添加,而不是你在上一步输入的内容的一部分)

  5. 然后,最后,在主题的“JS”选项卡中(而不是 <head>),你可以在那里使用步骤 1 中的精灵表 ID:

    import { apiInitializer } from “discourse/lib/api”;
    
    export default apiInitializer((api) => {
    api.replaceIcon(“shield-halved”, “my-logo”)
    });
    

因此,SCSS 变量名必须是完全的 icons-sprite,文件名无关紧要,API 图标名称由精灵表内的 symbol ID 决定。

3 个赞