You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Trix工具栏自定义元素重复渲染问题(疑似事件监听异常)

Turbo导航下Trix自定义按钮重复渲染问题解决

问题根源

  1. Turbo的页面缓存会保留前一次页面的DOM结构,当导航返回时,Stimulus控制器会重新执行connect方法
  2. 之前使用全局addEventListener,即使添加了{once: true},每次控制器实例化都会新增一个事件监听,导致trix-initialize触发时多个监听同时执行,重复插入按钮
  3. 没有做按钮存在校验,每次触发事件都直接插入新按钮

解决方法

方法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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:05:23