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

You can replace a Discourse’s default SVG icons individually or as a whole with your own custom SVG and override them within a theme or theme component.

Step 1 - Create an SVG Spritesheet

To get started, you must create an SVG Spritesheet. This can contain anything from a single additional custom SVG icon up to an entire replacement set of hundreds.

The spritesheet should be saved as an SVG file. In principle, you are nesting the <svg> tag contents from the original SVG icon file into <symbol> tags and giving them a nice identifier.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="my-theme-icon-1">
    <!--
      Code inside the <svg> tag from the source SVG icon file
      this is typically everything between the <svg> tags
      (but not the SVG tag itself, that's replaced by <symbol> above)
      You can transfer any attributes (i.e. ViewBox="0 0 0 0") to the <symbol> tag
      -->
  </symbol>

  <symbol id="my-theme-icon-2">
    <!-- SVG code here. Add more <symbol> blocks as needed.
      -->
  </symbol>
</svg>
  • Be sure to add a custom ID to each symbol in the spritesheet. It’s probably helpful for your sanity to prefix your IDs with your theme name my-theme-icon.

  • To have the icon color to be dynamic like the existing icons, set the fill to currentColor rather than a hardcoded color (like #333)

  • To scale or correctly centre your icon, utilise a viewBox attribute on the <symbol> tag. See How to Scale SVG | CSS-Tricks for more information.

  • Be on the lookout for style collisions within your SVGs. For example, SVGs will often have an inline style like .st0{fill:#FF0000;} defined. If you have multiple SVGs using the same classes this can cause issues (to fix these issues, edit the classes to be unique to each icon).

  • If you have many icons, there are ways to automate this. svg-sprite-generator - npm is a simple command line tool for combining SVGs into a spritesheet.

Example - single custom icon spritesheet

<?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>

Step 2 - Add the spritesheet to your theme

Once your spritesheet is built, you need to add the SVG file to your component/theme. This is easy via the UI, or you can hard code it into a component/theme.

:information_source: Once it is uploaded to any installed component/theme, it is available throughout your instance using the ID in the <symbol> tag.

Via the UI

Go to the Uploads section of the theme/component settings and add your sprite file with a SCSS var name of icons-sprite:

Hardcode into a Theme / Component

Add the spritesheet file to the Theme’s /assets folder. Then update your assets.json file in the root folder.
For an SVG sprite called my-icons.svg, your about.json should include this:

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

Step 3 (optional) - Overriding default icons

Now that your spritesheet is set, you can tell Discourse to replace icons. This is how you do it from an 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");
  // etc.
});

The first ID, bars, is the default icon ID in Discourse and the second is the ID of your replacement icon. The easiest way to find an ID of one of our icons is to inspect the icon in your browser.

Here the icon name follows the d-icon- prefix. So in this example it’s d-unliked

Most of our icons follow the icon names from https://fontawesome.com/, but there are exceptions (which is why checking the ID in your inspector is the most reliable method). You can see all the exceptions in the const REPLACEMENTS block here on github.

That’s it. You can now style Discourse with your own custom icons!


This document is version controlled - suggest changes on github.

57 个赞

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

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

Common / CSS

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

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

3 个赞

这不起作用。我尝试了:

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

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

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

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

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

1 个赞

是的,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-spritebat-iconbaticonsprite.svgmy-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")
});

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