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

Stimulus控制器实现可编辑区块工具箱显隐及点击交互问题

解决Contenteditable区块与工具箱的焦点冲突问题

需求概述

  • 聚焦contenteditable区块时,显示工具箱(移除d-none类)
  • 点击工具箱按钮时,为其添加Bootstrap的active类
  • 点击非工具箱/工具栏区域或完全失去焦点时,隐藏工具箱(添加d-none类)

现有问题

使用Stimulus控制器实现时,focusout事件与工具箱点击存在冲突:点击工具箱按钮时,focusout会先触发并隐藏工具箱,导致按钮的点击交互无法正常完成。

问题原因

点击工具箱按钮时,焦点会从contenteditable区块转移到按钮上,此时contentBlockTarget的focusout事件会立即触发,先将工具箱隐藏,后续的点击事件因为元素已不可见而无法正常执行。

解决方案

1. 修改Stimulus控制器逻辑

调整focusout事件的判断逻辑,确认焦点是否转移到了工具箱或工具栏内部;同时修复工具箱点击事件的无效代码(原代码添加active类后立即移除,等于无操作)。

修改后的Stimulus代码:

connect() {
  // 聚焦编辑区块时显示工具箱
  this.contentBlockTarget.addEventListener("focus", (event) => {
    this.toolboxTarget.classList.remove("d-none");
  });

  // 点击工具箱按钮时切换active类
  this.toolboxTarget.addEventListener("click", (event) => {
    this.toolboxTarget.classList.toggle("active");
    event.stopPropagation();
  });

  // 失去焦点时判断是否需要隐藏工具箱
  this.contentBlockTarget.addEventListener("focusout", (event) => {
    const relatedTarget = event.relatedTarget;
    const toolbar = document.getElementById("toolbar1");
    // 检查焦点是否落在工具箱或工具栏内部
    const isFocusInToolArea = relatedTarget === this.toolboxTarget || 
                             (toolbar && toolbar.contains(relatedTarget));
    
    if (!isFocusInToolArea) {
      this.toolboxTarget.classList.add("d-none");
      this.toolboxTarget.classList.remove("active");
    }
  });

  // 监听全局点击,处理点击页面其他区域的情况
  document.addEventListener("click", (event) => {
    const controllerRoot = this.element;
    const toolbar = document.getElementById("toolbar1");
    // 检查点击是否在控制器范围、工具箱或工具栏内
    const isClickInTargetArea = controllerRoot.contains(event.target) || 
                               event.target === this.toolboxTarget || 
                               (toolbar && toolbar.contains(event.target));
    
    if (!isClickInTargetArea) {
      this.toolboxTarget.classList.add("d-none");
      this.toolboxTarget.classList.remove("active");
    }
  });
}

2. 优化HTML结构(可选)

确保工具箱和工具栏的DOM结构被包含在Stimulus控制器的根元素内,方便后续元素范围判断:

.border.border-dark.p-4
  .position-relative.w-100{data: {controller: "content", content:{block:{value: true}}}}
    .position-absolute.bg-white{style: "right: 101%"}
      %button.d-none.btn.btn-outline-dark{type: "button", data: {content_target: "toolbox", bs:{toggle: "collapse", target: "#toolbar1"}}}
        %i.bi.bi-hammer
    
    #toolbar1.collapse.position-absolute.bottom-100.bg-white.btn-toolbar{role: "toolbar", aria:{label: "Toolbar with button groups"}}
      .btn-group.me-2{role: "group", aria:{label: "First group"}}
        %button.btn.btn-outline-dark.active{type: "button"} H2
        %button.btn.btn-outline-dark{type: "button"} H3
        %button.btn.btn-outline-dark{type: "button"} H4
        %button.btn.btn-outline-dark{type: "button"} H5
        %button.btn.btn-outline-dark{type: "button"} H6
        %button.btn.btn-outline-dark{type: "button"}
          %i.bi.bi-paragraph
      
    %p{contenteditable: true, data: {content_target: "contentBlock"}} This is some text

关键说明

  • 通过event.relatedTarget在focusout事件中判断焦点去向,避免误隐藏工具箱
  • 给工具箱点击事件添加event.stopPropagation(),防止触发全局点击事件的误判
  • 全局点击事件覆盖点击页面其他区域的场景,确保交互逻辑完整

内容的提问来源于stack exchange,提问作者Patrick Vellia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 16:30:49