打造独特弧形边框选项卡:HTML、CSS 与 JavaScript 实现

405 阅读8分钟

打造独特弧形边框选项卡:HTML、CSS 与 JavaScript 实现

一、最终效果展示

image.png

二、代码实现

(一)HTML 结构搭建

在 HTML 部分,创建一个基本的结构。<div class="root"> 作为整体容器,内部的 <ul class="tab"> 是选项卡的列表。每个选项卡是一个 <li> 元素,其中初始激活的选项卡添加了 active 类。为了避免文字随选项卡旋转而变形,我们在每个 <li> 中包裹了一个 <span> 标签来放置文本。代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>弧形边框选项卡</title>
    <!-- 引入CSS样式 -->
    <style>
        /* 这里是CSS代码,稍后详细讲解 */
    </style>
</head>
<body>
<div class="root">
    <!-- 选项卡列表 -->
    <ul class="tab">
        <!-- 初始激活的选项卡 -->
        <li class="active"><span>选项卡一</span></li>
        <!-- 普通选项卡 -->
        <li><span>选项卡二</span></li>
    </ul>
</div>
<!-- 引入JavaScript脚本 -->
<script>
    /* 这里是JavaScript代码,稍后详细讲解 */
</script>
</body>
</html>

(二)CSS 样式设计

全局样式重置
/* 全局样式重置,去除所有元素的默认外边距和内边距 */
* {
    margin: 0;
    padding: 0;
}

这一步确保所有浏览器对元素的默认样式设置一致,避免因浏览器差异导致的显示问题。

设置页面背景
body {
    background-color: #6e6e6e;
}

这里将页面背景颜色设置为深灰色,与选项卡的颜色形成对比,突出选项卡。

选项卡容器样式
/* 选项卡容器样式 */
ul {
    margin: 0 auto; /* 水平居中 */
    width: 90%; /* 宽度为父元素的90% */
    list-style-type: none; /* 去除列表项的默认样式 */
    display: flex; /* 使用弹性布局 */
    justify-content: center; /* 水平居中排列子元素 */
    text-align: center; /* 文本居中 */
    align-items: center; /* 垂直居中排列子元素 */
    border-bottom: 1px solid #ffffff; /* 底部添加白色边框 */
}

通过 display: flex<ul> 设置为弹性容器,使内部的 <li> 元素能够水平排列且居中。设置 width 为父元素的 90%,并通过 margin: 0 auto 实现水平居中。去除列表项默认样式,添加底部白色边框。

单个选项卡样式
/* 单个选项卡样式 */
li {
    box-sizing: border-box; /* 盒模型计算方式,包含内边距和边框 */
    background-color: #ffffff; /* 默认背景颜色 */
    width: 200px; /* 宽度 */
    height: 60px; /* 高度 */
    font-size: 24px; /* 字体大小 */
    line-height: 60px; /* 行高,使文字垂直居中 */
    border-radius: 20px 20px 0 0; /* 顶部圆角 */
    position: relative; /* 相对定位,为伪元素定位做准备 */
    user-select: none; /* 禁止用户选择文本 */
    transform: perspective(40px) rotateX(7deg); /* 3D透视旋转,使选项卡呈现弧形 */
    transform-origin: center bottom; /* 旋转原点在底部中心 */
    z-index: 1; /* 默认层级为1 */
}

每个选项卡 <li> 设置了固定的宽度、高度、字体大小和行高,确保文字垂直居中。通过 border-radius 设置顶部圆角,background-color 设置默认背景色为白色。利用 transformtransform-origin 属性实现 3D 透视旋转,使选项卡呈现独特的弧形效果。设置 position: relative,以便为后续的伪元素定位。user-select: none 禁止用户在选项卡上进行文本选择操作。z-index: 1 设置默认层级,确保选项卡正常显示。

