优化鼠标悬浮列表项显示更多操作按钮,解决点击后按钮消失的问题

573 阅读5分钟

需实现的功能

目前的更多操作按钮的显示逻辑,为用户点击对应列表项后(isActive 为 true 时),才显示更多操作按钮。

需要添加,当用户的鼠标悬浮到侧边栏列表的每个列表项上时,也显示更多操作的按钮(一般是以三个点的图标表示),点击更多操作按钮显示展开操作的列表。

项目技术栈和组件库

  • Next.js
  • TailwindCSS
  • Shadcn ui 的 DropdownMenu 相关组件 和 Button 组件
  • cn 自定义函数,用来优化类名

实现思路

  1. 给更多操作按钮的父容器添加 类名 group/side
  2. 给调用更多操作按钮的地方,添加 父容器 hover 时的相关样式,未 hover 时 更多操作按钮的透明度为 0,父容器 hover 时(group:hover/side),透明度为 1
  3. 利用 cn 函数 加上 isActive 时的判断,isActivetrue 时,透明度一样为 1

修改前的代码:

<div className="relative mr-3 h-8">
  {/*  MoreOperationsDropdown component */}
  <div className="absolute -right-2 top-0.5">{children}</div>
</div>

修改后的代码:

<div className="group/side relative mr-3 h-8">
  {/*  MoreOperationsDropdown component */}
  <div
    className={cn(
      "absolute -right-2 top-0.5 opacity-0 group-hover/side:opacity-100",
      isActive && "opacity-100",
    )}
  >
    {children}
  </div>
</div>

遇到的问题

按照实现思路中的代码进行修改后,发现当鼠标 hover 到列表的对应列表项上时,点击更多操作按钮显示操作列表后,hover 显示的更多操作按钮会 “消失”。

问题排查

尝试修改更多操作按钮的未 hover 时的透明度为 0.5,发现点击更多操作按钮后且显示操作菜单后,按钮显示的透明度样式从 hover 后的 1,变为 未 hover 时的样式,也就是透明度为 0.5

思考后,觉得是展开菜单的问题,找到对应代码,把 DropdownConent 相关的代码进行注释,再次操作,操作菜单没有显示,发现按钮的透明度样式没有变化,还是 hover 后的样式。

将上面的注释打开后,打开网页开发者工具,查看更多操作按钮元素,观察显示操作菜单前后,更多操作按钮元素有无变化。

展开操作列表前:

<button
        class="ring-offset-background focus-visible:ring-ring hover:bg-accent hover:text-accent-foreground x z-50 inline-flex h-full cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium opacity-0 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-hover/side:opacity-100 peer-[[data-state=open]]:opacity-100"
        type="button"
        id="radix-:r37:"
        aria-haspopup="menu"
        aria-expanded="false"
        data-state="closed"
        >
    <div class="flex items-center gap-0.5">
        <span class="bg-foreground h-1 w-1 rounded-full"></span>
        <span class="bg-foreground h-1 w-1 rounded-full"></span>
        <span class="bg-foreground h-1 w-1 rounded-full"></span>
    </div>
</button>

展开操作列表后:

<button
        class="ring-offset-background focus-visible:ring-ring hover:bg-accent hover:text-accent-foreground x z-50 inline-flex h-full cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium opacity-0 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-hover/side:opacity-100 peer-[[data-state=open]]:opacity-100"
        type="button"
        id="radix-:r37:"
        aria-haspopup="menu"
        aria-expanded="true"
        data-state="open"
        aria-controls="radix-:r38:"
        >
    <div class="flex items-center gap-0.5">
        <span class="bg-foreground h-1 w-1 rounded-full"></span>
        <span class="bg-foreground h-1 w-1 rounded-full"></span>
        <span class="bg-foreground h-1 w-1 rounded-full"></span>
    </div>
</button>

发现操作按钮上有三个属性,在展开列表后,发生了变化。

  1. aria-expanded 属性:

    • 第一个按钮的 aria-expanded 属性值为 false,表示菜单当前是关闭状态。
    • 第二个按钮的 aria-expanded 属性值为 true,表示菜单当前是打开状态。
  2. data-state 属性:

    • 第一个按钮的 data-state 属性值为 closed,表示按钮处于关闭状态。
    • 第二个按钮的 data-state 属性值为 open,表示按钮处于打开状态。
  3. aria-controls 属性:

    • 第一个按钮没有 aria-controls 属性。
    • 第二个按钮有 aria-controls="radix-:r38:" 属性,指向一个ID为 radix-:r38: 的元素,通常是与按钮关联的菜单或弹出框。

