css带有“反向圆角”的 Tab 凸起效果。clip-path

这套 CSS 规则的核心是一种非常巧妙的"视觉欺骗"技巧,它通过 主体两个伪元素 的拼接,来创造出带有"反向圆角"的 Tab 凸起效果。

其实现思路可以拆解为以下三个步骤:

1. 主体形状 (.tab-item.active)

  • 这是我们能直接看到的、带有文字的 Tab 主体部分。
  • background-color: red;这是整个效果能够成立的关键 。这个颜色必须和 <body> 的背景色(在您的代码中是 #e0e5ec)完全一样。当颜色相同时,它就能和页面背景融为一体,产生"浮出"的错觉。您在 Canvas 中看到的 red 应该是一个为了调试或演示而设置的临时颜色。
  • border-radius: 162px 162px 0 0;:这为主体的左上角和右上角设置了非常大的圆角,而底部两个角则保持直角,构成了 Tab 凸起的基本形状。

2. "反向圆角"的原材料 (.tab-item.active::before, ::after)

  • 这是整个技巧最核心、最神奇的部分。我们使用 ::before::after 这两个伪元素来分别构造左右两边的"凹陷"圆角。
  • 画一个大圆 :我们并没有直接画一个圆角,而是先用 border-radius: 100%box-shadow: 0 0 0 40px red 画了两个巨大的、和背景色相同的实心圆。使用 box-shadow 而非 background 是一个聪明的技巧,因为它不占据实际的布局空间,便于定位。
  • 精确定位 :通过 position: absolutebottom: 0left: -55px / right: -55px,我们将这两个大圆正好放置在了主体部分的左下角和右下角的外侧。

3. 裁剪与拼接 (clip-path)

  • 现在我们有了两个紧贴着主体的大圆,但我们实际上只需要每个圆的四分之一来形成那个"凹陷"的弧度。

  • clip-path 登场clip-path 属性就像一把剪刀,可以裁剪掉元素不需要的部分。

    • 对于左边的 ::beforeclip-path: inset(50% -10px 0 50%) 裁剪掉了圆的"左半部分"和"上半部分",只留下了右下角的四分之一
    • 对于右边的 ::afterclip-path: inset(50% 50% 0 -10px) 裁剪掉了圆的"右半部分"和"上半部分",只留下了左下角的四分之一
  • 完成拼接:这样一来,两个被裁剪后的四分之一圆弧,就完美地与主体的直角底部拼接在了一起,从视觉上"覆盖"了灰色容器的直角,从而形成了流畅的、内凹的反向圆角效果。

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>纯净版反向圆角Tabs</title>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');

        /* --- 基础和布局样式 --- */
        body {
            font-family: 'Inter', sans-serif;
            display: grid;
            place-items: center;
            min-height: 100vh;
            margin: 0;
            background-color: #e0e5ec;
        }

        /* --- Tab 容器样式 --- */
        .tab {
            display: flex;
            background-color: #d1d9e6;
            padding: 50px;
            border-radius: 12px;
            box-shadow: inset 5px 5px 10px #babecc, inset -5px -5px 10px #f0f2f5;
        }

        /* --- Tab-Item 默认样式 --- */
        .tab-item {
            position: relative; /* 为伪元素提供定位上下文 */
            padding: 12px 23px;
            font-size: 16px;
            font-weight: 500;
            color: #6d6d6d;
            cursor: pointer;
            transition: color 0.3s ease;
            white-space: nowrap;
            border-radius: 12px;
        }

        /* --- 激活状态下的样式 --- */
        .tab-item.active {
            width:40px;
            height:40px;
            color: #1a73e8;
            background-color: red; /* 背景色必须与页面背景一致 */
            border-radius: 162px 162px 0 0;
            transition: color 0.3s ease;
        }
        
        /* 隐藏非激活状态下的伪元素 */
        .tab-item::before,
        .tab-item::after {
            content: '';
            position: absolute;
            bottom: 0;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s ease;
        }

        /*
         * --- 魔法核心: 伪元素拼接反向圆角 ---
         * 激活状态下显示伪元素,并赋予它们固定的尺寸和样式
         */
        .tab-item.active::before,
        .tab-item.active::after {
            opacity: 1;
            width: 55px;  /* 反向圆角尺寸 */
            height: 55px; /* 反向圆角尺寸 */
            border-radius: 100%;
            /* 使用与页面背景色一致的阴影来填充圆形 */
            box-shadow: 0 0 0 40px red;
        }
        
        /* 左侧反向圆角 */
        .tab-item.active::before {
            left: -55px; /* 根据尺寸定位 */
            clip-path: inset(50% -10px 0 50%);
        }

        /* 右侧反向圆角 */
        .tab-item.active::after {
            right: -55px; /* 根据尺寸定位 */
            clip-path: inset(50% 50% 0 -10px);
        }
    </style>
</head>

<body>

    <nav class="tab">
        <a class="tab-item active"></a>
    </nav>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const tabItems = document.querySelectorAll('.tab-item');

            tabItems.forEach(item => {
                item.addEventListener('click', () => {
                    // 移除所有兄弟元素的 active 类
                    tabItems.forEach(i => i.classList.remove('active'));
                    // 为当前点击的项添加 active 类
                    item.classList.add('active');
                });
            });
        });
    </script>
</body>

</html>
相关推荐
小爱同学_32 分钟前
一次面试让我重新认识了 Cursor
前端·面试·程序员
golang学习记40 分钟前
AI 乱写代码?不是模型不行,而是你的 VS Code 缺了 Context!MCP 才是破局关键
前端
星光不问赶路人1 小时前
Vite 中的 import.meta.glob vs 动态导入:该用哪个?
前端·vite
z_y_j2299704381 小时前
服务器中使用Docker部署前端项目
服务器·前端·docker·容器
迪丽热爱2 小时前
解决【npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。】问题
前端·npm·node.js
数字冰雹2 小时前
图观 流渲染场景服务器
服务器·前端·数据库·数据可视化
李明卫杭州2 小时前
详细讲解js中的ResizeObserver
前端·javascript
千叶寻-2 小时前
package.json详解
前端·vue.js·react.js·webpack·前端框架·node.js·json
zz-zjx2 小时前
Web接入层的“铁三角”---防盗链、反向代理,负载均衡(nginx)
前端·nginx·负载均衡
C+ 安口木2 小时前
CSS通用优惠券样式
前端·css