选项卡左右两侧的伪元素样式
/* 选项卡左右两侧的伪元素,用于实现弧形效果 */
li::before, li::after {
    content: ''; /* 伪元素内容为空 */
    position: absolute; /* 绝对定位 */
    width: 30px; /* 宽度 */
    height: 30px; /* 高度 */
    bottom: 0; /* 位于底部 */
}

/* 左侧伪元素 */
li::before {
    left: -30px; /* 位于左侧 */
    background: radial-gradient(circle at 0 0, transparent 30px, #ffffff 30px); /* 径向渐变,实现弧形背景 */
}

/* 右侧伪元素 */
li::after {
    right: -30px; /* 位于右侧 */
    background: radial-gradient(circle at 100% 0, transparent 30px, #ffffff 30px); /* 径向渐变,实现弧形背景 */
}

通过 ::before::after 伪元素,在选项卡的左右两侧添加了两个小的圆形区域,利用径向渐变实现与选项卡主体颜色一致的弧形背景,进一步增强选项卡的弧形视觉效果。

激活状态的选项卡样式
/* 激活状态的选项卡样式 */
li.active {
    background-color: #ff6b6b; /* 激活状态背景颜色 */
    z-index: 2; /* 激活状态层级为2,确保在最上方 */
}

/* 激活状态选项卡的左侧伪元素样式 */
li.active::before {
    background: radial-gradient(circle at 0 0, transparent 30px, #ff6b6b 30px); /* 激活状态时的径向渐变背景 */
}

/* 激活状态选项卡的右侧伪元素样式 */
li.active::after {
    background: radial-gradient(circle at 100% 0, transparent 30px, #ff6b6b 30px); /* 激活状态时的径向渐变背景 */
}

当选项卡处于激活状态(即被点击选中)时,通过 .active 类改变其背景颜色为醒目的橙色,并将 z-index 提升到 2,确保激活的选项卡显示在最上方。同时,相应地改变其左右两侧伪元素的背景颜色,以保持整体样式的一致性。

选项卡内文字样式
/* 选项卡内文字样式,通过反向旋转抵消选项卡的旋转,避免文字变形 */
li span {
    display: inline-block; /* 行内块元素 */
    transform: perspective(40px) rotateX(-7deg); /* 反向3D透视旋转 */
    transform-origin: center bottom; /* 旋转原点在底部中心 */
}

由于选项卡整体进行了旋转,为了避免文字随之旋转而变形,对 <span> 标签内的文字应用了反向的 transform,使其保持正常显示。

(三)JavaScript 交互实现

// 获取选项卡列表元素
let tab = document.querySelector('.tab');
// 为选项卡列表添加点击事件监听器
tab.addEventListener('click', function (e) {
    // 检查点击的元素是否为列表项
    if (e.target.closest('li')) {
        // 获取所有列表项
        const lis = tab.querySelectorAll('li');
        // 遍历所有列表项,移除激活类
        lis.forEach(li => li.classList.remove('active'));
        // 获取点击的列表项
        const targetLi = e.target.closest('li');
        // 为点击的列表项添加激活类
        targetLi.classList.add('active');
    }
});

在 JavaScript 部分,首先通过 document.querySelector('.tab') 获取到选项卡的列表元素。然后为该元素添加点击事件监听器,当用户点击选项卡列表时,判断点击的元素是否为 <li>。如果是,则获取所有的 <li> 元素,遍历并移除它们的 active 类,以取消之前激活的选项卡。接着获取当前点击的 <li> 元素,并为其添加 active 类,从而实现选项卡的切换效果。

三、完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>弧形边框选项卡</title>
    <style>
        /* 全局样式重置,去除所有元素的默认外边距和内边距 */
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: #6e6e6e;
        }

        /* 选项卡容器样式 */
        ul {
            margin: 0 auto; /* 水平居中 */
            width: 90%; /* 宽度为父元素的90% */
            list-style-type: none; /* 去除列表项的默认样式 */
            display: flex; /* 使用弹性布局 */
            justify-content: center; /* 水平居中排列子元素 */
            text-align: center; /* 文本居中 */
            align-items: center; /* 垂直居中排列子元素 */
            border-bottom: 1px solid #ffffff; /* 底部添加灰色边框 */
        }

        /* 单个选项卡样式 */
        li {
            box-sizing: border-box; /* 盒模型计算方式,包含内边距和边框 */
            background-color: #ffffff; /* 默认背景颜色 */
            width: 200px; /* 宽度 */
            height: 60px; /* 高度 */
            font-size: 24px; /* 字体大小 */
            line-height: 60px; /* 行高,使文字垂直居中 */
            border-radius: 20px 20px 0 0; /* 顶部圆角 */
            position: relative; /* 相对定位,为伪元素定位做准备 */
            user-select: none; /* 禁止用户选择文本 */
            transform: perspective(40px) rotateX(7deg); /* 3D透视旋转,使选项卡呈现弧形 */
            transform-origin: center bottom; /* 旋转原点在底部中心 */
            z-index: 1; /* 默认层级为1 */
        }

        /* 选项卡左右两侧的伪元素,用于实现弧形效果 */
        li::before, li::after {
            content: ''; /* 伪元素内容为空 */
            position: absolute; /* 绝对定位 */
            width: 30px; /* 宽度 */
            height: 30px; /* 高度 */
            bottom: 0; /* 位于底部 */
        }

        /* 左侧伪元素 */
        li:before {
            left: -30px; /* 位于左侧 */
            background: radial-gradient(circle at 0 0, transparent 30px, #ffffff 30px); /* 径向渐变,实现弧形背景 */
        }

        /* 右侧伪元素 */
        li:after {
            right: -30px; /* 位于右侧 */
            background: radial-gradient(circle at 100% 0, transparent 30px, #ffffff 30px); /* 径向渐变,实现弧形背景 */
        }

        /* 激活状态的选项卡样式 */
        li.active {
            background-color: #ff6b6b; /* 激活状态背景颜色 */
            z-index: 2; /* 激活状态层级为2,确保在最上方 */
        }

        /* 激活状态选项卡的左侧伪元素样式 */
        li.active::before {
            background: radial-gradient(circle at 0 0, transparent 30px, #ff6b6b 30px); /* 激活状态时的径向渐变背景 */
        }

        /* 激活状态选项卡的右侧伪元素样式 */
        li.active::after {
            background: radial-gradient(circle at 100% 0, transparent 30px, #ff6b6b 30px); /* 激活状态时的径向渐变背景 */
        }

        /* 选项卡内文字样式,通过反向旋转抵消选项卡的旋转,避免文字变形 */
        li span {
            display: inline-block; /* 行内块元素 */
            transform: perspective(40px) rotateX(-7deg); /* 反向3D透视旋转 */
            transform-origin: center bottom; /* 旋转原点在底部中心 */
        }
    </style>
</head>
<body>
<div class="root">
    <!-- 选项卡列表 -->
    <ul class="tab">
        <!-- 初始激活的选项卡 -->
        <li class="active"><span>选项卡一</span></li>
        <!-- 普通选项卡 -->
        <li><span>选项卡二</span></li>
    </ul>
</div>

<script>
    // 获取选项卡列表元素
    let tab = document.querySelector('.tab');
    // 为选项卡列表添加点击事件监听器
    tab.addEventListener('click', function (e) {
        // 检查点击的元素是否为列表项
        if (e.target.closest('li')) {
            // 获取所有列表项
            const lis = tab.querySelectorAll('li');
            // 遍历所有列表项,移除激活类
            lis.forEach(li => li.classList.remove('active'));
            // 获取点击的列表项
            const targetLi = e.target.closest('li');
            // 为点击的列表项添加激活类
            targetLi.classList.add('active');
        }
    });
</script>
</body>
</html>