Trix工具栏自定义元素重复渲染问题(疑似事件监听异常)
Turbo导航下Trix自定义按钮重复渲染问题解决
问题根源
- Turbo的页面缓存会保留前一次页面的DOM结构,当导航返回时,Stimulus控制器会重新执行
connect方法 - 之前使用全局
addEventListener,即使添加了{once: true},每次控制器实例化都会新增一个事件监听,导致trix-initialize触发时多个监听同时执行,重复插入按钮 - 没有做按钮存在校验,每次触发事件都直接插入新按钮
解决方法
方法1:修改现有控制器(保持两个独立控制器)
针对definition和example控制器分别修改,核心是绑定元素级事件监听+添加按钮存在校验:
trix_definition_controller.js
import { Controller } from "@hotwired/stimulus"; import Trix from "trix"; export default class extends Controller { connect() { // 全局块属性只初始化一次 if (!Trix.config.blockAttributes.definition) { Trix.config.blockAttributes.definition = { tagName: "trix-definition", terminal: true, breakOnReturn: true, nestable: false, group: false } } // 绑定到当前编辑器元素的trix-initialize事件,而非全局 this.element.addEventListener("trix-initialize", event => { const toolbarGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools"); // 检查按钮是否已存在,避免重复插入 if (!toolbarGroup.querySelector('[data-trix-attribute="definition"]')) { const definitionButton = '<button type="button" class="trix-button" data-trix-attribute="definition">Definition</button>' toolbarGroup.insertAdjacentHTML("beforeend", definitionButton) } }, { once: true }) } }
trix_example_controller.js
import { Controller } from "@hotwired/stimulus"; import Trix from "trix"; export default class extends Controller { connect() { if (!Trix.config.blockAttributes.example) { Trix.config.blockAttributes.example = { tagName: "trix-example", terminal: true, breakOnReturn: true, nestable: false, group: false } } this.element.addEventListener("trix-initialize", event => { const toolbarGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools"); if (!toolbarGroup.querySelector('[data-trix-attribute="example"]')) { const exampleButton = '<button type="button" class="trix-button" data-trix-attribute="example">Example</button>' toolbarGroup.insertAdjacentHTML("beforeend", exampleButton) } }, { once: true }) } }
方法2:合并控制器(优化代码复用)
将两个按钮的逻辑合并到一个控制器中,减少重复代码:
trix_custom_blocks_controller.js
import { Controller } from "@hotwired/stimulus"; import Trix from "trix"; export default class extends Controller { connect() { // 定义所有自定义块的配置 const customBlocks = { definition: { tagName: "trix-definition", terminal: true, breakOnReturn: true, nestable: false, group: false, buttonLabel: "Definition" }, example: { tagName: "trix-example", terminal: true, breakOnReturn: true, nestable: false, group: false, buttonLabel: "Example" } } // 初始化全局块属性,确保只执行一次 Object.entries(customBlocks).forEach(([key, config]) => { if (!Trix.config.blockAttributes[key]) { const { buttonLabel, ...blockConfig } = config; Trix.config.blockAttributes[key] = blockConfig; } }) // 绑定编辑器初始化事件 this.element.addEventListener("trix-initialize", event => { const toolbarGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools"); // 遍历添加按钮,先检查是否已存在 Object.entries(customBlocks).forEach(([key, config]) => { if (!toolbarGroup.querySelector(`[data-trix-attribute="${key}"]`)) { const button = `<button type="button" class="trix-button" data-trix-attribute="${key}">${config.buttonLabel}</button>`; toolbarGroup.insertAdjacentHTML("beforeend", button); } }) }, { once: true }) } }
使用时,只需将这个控制器绑定到Trix编辑器元素即可,无需再使用两个独立控制器。
核心优化点
- 放弃全局事件监听,改为绑定到当前控制器对应的Trix编辑器元素,确保事件监听与控制器生命周期绑定
- 插入按钮前先检查DOM中是否已存在对应按钮,从根源避免重复渲染
- 全局的Trix块属性配置只初始化一次,提升代码执行效率
内容的提问来源于stack exchange,提问作者Darksi
相关产品推荐
相关产品推荐