这些属性的不同主要用于辅助技术(如屏幕阅读器)和JavaScript,以便正确地识别和处理按钮的状态变化。具体来说:

  • aria-expanded: 用于指示按钮控制的内容(如菜单)是否可见。
  • data-state: 通常用于JavaScript逻辑,以便根据按钮的状态执行不同的操作。
  • aria-controls: 用于关联按钮和它控制的元素,增强可访问性。

解决办法

可以根据属性的值,设置指定属性的指定值时的样式。

用到了 TailwindCSS 中的样式写法,设置 当 aria-expanded 属性的值为 true 时,透明度也为 1,这样操作菜单展开后,更多操作按钮依旧会显示。

这边需要把对更多按钮显示的样式逻辑,直接写在了更多操作按钮上,而不是在更多操作菜单组件。

因为发现,写在引用处时,属性值设置样式不生效,因为这个属性只在更多操作按钮上,所以直接把显示逻辑写在了更多操作按钮上。对应的把 isActive 的值的获取 也放到了更多操作按钮组件的代码中。

<DropdownMenu>
    <DropdownMenuTrigger asChild disabled={isDisabled}>
        <Button
            className={cn(
                "z-50 h-full cursor-pointer py-2 opacity-0 
                group-hover/side:opacity-100 aria-expanded:opacity-100",
                isActive && "opacity-100",
            )}
            >
            <div className="flex items-center gap-0.5">
                <span className="h-1 w-1 rounded-full bg-foreground" />
                <span className="h-1 w-1 rounded-full bg-foreground" />
                <span className="h-1 w-1 rounded-full bg-foreground" />
            </div>
        </Button>
    </DropdownMenuTrigger>
    <DropdownMenuContent>
        <DropdownMenuItem>
            操作1
        </DropdownMenuItem>
        <DropdownMenuItem>
            操作2
        </DropdownMenuItem>
        <DropdownMenuItem>
            操作3
        </DropdownMenuItem>
    </DropdownMenuContent>
</DropdownMenu>

功能测试

作为用户进行使用,发现进行完一些操作后,页面刷新时,列表中的每个列表项会在一瞬间都显示更多操作按钮图标,但看起来样式不是 hover 前也不是 hover 后的。

猜测是因为页面刷新时,更多操作按钮被禁用,显示的是禁用状态时的样式。

再给更多操作按钮添加禁用状态时的样式 disabled:opacity-0,设置透明度为 0,最终解决了这个问题。

<Button
    className={cn(
                "z-50 h-full cursor-pointer py-2 opacity-0 
                group-hover/side:opacity-100 aria-expanded:opacity-100 disabled:opacity-0", 
                isActive && "opacity-100",
    )}
>
    <div className="flex items-center gap-0.5">
        <span className="h-1 w-1 rounded-full bg-foreground" />
        <span className="h-1 w-1 rounded-full bg-foreground" />
        <span className="h-1 w-1 rounded-full bg-foreground" />
    </div>
</Button>

鼠标点击更多操作按钮后,再点击其他地方关闭操作菜单,按钮的周围会显示一圈边框,觉得应该是 focus 的问题,查询了 Shadcn ui 的 DropdownMenu 组件的文档,发现 DropdownConent 有 几个 focus 相关的属性,感觉应该是 onCloseAutoFocus 属性,经过尝试,确实是 onCloseAutoFocus 属性,把该属性的方法的默认行为进行了禁用,这样点击更多操作按钮后,再点击其他地方,就不会有一圈边框在按钮周围了。

<DropdownMenuContent
    onCloseAutoFocus={(e) => {
        e.preventDefault();
    }}
    >
    <DropdownMenuItem>
        操作1
    </DropdownMenuItem>
    <DropdownMenuItem>
        操作2
    </DropdownMenuItem>
    <DropdownMenuItem>
        操作3
    </DropdownMenuItem>
</DropdownMenuContent>

总结

通过上述的思路和实践的过程,最终实现需要完成的功能。如有更好的方法或建议,欢迎提供和指出。