纯CSS实现多级标签样式

发布时间:

最后更新:

关键词: CSS flex 三角形 多级标签

1.多级分类标签介绍

在一个分类系统中,类别往往具有从属关系,比如生物属于自然界,动物属于生物,猫属于动物,你家里的叫咪咪的那只又属于猫。现在你建立了一个网站,让每个人都能够展示自己的某个东西,在名称的旁边有一个小标签,显示出了这一趟的从属关系。

本博客在编写时就遇到了这样的需求,需要在首页https://kaciras.net/的文章列表中显示出完整的分类路径,本以为是个很简单的问题,动手之后才发现并没有那么容易,特别是存在点击区域和覆盖的问题,在苦战3个小时之后终于实现了一个比较完美的结果,效果如下(GIF录制问题,实际上内部并不会变色):

动态效果 动态效果

本实现具有以下优点:

  1. 纯CSS,干净无污染
  2. 三角形部分是真正的不规则(非矩形盒子)元素,点击更精确
  3. 一键使用,仅需在容器上加上一个class即可

唯一的遗憾就是HTML中的顺序必须反过来,因为HTML中下面的元素Z-Index默认比上面的高。最终版代码如下: 多级分类标签展示 多级分类标签展示

<div class="tag-group">
	<a>4级分类</a>
	<a>3级分类</a>
	<a>二级分类</a>
	<a>一级分类</a>
	<a>顶级分类</a>
</div>

CSS代码如下,直接复制即可使用:

.tag-group > * {
  position: relative;
  cursor: pointer;
  border: solid 1px #ffa900;
  padding: .2em .4em;
}
.tag-group > *:last-child {
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;
}
.tag-group > *:first-child {
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px;
}
.tag-group > *:not(:last-child) {
  padding-left: 1.2em;
  border-left-width: 0;
}
.tag-group > *:not(:first-child):after {
  content: "";
  position: absolute;
  top: 50%;
  right: -1.2em;
  border: solid .65em transparent;
  border-top-color: #ffffff;
  border-right-color: #ffffff;
  transform: translateX(-50%) translateY(-50%) rotate(45deg);
  box-shadow: 1px -1px 0 0 #ffa900;
}
.tag-group > *:hover {
  color: #ffffff;
  background-color: #ffa900;
}
.tag-group > *:hover:after {
  border-top-color: #ffa900;
  border-right-color: #ffa900;
}

当然我在开发时使用的是LESS

@color-tags: #ffa900;
@color-tag-background: white;

.tag-group {
	display: flex;
	flex-direction: row-reverse;
	justify-content: flex-end;

	background-color: @color-tag-background;


	& > * {
		position: relative;
		cursor: pointer;
		border: solid 1px @color-tags;
		padding: .2em .4em;

		&:last-child {
			border-top-left-radius: 4px;
			border-bottom-left-radius: 4px;
		}
		&:first-child {
			border-top-right-radius: 4px;
			border-bottom-right-radius: 4px;
		}

		&:not(:last-child) {
			padding-left: 1.2em;
			border-left-width: 0;
		}

		&:not(:first-child) {
			&:after {
				content: "";
				position: absolute;
				top: 50%;
				right: -1.2em;

				border: solid .65em transparent;
				border-top-color: @color-tag-background;
				border-right-color: @color-tag-background;
				
				transform: translateX(-50%) translateY(-50%) rotate(45deg);
				box-shadow: 1px -1px 0 0 @color-tags;
			}
		}

		&:hover {
			color: @color-tag-background;
			background-color: @color-tags;
			&:after {
				border-top-color: @color-tags;
				border-right-color: @color-tags;
			}
		}
	}
}

2.三角形的边框实现

方形的边框谁都会,所以要做出这么一个效果,关键点就在两个分类之间的三角部分。在网上查阅一些资料后,发现了一个Bootstarp实现三角形的方法。其原理则是利用了边框的分割方式,元素的上下左右四个边框在交界处是以45°为界分开,就像下面这样:

border分隔 border分隔

如果边框宽度等于元素宽度的一半,那就刚好可以以X型4等分:

border的4等分 border的4等分

这4个边框就是4个三角形,刚好上下左右各一个,要用哪一个就把其他三个设为透明即可,另外由于边框本身占有宽度,所以元素的width属性也可以设置为0.

border实现的三角形 border实现的三角形

这种方式虽然能做出三角形的效果,但是元素本身还是个方形的,在鼠标所在区域判定上,透明的部分仍会被算作当前元素,多个HTML元素挨在一起时会出现看似点击右边,实际上鼠标落在左边的情况。

可惜我们的多级标签需要一个点击功能,点击后跳转到相应分类的页面下,如果采用这种方法,就有可能跳到错误的位置,并且在使用hover伪类实现动态效果上也会让人感觉很不好。

