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>
相关推荐
月亮慢慢圆3 小时前
Intersection Observer API
前端
薛定谔的算法3 小时前
《虚拟 DOM 与 Diff 算法:用 1500 字把它讲成“人话”》
前端·react.js·前端框架
Mintopia4 小时前
🚀 Next.js 企业级文件上传方案全解
前端·javascript·全栈
lyq3154 小时前
Sass 常用语法
css·scss
雾岛听风来4 小时前
k9s监控k8s集群工具
前端
用户87261342418514 小时前
封装组件库并上传npm源
前端
Mintopia4 小时前
🌐 Web3.0 时代:AIGC 如何赋能去中心化内容生态?
前端·javascript·aigc
鹏多多4 小时前
前端项目eslint配置选项详细解析
前端·vue.js·react.js
然我4 小时前
面试官:这道 Promise 输出题你都错?别再踩 pending 和状态凝固的坑了!(附超全解析)
前端·javascript·面试