最近,群里聊到了一个很有意思的布局效果。大致效果如下所示,希望使用 CSS 实现如下所示的布局效果:

正常而言,我们的 HTML 结构大致是如下所示:

<div class="g-container">
<div class="g-nav">
<ul>
<li>Tab 1</li>
<li>Tab 2</li>
<li>Tab 3</li>
<li>Tab 4</li>
</ul>
</div>
<div class="g-main">
<ul class="g-content">
<li>...</li>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>
</div>
</div>

对于 Hover 导航 Tab 时候的内容切换,暂且不谈。本文,我们核心想探讨的是两个点:

  1. 一是对于如下所示的不规则布局,应该如何实现:

并且,这里我们可能还需要给它加上阴影效果:

  1. 如何配合 Hover 动作,实现整个切换效果

带着这两个问题,我们一起来尝试慢慢把这个效果实现。

借助伪元素实现不规则按钮

首先,我们需要实现这个效果:

这个,其实在很多篇文章都有提及过:

想一想,这里其实就是竖向的 Chrome 分 Tab 的效果:

像是这样:

我们对这个按钮形状拆解一下,这里其实是 3 块的叠加:

只需要想清楚如何实现两侧的弧形三角即可。这里还是借助了渐变 -- 径向渐变,其实他是这样,如下图所示,我们只需要把黑色部分替换为透明即可,使用两个伪元素即可:

代码如下:

