如何在 TreeView 折叠时隐藏加载图标(Loading Icon)

本文介绍如何通过引入状态标志(flag)精准控制 treeview 展开/折叠时的加载图标显示逻辑,确保仅在展开子节点时显示旋转加载动画,折叠时不触发任何加载态,从而

提升用户体验与交互准确性。

在基于 jQuery 实现的 TreeView 组件中,常见做法是在点击父节点时统一显示加载图标(如 Font Awesome 的 fa-spinner fa-spin),再通过 setTimeout 延迟移除并执行 slideToggle 动画。但原始实现未区分「展开」与「折叠」状态,导致每次点击都插入 .loading 元素——这不仅造成视觉冗余,还可能引发 DOM 冗余或样式冲突。

核心解决方案:为每个可折叠分支(

  • )维护独立的状态标志(flag)
  • ,用于记录当前是否处于「已展开」状态。该 flag 必须声明在 tree.find('li').has("ul").each(...) 的迭代作用域内,确保每个分支拥有私有状态,避免相互干扰。

    以下是关键逻辑重构说明:

    状态隔离

    tree.find('li').has("ul").each(function () {
      var branch = $(this);
      let flag = false; // ✅ 每个分支独享 flag,不可提至外层!
      branch.on('click', function (e) {
        if (this === e.target) {
          flag = !flag; // 点击即切换状态
          // ...
        }
      });
    });

    条件化加载图标注入
    仅当 flag === true(即本次操作为「展开」)时插入 loading 元素;折叠时直接执行收起动画,不添加、不移除加载元素:

    if (flag) {
      $(this).append("");
      setTimeout(() => {
        $(this).find('.loading').remove();
        $(this).children().children().slideToggle(400);
      }, 400);
    } else {
      $(this).children().children().slideToggle(400); // 无 loading,直切动画
    }

    ⚠️ 注意事项

    • ❌ 错误做法:将 let flag = false 声明在 $.fn.treed 函数顶部 → 所有分支共享同一 flag,状态混乱;
    • ✅ 正确做法:let flag 必须置于 each() 回调内部,利用闭包维持各分支独立状态;
    • ? 图标类名需同步更新:icon.toggleClass(openedClass + " " + closedClass) 保证 +/- 图标随状态正确切换;
    • ? 清理时机:setTimeout 中的 .remove() 仅在展开流程中执行,折叠路径完全跳过 DOM 操作,性能更优。

    完整精简版插件片段(含注释)

    $.fn.extend({
      treed: function (options) {
        const opts = $.extend({
          openedClass: 'fa-minus-circle',
          closedClass: 'fa-plus-circle'
        }, options);
    
        return this.each(function () {
          const $tree = $(this).addClass('tree');
          $tree.find('li').has('ul').each(function () {
            const $branch = $(this);
            $branch
              .prepend(``)
              .addClass('branch');
    
            let isOpen = false; // 分支级状态标识
    
            $branch.on('click', function (e) {
              if (e.target !== this) return;
    
              isOpen = !isOpen;
              const $icon = $branch.children('i:first');
              $icon.toggleClass(`${opts.openedClass} ${opts.closedClass}`);
    
              if (isOpen) {
                // 展开:显示 loading → 延迟移除 → 执行展开动画
                $branch.append('');
                setTimeout(() => {
                  $branch.find('.loading').remove();
                  $branch.children('ul').slideDown(400);
                }, 400);
              } else {
                // 折叠:直接收起,不触发 loading
                $branch.children('ul').slideUp(400);
              }
            });
    
            // 初始化:默认收起子列表
            $branch.children('ul').hide();
          });
        });
      }
    });

    最后,初始化时可传入自定义图标类:

    $('#tree2').treed({
      openedClass: 'fa-folder-open',
      closedClass: 'fa-folder'
    });

    通过该方案,TreeView 的加载反馈变得语义清晰、行为可控——用户能明确感知“正在加载新内容”仅发生在展开动作中,折叠则即时响应,无任何视觉延迟或干扰,符合现代 Web 交互设计最佳实践。