Ribbon 与 StatusBar API
本文详细介绍如何为插件添加 Ribbon 侧边栏图标和 StatusBar 状态栏元素。
Ribbon 侧边栏图标
Ribbon 是 Obsidian 左侧的垂直按钮栏,用于快速触发插件功能。
基本用法
typescript
import { Plugin, addIcon } from 'obsidian';
export default class MyPlugin extends Plugin {
async onload() {
// 创建 Ribbon 图标
const ribbonIcon = this.addRibbonIcon(
'dice', // 图标名称(lucide 图标集)
'My Plugin', // 悬停提示文字
(evt: MouseEvent) => {
// 点击回调
new Notice('Ribbon clicked!');
}
);
// 可以给元素添加样式
ribbonIcon.addClass('my-plugin-ribbon-class');
}
}使用自定义 SVG 图标
typescript
import { addIcon } from 'obsidian';
// 注册自定义图标
addIcon('my-custom-icon', `<svg viewBox="0 0 100 100" ...>
<!-- 你的 SVG 路径 -->
</svg>`);
// 使用自定义图标
this.addRibbonIcon('my-custom-icon', 'Custom Action', () => {
// 处理点击
});常用 Lucide 图标
| 图标名 | 视觉效果 | 适用场景 |
|---|---|---|
dice | 🎲 | 随机/通用功能 |
bookmark | 🔖 | 书签/收藏 |
calendar | 📅 | 日历/日程 |
search | 🔍 | 搜索/查找 |
settings | ⚙️ | 设置/配置 |
file-plus | 📄+ | 新建文件 |
folder-plus | 📁+ | 新建文件夹 |
refresh-cw | 🔄 | 同步/刷新 |
download | ⬇️ | 下载/导入 |
upload | ⬆️ | 上传/导出 |
eye | 👁️ | 预览/查看 |
edit | ✏️ | 编辑 |
trash | 🗑️ | 删除 |
link | 🔗 | 链接 |
image | 🖼️ | 图片 |
code | 💻 | 代码 |
terminal | >_ | 终端/命令 |
clock | 🕐 | 时间/定时 |
star | ⭐ | 收藏/星标 |
heart | ❤️ | 喜欢/关注 |
完整图标列表请参阅 Lucide Icons。
条件显示与隐藏
typescript
export default class MyPlugin extends Plugin {
private ribbonIcon: HTMLElement | null = null;
async onload() {
// 始终创建,通过 CSS 控制显示
this.ribbonIcon = this.addRibbonIcon('clock', 'Timer', () => {
this.toggleTimer();
});
// 根据设置决定初始可见性
this.updateRibbonVisibility();
}
private updateRibbonVisibility() {
if (this.ribbonIcon) {
this.ribbonIcon.style.display =
this.settings.showTimer ? '' : 'none';
}
}
private toggleTimer() {
this.settings.showTimer = !this.settings.showTimer;
this.updateRibbonVisibility();
this.saveSettings();
}
}Ribbon 图标动态更新
typescript
export default class MyPlugin extends Plugin {
private ribbonIcon: HTMLElement | null = null;
private timerRunning = false;
async onload() {
this.ribbonIcon = this.addRibbonIcon(
'play',
'Toggle Timer',
() => this.toggleTimer()
);
}
private toggleTimer() {
this.timerRunning = !this.timerRunning;
// 动态更换图标
const iconEl = this.ribbonIcon?.querySelector('svg');
if (iconEl) {
// 方法 1:切换 CSS 类
iconEl.classList.toggle('is-active', this.timerRunning);
}
// 方法 2:更换整个图标(需要重新创建)
// 注意:addRibbonIcon 不支持直接更换图标,需要通过 CSS 变通
}
}css
/* styles.css - 图标状态样式 */
.my-plugin-ribbon-class.is-active {
color: var(--text-accent);
}
.my-plugin-ribbon-class.is-active svg {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}StatusBar 状态栏
StatusBar 位于 Obsidian 窗口底部右侧,用于显示持续性的状态信息。
基本用法
typescript
export default class MyPlugin extends Plugin {
private statusBarItem: HTMLElement;
async onload() {
// 创建状态栏项
this.statusBarItem = this.addStatusBarItem();
// 设置文字
this.statusBarItem.setText('Ready');
// 添加点击事件
this.statusBarItem.addEventListener('click', () => {
new Notice('StatusBar clicked!');
});
// 添加 CSS 类
this.statusBarItem.addClass('my-plugin-status');
}
}状态栏完整示例
typescript
export default class WordCounterPlugin extends Plugin {
private statusBarItem: HTMLElement;
async onload() {
this.statusBarItem = this.addStatusBarItem();
this.statusBarItem.addClass('word-count-status');
// 监听编辑器变化
this.registerEvent(
this.app.workspace.on('active-leaf-change', () => {
this.updateWordCount();
})
);
this.registerEvent(
this.app.workspace.on('editor-change', () => {
this.updateWordCount();
})
);
// 初始更新
this.updateWordCount();
}
private updateWordCount() {
const editor = this.app.workspace.activeEditor?.editor;
if (!editor) {
this.statusBarItem.setText('0 words');
return;
}
const text = editor.getValue();
const wordCount = text.split(/\s+/).filter(w => w.length > 0).length;
const charCount = text.length;
// 支持 HTML 内容(富文本显示)
this.statusBarItem.setText(
`${wordCount} words · ${charCount} chars`
);
}
}StatusBar 富文本显示
typescript
// 使用 createEl 构建富文本状态栏
private setupRichStatusBar() {
this.statusBarItem = this.addStatusBarItem();
this.statusBarItem.addClass('rich-status');
// 创建多个子元素
const container = this.statusBarItem.createDiv('status-container');
const wordIcon = container.createSpan('status-icon');
wordIcon.innerHTML = '📝'; // 或使用 SVG
const wordCount = container.createSpan('status-value');
wordCount.setText('0');
const separator = container.createSpan('status-separator');
separator.setText('·');
const charIcon = container.createSpan('status-icon');
charIcon.innerHTML = '📄';
const charCount = container.createSpan('status-value');
charCount.setText('0');
}状态指示器模式
typescript
// 不同状态显示不同样式
class SyncStatusBar {
private statusBar: HTMLElement;
private state: 'idle' | 'syncing' | 'error' | 'success' = 'idle';
constructor(plugin: Plugin) {
this.statusBar = plugin.addStatusBarItem();
this.render();
}
setState(state: typeof this.state) {
this.state = state;
this.render();
}
private render() {
// 清除之前的状态类
this.statusBar.removeClass(
'status-idle', 'status-syncing', 'status-error', 'status-success'
);
switch (this.state) {
case 'idle':
this.statusBar.addClass('status-idle');
this.statusBar.setText('📴 Idle');
break;
case 'syncing':
this.statusBar.addClass('status-syncing');
this.statusBar.setText('🔄 Syncing...');
break;
case 'error':
this.statusBar.addClass('status-error');
this.statusBar.setText('❌ Sync failed');
break;
case 'success':
this.statusBar.addClass('status-success');
this.statusBar.setText('✅ Synced');
// 3 秒后回到 idle
setTimeout(() => this.setState('idle'), 3000);
break;
}
}
}多个状态栏项管理
typescript
export default class MyPlugin extends Plugin {
private statusItems: Map<string, HTMLElement> = new Map();
private addStatusItem(id: string): HTMLElement {
const item = this.addStatusBarItem();
this.statusItems.set(id, item);
return item;
}
async onload() {
// 创建多个状态栏项
this.addStatusItem('sync').setText('📴 Ready');
this.addStatusItem('cursor').setText('1:1');
this.addStatusItem('file-size').setText('0 KB');
// 监听更新
this.registerEvent(
this.app.workspace.on('editor-change', () => {
this.updateCursorPosition();
this.updateFileSize();
})
);
}
private updateCursorPosition() {
const editor = this.app.workspace.activeEditor?.editor;
if (editor) {
const cursor = editor.getCursor();
const item = this.statusItems.get('cursor');
item?.setText(`${cursor.line + 1}:${cursor.ch + 1}`);
}
}
private updateFileSize() {
const file = this.app.workspace.getActiveFile();
if (file) {
const size = file.stat.size;
const formatted = size > 1024
? `${(size / 1024).toFixed(1)} KB`
: `${size} B`;
const item = this.statusItems.get('file-size');
item?.setText(formatted);
}
}
}移动端适配
Ribbon 移动端差异
typescript
import { Platform } from 'obsidian';
async onload() {
if (Platform.isMobile) {
// 移动端 Ribbon 空间有限
// 考虑只在桌面端添加 Ribbon 图标
} else {
this.addRibbonIcon('star', 'Favorite', callback);
}
}StatusBar 移动端适配
typescript
private updateStatusForPlatform() {
if (Platform.isMobile) {
// 移动端:精简显示,减少信息密度
this.statusBarItem.setText(`${wordCount}w`);
this.statusBarItem.style.fontSize = '11px';
this.statusBarItem.style.padding = '0 4px';
} else {
// 桌面端:完整显示
this.statusBarItem.setText(`${wordCount} words · ${charCount} chars`);
this.statusBarItem.style.fontSize = 'var(--font-ui-small)';
}
}样式定制
Ribbon 图标样式
css
/* 自定义 Ribbon 按钮样式 */
.my-plugin-ribbon-class {
opacity: 0.7;
transition: opacity 0.2s, color 0.2s;
}
.my-plugin-ribbon-class:hover {
opacity: 1;
color: var(--text-accent);
}
.my-plugin-ribbon-class:active {
transform: scale(0.95);
}StatusBar 样式
css
/* 自定义状态栏样式 */
.my-plugin-status {
cursor: pointer;
padding: 0 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.my-plugin-status:hover {
background-color: var(--background-modifier-hover);
}常见问题
Q: 如何动态更新 StatusBar 文字?
A: 使用 statusBarItem.setText() 方法,它可以直接替换内容:
typescript
this.statusBarItem.setText(`Updated: ${new Date().toLocaleTimeString()}`);Q: addRibbonIcon 能更换图标吗?
A: 不能直接更换。推荐通过 CSS 类控制不同状态的样式,或为不同状态创建不同的逻辑展示。
Q: StatusBar 能显示在左侧吗?
A: 原生 API 将 StatusBar 固定在最右侧。如需左侧显示,可以在 onload 时操作 DOM 调整顺序。
Q: 移动端 StatusBar 被截断怎么办?
A: 移动端屏幕较窄,建议:
- 使用缩写代替完整文字
- 减小内边距
- 使用
font-size缩放