3.CSS3元素旋转实现三角形

CSS3新增一个transform属性,它可以有好几个值,专门用来做形状变换:

  • translate(X,Y):根据给定的 left(X) 和 top(Y),从其当前位置移动元素。
  • rotate(deg):将元素顺时针旋转一定的角度
  • scale(X,Y):将元素沿X轴和Y轴缩放指定的倍数
  • skew(Xdeg,Ydeg):将元素沿X轴和Y轴翻转指定的角度
  • matrix(...):旋转、缩放、移动以及倾斜元素的合并写法

使用transform,可以实现非方形盒子元素,比如transform: rotate(45deg)就可以做出一个菱形:

旋转45度 旋转45度

接下来把背景颜色设置为白色,并加上上边框和右边框:

旋转+边框实现三角形 旋转+边框实现三角形

可以看到一个三角形的边框已经出来了,并且元素覆盖的区域也随之一起旋转,在斜线外点击是不会被算作点击该元素的。接下来把它作为标签的附加内容:after 即可实现右三角边框:

右箭头形元素边框 右箭头形元素边框

4.边框+阴影实现半边透明

边框做好了,但是当你往里面放几个字的时候又出现问题了。

伪元素挡住内容 伪元素挡住内容

伪元素把内容给挡住了(╬ ̄皿 ̄),这还了得?赶紧想办法。

首先可以设置一个右填充,但是这样标签右边空白就太多了,显得很难看。那能不能用z-index实现呢?在分别给他俩设置了z-index后,什么卵用都没有。

z-index无效 z-index无效

当然如果单独给:after设置一个负数z-index是可以让它跑到下面去的,但负数z-index尽量不要用,因为这将可能导致元素被其他没有设置z-index的元素覆盖。

既然没法把伪元素挪到下面去,就得换个思路,首先直接把伪类元素设置为透明不可取,因为后面还要做:hover的高亮效果,必须要求伪类元素能够设置颜色。不过如果能把菱形左半三角去掉的话位置差不多也够了,那应该怎么做呢?回想一下第二节那个假三角的实现,用它可以把元素盒子以X形分开,那么上和右这两个部分的边框在旋转后不正好组成右半边三角吗?

边框+旋转效果 边框+旋转效果

不过既然把边框用来设置背景,那么原本需要的边框怎么办呢?这里需要另一个技巧,就是拿阴影来做边框,我们都知道box-shadow有6个属性:

  • 前两个是设置阴影偏移,把它设置为1px -1px的话表示阴影向右上角(X向右,Y向上)移动一个像素。
  • 第三个参数是模糊距离,这里不需要模糊,故设为0
  • 第四个是阴影尺寸,把它设置为0表明阴影跟元素一样大,然后依靠偏移把它往右上挪,就能实现只显示出右上1个像素的阴影
  • 最后一个参数不填,表示阴影在边框外
.kaciras.net:after{
	border: 14px solid transparent;
	border-top-color: #e3ff82;
	border-right-color: #ff8ec6;
	box-shadow: 1px -1px 0 0 #0092ff;

	position: absolute;
	top: 50%;
	right: -26px;
	content: "";
	transform: translate(-50%, -50%) rotate(45deg);
}

阴影实现边框 阴影实现边框

经过一番折腾之后,菱形的左半边终于被消灭,终于成功搞定这个很小的三角。

可以高亮的三角 可以高亮的三角

5.多标签堆叠覆盖

一个成功不等于全部成功,当你组合使用时就会发现...右边的比左边不知道高到哪里去了,当右边高亮的时候左边的三角部分被遮挡住了。为什么会这样呢?因为后面的元素默认比前面的z-index要高,这是HTML规范钦定的。

右边覆盖了左边 右边覆盖了左边

要解决这个问题,首先可以手动指定两者的z-index值,把前面的设置的比后头的大即可。但是本站是无限级分类,也就是说标签可能有无数多层(虽然在实际中很少有超过5层的分类的博客,但技术就是追求完美),手动设置就显得NAIVE。

这个问题我想了很久也没想出个完美的办法,只能退求其次,让HTML里顺序反过来,在显示上再反一遍,即反序排列。

这个就比较简单了,CSS中的flex布局是一个非常灵活的排版方式,其中flex-direction就能够让元素反着排列,再加上justify-content把浮动方向再反一次,即可实现反序排列。

最终效果 最终效果

6.结语

一个多级标签,看似虽小,实现起来却并不简单。一个前端,到处都是坑,不过技术也正是在一次次踩坑中逐渐成长。而本博客也在一次次更新中完善,到此,多级分类的标签功能成功搞定,关于无限级分类的数据库设计将在另一篇文章中讲解。

评论加载中