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>
相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端