<div class="outside-circle"></div>
.outside-circle {
position: relative;
background: #e91e63;
border-radius: 10px 10px 0 0; &::before {
content: "";
position: absolute;
width: 20px;
height: 20px;
left: -20px;
bottom: 0;
background: #000;
background:radial-gradient(circle at 0 0, transparent 20px, #e91e63 21px);
}
&::after {
content: "";
position: absolute;
width: 20px;
height: 20px;
right: -20px;
bottom: 0;
background: #000;
background:radial-gradient(circle at 100% 0, transparent 20px, #e91e63 21px);
}
}

即可得到:

我们照葫芦画瓢,即可非常轻松的实现竖向的相同的效果,示意图如下:

利用 drop-shadow 实现按钮阴影

好,接下来,我们需要给按钮添加上阴影效果,像是这样:

因为使用了两个伪元素,当前单个按钮在 Hover 状态下的大致代码如下:


li {
position: relative;
width: 160px;
height: 36px;
border-radius: 10px 0 0 10px;
background: #ddd; &::before,
&::after {
content: "";
position: absolute;
right: 0;
border-radius: unset;
} &::before {
width: 20px;
height: 20px;
top: -20px;
background: radial-gradient(circle at 0 0, transparent, transparent 19.5px, #ddd 20px, #ddd);
}
&::after {
width: 20px;
height: 20px;
bottom: -20px;
background: radial-gradient(circle at 0 100%, transparent, transparent 19.5px, #ddd 20px, #ddd);
}
}

如果使用 box-shadow 肯定是不行的,整个效果就会露馅:

尝试给按钮添加一个 box-shadow: 0 0 5px 0 #333

弯曲的连接处,明显没有阴影效果,怎么解决呢?

嗯哼,老读者一定也知道,这里我们需要对整个可见部分添加阴影,需要使用 filter:drop-shadow()

drop-shadow() 滤镜的作用用于创建一个符合元素(图像)本身形状(alpha 通道)的阴影。其中,最为常见的技巧,就是利用它生成不规则图形的阴影。

因此,我们把上述的 box-shadow 替换成:filter: drop-shadow(0 0 5px #ddd)

这样,我们就实现了基于单个不规则按钮的阴影效果。

但是,显然事情还没有结束。

修改布局结构,再借助利用 drop-shadow 实现统一阴影

记得我们上面提到过的 HTML 的布局吗?正常而言,右侧的主体内容和左侧的导航,结构是分离的:

<div class="g-container">
<div class="g-nav">
<ul>
<li>Tab 1</li>
// ...
</ul>
</div>
<div class="g-main">
<ul class="g-content">
<li>...</li>
// ...
</ul>
</div>
</div>

因此,这里最为麻烦的地方在于,左侧按钮的阴影,需要和右侧的主体内容连在一起!,所以当我们给右侧的 .g-main 也添加上相同的 filter:drop-shadow() 时,整个效果会变得非常奇怪:

// 当前被 Hover 的 li
.g-nav li {
filter: drop-shadow(0 0 5px #ddd)
}
// 右侧的主体
.g-main {
filter: drop-shadow(0 0 5px #ddd)
}

无论层级谁在上,整体阴影的展示都会瑕疵:

所以,如果想要实现整个元素的阴影是一整个的整体的效果,我们就不得不另辟蹊径。

这里,我们的思路如下:

  1. 可以尝试在 .g-main 中,添加一组与 .g-nav 相同的结构,负责样式层面的展示
  2. 把新增的结构,利用绝对定位,让其与实际的导航位置重叠
  3. 在原本的 .g-nav 中,通过 :has() 伪类,传递实时的 Hover 状态

基于此,我们需要改造一下我们的结构:

<div class="g-container">
<div class="g-nav">
<ul>
<li>Tab 1</li>
<li>Tab 2</li>
<li>Tab 3</li>
<li>Tab 4</li>
</ul>
</div>
<div class="g-main">
<ul class="g-status">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<ul class="g-content">
<li>...</li>
// ...
</ul>
</div>
</div>

仔细看上面的结构,我们多了一组 .g-stauts 结构,放置在了 .g-main 之下。其 li 个数与实际的导航 .g-nav 保持一致,并且高宽大小都是一模一样的。

并且,可以利用绝对定位,让其完全叠加在 .g-nav 的位置上。

然后,我们把上述类 Chrome Tab 样式的不规则按钮结构的 CSS 代码结构,都赋给 .g-status 下的 li。

此时,由于不规则按钮结构和右侧的主体内容结构,其实是在一个父 div 之下,所以,我们只需要给 .g-main 元素添加 filter: drop-shadow(),就可以实现一整个整体的阴影效果:

最后,我们利用 :has() 伪类,传递实时的 Hover 状态,把内外的结构连接起来。

最为核心的代码如下:

.g-main {
background: #ddd;
filter: drop-shadow(0 0 3px #999);
}
.g-status {
position:absolute;
left: -160px;
top: 0;
width: 160px; li {
width: 160px;
height: 36px;
position: relative;
background: #ddd;
opacity:0; &::before,
&::after {
content: "";
position: absolute;
right: 0;
border-radius: unset;
}
&::before {
width: 20px;
height: 20px;
top: -20px;
background: radial-gradient(circle at 0 0, transparent, transparent 19.5px, #ddd 20px, #ddd);
}
&::after {
width: 20px;
height: 20px;
bottom: -20px;
background: radial-gradient(circle at 0 100%, transparent, transparent 19.5px, #ddd 20px, #ddd);
}
}
}
.g-status li {
opacity: 0;
}
.g-nav:has(li:nth-child(1):hover) + .g-main {
.g-status li:nth-child(1) {
opacity: 1;
}
}
.g-nav:has(li:nth-child(2):hover) + .g-main {
.g-status li:nth-child(2) {
opacity: 1;
}
}
.g-nav:has(li:nth-child(3):hover) + .g-main {
.g-status li:nth-child(3) {
opacity: 1;
}
}
.g-nav:has(li:nth-child(4):hover) + .g-main {
.g-status li:nth-child(4) {
opacity: 1;
}
}

什么意思呢?解释一下:

  1. 事先把每一个 Tab 被 Hover 时的样式,都写在了 .g-stauts 中,并且再提醒一下,这个结构是在 .g-main 之下的。然后,默认设置隐藏即可;
  2. 实际触发 Hover 动画效果,是正常的 .g-nav 下的一个一个的 li 结构;
  3. .g-nav 下的一个一个的 li 被 Hover 时,我们通过 :has() 伪类,能够拿到此事件,并且根据当前是第几个元素被 hover,对应的控制实际在 .g-main 下的结构进行样式的展示;

不太了解的 :has() 伪类的小伙伴,可以先读一读这篇文章 -- 浅谈逻辑选择器 is、where、not、has,此伪类的诞生,填补了在之前 CSS 选择器中,没有父选择器的空缺。让我们能够在父元素节点上,根据子元素的状态变化,做出样式的调整。

这样,我们就最终实现了我们文章一开始的效果:

文章可能有部分内容没有阐述的很清晰,完整的代码其实行数非常之少,对文章内容还不太理解的建议戳进 DEMO 中看看。

完整的 DEMO 效果:CodePen Demo -- Tab Hover Effect

有小伙伴会有疑问,为什么不直接在 .g-nav 导航结构和 .g-main 结构的父节点直接添加 drop-shadow(),不是也可以实现一样的效果吗?

是的,对于本文贴出的代码效果,是可以实现一样的效果。但是,实际业务中,.g-nav 会更复杂,它们的共同父元素下也可能还有其他元素,实际情况远比本文贴出来的结构复杂,因此借助多一层虚拟 ul,实际上是更好的解法。

最后

好了,本文到此结束,希望本文对你有所帮助

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

【布局进阶】巧用 :has & drop-shadow 实现复杂布局效果的更多相关文章

  1. 手机端页面自适应解决方案—rem布局进阶版

    手机端页面自适应解决方案—rem布局进阶版   https://www.jianshu.com/p/985d26b40199 注:本文转载之处:https://www.cnblogs.com/anni ...

  2. 我的Android进阶之旅------>Android利用温度传感器实现带动画效果的电子温度计

    要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...

  3. 巧用Drawable 实现Android UI 元素间距效果

    源文地址: 巧用Drawable 实现Android UI 元素间距效果 在大部分的移动UI或者Web UI都是基于网格概念而设计的.这种网格一般都是有一些对其的方块组成,然后它们组合成为一个块.使用 ...

  4. ccs之经典布局(三)(等分,等高布局)

    接上篇ccs之经典布局(二)(两栏,三栏布局) 七.等分布局 等分布局是指一行被分为若干列,每一列的宽度是相同的值.两列之间有若干的距离. 1.float+padding+background-cli ...

  5. Qt Quick里的图形效果:阴影(Drop Shadow)

    Qt Quick提供了两种阴影效果: DropShow,阴影.这个元素会根据源图像,产生一个彩色的.模糊的新图像,把这个新图像放在源图像后面,给人一种源图像从背景上凸出来的效果. InnerShado ...

  6. 巧用tab组件实现APP的布局效果

    1. 版本说明 iOS/Android支持版本 jar包版本 8.4及往后版本 2017年4月1日 2. 描述 tab布局能避免多层次钻取与返回,可以在一个报表内部进行切换,钻取层数如果过多的话,看报 ...

  7. rem布局进阶

    <script>!function(e){function t(a){if(i[a])return i[a].exports;var n=i[a]={exports:{},id:a,loa ...

  8. Win10系列:UWP界面布局进阶9

    Grid Grid元素用来定义一个由行和列构成的网格,这是一个功能强大的布局容器,当新建一个页面时会默认选用Grid作为顶级布局元素,下面将通过三个示例来介绍Grid的使用方法. (1)定义Grid的 ...

  9. Win10系列:UWP界面布局进阶7

    Canvas Canvas元素用于定义一个区域,可以向这个区域中添加不同的XAML界面元素.Canvas会对其内部的元素采用绝对布局方式进行布局,下面通过三个示例来介绍Canvas的使用方法. (1) ...

  10. Win10系列:UWP界面布局进阶6

    在Windows 10的"个性化设置"中,用户可以更改计算机在锁屏状态下的背景图片,除此之外,也可以通过Windows应用商店应用程序将喜欢的图片设置为锁屏背景,下面通过一个示例来 ...

随机推荐

  1. 3 - 任务调度算法 & 同步与互斥 &队列

    之前的都是按照优先级不同允许抢占(不讲道理),不管你在做什么,轮到优先级最高的任务,直接抢占执行 怎样才能讲道理呢?稍微等等嘛,等我做完活你再做   1 支持抢占,0不支持抢占  同优先级任务是否交替 ...

  2. 摆脱鼠标操作 vscode-vim-use-readme.md

    vscode-vim 学习笔记 梳理下自己定义的快捷键 Normal模式返回 ESC capsLock 双击shift ctrl+[ jj ctrl+c (这个键比较特殊 用习惯y的话,考虑这个) 一 ...

  3. 【预训练语言模型】 使用Transformers库进行BERT预训练

    基于 HuggingFace的Transformer库,在Colab或Kaggle进行预训练. 鉴于算力限制,选用了较小的英文数据集wikitext-2 目的:跑通Mask语言模型的预训练流程 一.准 ...

  4. 基于pythondetcp多个客户端连接服务器

    壹: TCP是面向运输层的协议.使用TCP协议之前,必须先建立TCP连接,在传输完成后,必须释放已经建立的TCP连接.每条TCP连接只能有两个端,每一条TCP连接只能是点对点的.TCP提供可靠的交付的 ...

  5. vscode远程登陆免密码

    A,B双方通信,A想向B发送信息,又不想让别人知道,使用非对称加密:若A向B发送信息,A需要知道B的公钥简称B-pub,用B-pub加密信息后 发送给B,B再用自己的私钥B-prv解密出信息. A想验 ...

  6. 记录--form 表单恢复初始数据

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 form 表单恢复初始数据 在现代的 Web 开发中,表单是不可或缺的组件之一.用户可以通过表单输入和提交数据,而开发者则需要对这些数据进 ...

  7. 二次元 & 动漫壁纸网站(内容记录)

    前言 天天和电脑.手机以及平板等电子设备打交道,一个好看的桌面壁纸图片当然是必不可少的,也曾经分享过<值得珍藏的高清壁纸网站推荐>,各种类型和分辨率的壁纸都有. 今天再分享些「高清二次元& ...

  8. Jmeter的Throughput有误差与分布式测试时的坑

    我是两台压力机,分布式启动jmeter压测180秒,结果throughput显示3075,我用总请求数/总耗时,64万左右/180秒,得到的TPS是3500左右.误差17% 网上说jmeter的thr ...

  9. HDFS Balancer负载均衡器

    目录 1.背景 2.什么是平衡 2.1 每个DataNode的利用率计算 2.2 集群的利用率 2.3 平衡 3.hdfs balancer语法 4.运行一个简单的balance案例 4.1 设置平衡 ...

  10. 【VMware ESXi】HP Z4G4 Workstation安装ESXi停留在Shutting down firmware services...的解决办法。

    家里有台HP Z4G4 Workstation工作站,底层安装运行了VMware的ESXi Hypervisor,作为Homelab的All in one环境. 之前安装ESXi 8的时候有个问题,在 ...