【布局进阶】巧用 :has & drop-shadow 实现复杂布局效果
最近,群里聊到了一个很有意思的布局效果。大致效果如下所示,希望使用 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 时候的内容切换,暂且不谈。本文,我们核心想探讨的是两个点:
- 一是对于如下所示的不规则布局,应该如何实现:

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

- 如何配合 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)
}
无论层级谁在上,整体阴影的展示都会瑕疵:

所以,如果想要实现整个元素的阴影是一整个的整体的效果,我们就不得不另辟蹊径。
这里,我们的思路如下:
- 可以尝试在
.g-main中,添加一组与.g-nav相同的结构,负责样式层面的展示 - 把新增的结构,利用绝对定位,让其与实际的导航位置重叠
- 在原本的
.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;
}
}
什么意思呢?解释一下:
- 事先把每一个 Tab 被 Hover 时的样式,都写在了
.g-stauts中,并且再提醒一下,这个结构是在.g-main之下的。然后,默认设置隐藏即可; - 实际触发 Hover 动画效果,是正常的
.g-nav下的一个一个的 li 结构; - 当
.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 实现复杂布局效果的更多相关文章
- 手机端页面自适应解决方案—rem布局进阶版
手机端页面自适应解决方案—rem布局进阶版 https://www.jianshu.com/p/985d26b40199 注:本文转载之处:https://www.cnblogs.com/anni ...
- 我的Android进阶之旅------>Android利用温度传感器实现带动画效果的电子温度计
要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...
- 巧用Drawable 实现Android UI 元素间距效果
源文地址: 巧用Drawable 实现Android UI 元素间距效果 在大部分的移动UI或者Web UI都是基于网格概念而设计的.这种网格一般都是有一些对其的方块组成,然后它们组合成为一个块.使用 ...
- ccs之经典布局(三)(等分,等高布局)
接上篇ccs之经典布局(二)(两栏,三栏布局) 七.等分布局 等分布局是指一行被分为若干列,每一列的宽度是相同的值.两列之间有若干的距离. 1.float+padding+background-cli ...
- Qt Quick里的图形效果:阴影(Drop Shadow)
Qt Quick提供了两种阴影效果: DropShow,阴影.这个元素会根据源图像,产生一个彩色的.模糊的新图像,把这个新图像放在源图像后面,给人一种源图像从背景上凸出来的效果. InnerShado ...
- 巧用tab组件实现APP的布局效果
1. 版本说明 iOS/Android支持版本 jar包版本 8.4及往后版本 2017年4月1日 2. 描述 tab布局能避免多层次钻取与返回,可以在一个报表内部进行切换,钻取层数如果过多的话,看报 ...
- rem布局进阶
<script>!function(e){function t(a){if(i[a])return i[a].exports;var n=i[a]={exports:{},id:a,loa ...
- Win10系列:UWP界面布局进阶9
Grid Grid元素用来定义一个由行和列构成的网格,这是一个功能强大的布局容器,当新建一个页面时会默认选用Grid作为顶级布局元素,下面将通过三个示例来介绍Grid的使用方法. (1)定义Grid的 ...
- Win10系列:UWP界面布局进阶7
Canvas Canvas元素用于定义一个区域,可以向这个区域中添加不同的XAML界面元素.Canvas会对其内部的元素采用绝对布局方式进行布局,下面通过三个示例来介绍Canvas的使用方法. (1) ...
- Win10系列:UWP界面布局进阶6
在Windows 10的"个性化设置"中,用户可以更改计算机在锁屏状态下的背景图片,除此之外,也可以通过Windows应用商店应用程序将喜欢的图片设置为锁屏背景,下面通过一个示例来 ...
随机推荐
- go程序在mac下的交叉编译
主页 微信公众号:密码应用技术实战 博客园首页:https://www.cnblogs.com/informatics/ 背景 go语言的一大优势就是跨平台,go语言是编译型语言,与Java.C#等语 ...
- C#事件(event)的理解
一.多播委托的应用--观察者模式 遇到一个开发的问题? 面试者:以面向对象的思想实现一下的场景: 猫:Miao一声,紧接着引发了一系列的行为~ Miao:引发了一系列的动作: 从代码层面来说:代码这样 ...
- 数字政府!3DCAT实时云渲染助推上海湾区数字孪生平台
数字孪生,是一种利用物理模型.传感器数据.运行历史等信息,在虚拟空间中构建实体对象或系统的精确映射,从而实现对其全生命周期的仿真.优化和管理的技术.数字孪生可以应用于各个领域,如工业制造.智慧城市.医 ...
- 优化您的部署:Docker 镜像最佳实践
介绍 在快速发展的软件开发和部署领域,Docker 已成为容器化的强大工具,为打包.分发和运行应用程序提供了一种标准化的高效方式.Docker 镜像在这一过程中发挥着至关重要的作用,是容器化应用程序的 ...
- 记录--手写vm.$mount方法
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.概述 在我们开发中,经常要用到Vue.extend创建出Vue的子类来构造函数,通过new 得到子类的实例,然后通过$mount挂载到 ...
- KingbaseESV8R6中查看索引常用sql
前言 KingbaseES具有丰富的索引功能,对于运行一段时间的数据库,经常需要查看索引的使用大小,使用状态等. 尤其重复索引的存在,有时会因为索引过多而造成维护成本加大和减慢数据库的运行速度. 下面 ...
- JS实现打开报表时默认为最后一次查询参数
问题描述 普通报表在打开时希望参数面板中的参数控件的值可以默认是上一次页面关闭前最后一次查询所选择的值. 解决方案 每次在页面关闭时将参数值保存到浏览器缓存中(适用用非FS平台),或每次点击查询后将参 ...
- 强烈推荐:2024 年12款 Visual Studio 亲测、好用、优秀的工具,AI插件等
工具类扩展 1. ILSpy 2022 (免费) ILSpy 是 ILSpy 开源反编译器的 Visual Studio 扩展. 是一款开源.免费的.且适用于.NET平台反编译[C#语言编写的程序和库 ...
- #FFT#P4173 残缺的字符串
题目 有一个长度为 \(m\) 的字符串 \(A\) 和 一个长度为 \(n\) 的字符串 \(B\), 它们有若干位置为通配符,现在问 \(B\) 的每一个后缀是否存在前缀为 \(A\). 分析 考 ...
- #dp,齐肯多夫定理#CF126D Fibonacci Sums
题目 \(T(T\leq 10^5)\) 组数据,每次给定数字 \(n(n\leq 10^{18})\), 问有多少种方案将 \(n\) 分解成若干个互不相同的斐波那契数 分析 如果找到一个方案使得所 ...