搞个CSS提示框的小三角,到底有哪些野路子?

4,462字
19–28 分钟
in

平时做个网页提示框,那个带尖尖的小三角看着简单,真动手整起来就有点懵。网上搜一圈,方法五花八门,跟做菜似的,每家都有独门秘方。今儿就把这些“野路子”挨个捋一遍,从最基础的边框大法到只用单个元素的骚操作,手把手带小白走一遍流程。

目录

提示框的小三角本质上是个视觉小把戏。常见思路有两种:一是用边框拼出三角形,二是把正方形旋转45度再切掉多余部分。核心都是利用CSS的透明、定位和裁剪属性,把一个普通元素伪装成尖角。下面这几个方案,覆盖从简单到复杂的各种场景,按需取用就行。

三角形咋来的

先整明白原理。一个宽高为0的元素,给它四周都加上边框,每个边框会变成梯形。当宽高为0时,四个梯形就变成了四个三角形,每个角一个。把其中三个边框颜色设成透明,剩下的那个就单独露出来了。这就是边框大法的核心。另一种思路是弄个正方形,转45度角,让其中一个角露在外面,看起来就是三角形。搞懂了这俩基础,后面所有花活都围绕它们转。

边框大法走起

这个方法最老牌,2010年的Stack Overflow上就有了,经得起考验。咱一步步来。

搭骨架

先写个提示框的HTML,只用单个元素,干净利落:

<span class="tooltip">别摸我,有毒</span>

画三角形

用伪元素::before来画那个小三角。需要把伪元素的宽高归零,只留边框。

.tooltip::before {
  content: "";
  border-width: 8px;   /* 这个控制三角底边宽度 */
  border-style: solid;
  border-color: transparent;  /* 先全透明 */
  border-top-width: 12px;     /* 顶部边框单独加粗,三角高度就变大了 */
  border-top-color: #ff5722;  /* 只让顶部边框有色,其他方向透明 */
}

代码里的border-width设8px,四个边框都是8px粗。然后border-top-width单独改成12px,相当于顶部边框比其他三边更厚。因为宽高为0,顶部边框会形成一个底边宽16px(左右边框各8px)、高12px的三角形。想要三角朝哪个方向,就改哪个方向的边框颜色,其他方向保持透明。

贴到提示框上

需要把提示框设成相对定位,伪元素设成绝对定位,这样才能把三角粘在框框的某个边上。

.tooltip {
  position: relative;
  display: inline-block;
  background: #ff5722;
  color: white;
  padding: 8px 12px;
  border-radius: 6px;
}

.tooltip::before {
  content: "";
  position: absolute;
  top: 100%;        /* 让三角出现在提示框底部外侧 */
  left: 50%;
  border-width: 8px;
  border-style: solid;
  border-color: transparent;
  border-top-width: 12px;
  border-top-color: #ff5722;
  transform: translateX(-50%);  /* 水平居中,因为left:50%会让三角左边缘在中间,平移一半宽度才正中 */
}

这里transform: translateX(-50%)是个坑点。伪元素宽高为0,但它的定位原点在左上角。left: 50%会让左上角跑到父元素中间,三角本身不对称,所以需要往左挪自身宽度的一半。由于没有宽高,平移比例是按边框算的?实际上translateX(-50%)是相对于伪元素自身的宽度,但伪元素宽度为0,这步有用吗?仔细测过,因为边框撑开了实际占据空间,translateX(-50%)确实能居中。稳妥起见,也可以用left: calc(50% - 4px)之类的硬调,但平移更省事。

如果想把三角放到左边或右边,只需要改topleft的值,同时改transform的方向。比如三角朝右,就把border-left-color改成有色,然后设置left: 100%; top: 50%; transform: translateY(-50%);

翻车现场

边框大法有个硬伤:三角的方向变了,就得重新调边框颜色和位置。比如提示框出现在元素上方,三角得朝下,那就得把border-bottom-color设成有色,然后把top改成autobottom: 100%。每次都得改好几个属性,不够灵活。而且用了边框属性,就没法再给伪元素加其他边框效果了。

旋转正方形

换个思路,不用边框,直接弄个正方形,转45度,只让一个角露出来。

画方块

.tooltip::before {
  content: "";
  width: 16px;
  height: 16px;
  background: #ff5722;
  position: absolute;
  top: 75%;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  z-index: -1;  /* 藏到提示框背景后面,只露出半个角 */
}

这个方块转了45度之后,原本的四个角变成了上下左右四个尖尖。只让朝上的那个尖尖露出来,其他部分被提示框的背景盖住。z-index: -1就是把方块塞到提示框的底层。

调整位置

方块的位置需要算一下。因为旋转中心默认是元素中心,top: 75%配合translateX(-50%)再加rotate(45deg),能让尖尖刚好卡在提示框底部中间。这里top值需要根据提示框的高度微调,一般取100%的话三角会掉出去,取75%左右比较稳。

潜在bug

这种方法做出来的不是严格意义上的三角形,是个旋转45度的正方形露出的角。如果提示框高度特别小(比如只有20px高),那个方块的上半截可能会从顶部冒出来。解决办法是给提示框加overflow: hidden,但这样又会把超出部分切掉,三角可能缺一块。所以只适合提示框高度足够大的场景。

clip-path精准切割

旋转正方形不够完美,那就直接用clip-path把正方形切成真正的三角形,想切多尖就切多尖。

切三角形

.tooltip::before {
  content: "";
  width: 20px;   /* 三角底边宽度 */
  height: 14px;  /* 三角高度 */
  background: #ff5722;
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  clip-path: polygon(0% 0%, 100% 0%, 50% 100%);
}

polygon(0% 0%, 100% 0%, 50% 100%)的意思是:从左上角(0%,0%)开始,画到右上角(100%,0%),再画到底边中点(50%,100%),最后闭合。这样就切出了一个朝上的三角形。如果想朝下,把三个点改成0% 100%, 100% 100%, 50% 0%。朝左朝右同理,改x轴百分比就行。

防越界

三角贴边的时候容易戳出去。比如三角要贴在提示框左侧,并且提示框靠页面左边缘很近,三角的左边可能会超出屏幕。这时可以用min()max()函数动态限制切割点。

.tooltip::before {
  clip-path: polygon(
    max(50% - var(--left-offset), 0%) 0%,
    min(150% - var(--left-offset), 100%) 0%,
    50% 100%
  );
}

--left-offset是三角离提示框左边界的距离。当三角太靠左时,max(50% - 偏移, 0%)保证切割点不会跑到0%左边;当三角太靠右时,min(150% - 偏移, 100%)保证不会超过100%。这样三角永远不会被切崩。

单元素终极方案

前面几个都用到了伪元素。万一伪元素被占用了(比如已经用来做其他装饰),或者单纯就想用一个标签搞定所有,那就得祭出border-imageclip-path的组合拳。

思路

不用伪元素,直接在.tooltip本体上动刀。先用border-image给元素扩展出一块“假背景”,然后用clip-path把本体和扩展出来的三角一起切出来。

操作流程

.tooltip {
  /* 先画本体背景 */
  background: #ff5722;
  padding: 8px 12px;
  border-radius: 6px;
  position: relative;

  /* 关键:用border-image造一个额外的三角区域 */
  border-image: fill 0 // 14px conic-gradient(#ff5722 0 0);
  /* fill表示让背景填充到边框区域,14px是三角高度,conic-gradient就是纯色 */

  /* 再用clip-path切出带三角的形状 */
  clip-path: polygon(
    0% 100%,                    /* 左下角 */
    0% 0%,                      /* 左上角 */
    100% 0%,                    /* 右上角 */
    100% 100%,                  /* 右下角 */
    calc(50% + 10px) 100%,     /* 三角右边起点 */
    50% calc(100% + 14px),     /* 三角尖点 */
    calc(50% - 10px) 100%      /* 三角左边起点 */
  );
}

border-image那一行比较绕。fill表示把背景延伸到边框区域。0是边框宽度(这里不设边框厚度),// 14px是边框外扩的距离(三角的高度)。conic-gradient(#ff5722 0 0)是生成一个纯色渐变,相当于一张纯色图片。整句的意思就是:在元素底部往外扩展14px的区域,涂上红色。

然后clip-path把本体和这个扩展出来的三角一起切出来。七个点的顺序按逆时针或顺时针都行,只要连起来是个带尖角的形状。calc(50% + 10px)里的10px是三角底边的一半,根据实际需要调整。

动态防越界

同样需要加上边界保护,避免三角太靠边时被切歪。

.tooltip {
  clip-path: polygon(
    0% 100%,
    0% 0%,
    100% 0%,
    100% 100%,
    min(calc(50% + var(--三角半宽)), 100%) 100%,
    var(--三角水平位置) calc(100% + var(--三角高度)),
    max(calc(50% - var(--三角半宽)), 0%) 100%
  );
}

--三角水平位置left百分比传入,--三角半宽--三角高度按实际尺寸设置。min()max()保证三角的左右两个底角不会超出提示框的边缘。

这个方案的优点是只用一个元素,没有任何伪元素,代码干净。缺点是border-image的语法比较冷门,新手看了容易懵,但抄下来能用就行。

各方案咋选

方案名称优点缺点适用场景
边框大法简单易懂改方向麻烦固定方向提示框
旋转方块代码少不精确高度较大的提示框
clip-path形状精确需伪元素大部分常规场景
单元素终极省标签语法复杂伪元素被占时

平时开发,优先用边框大法或者clip-path的伪元素方案,够用了。实在有洁癖或者伪元素不够使,再上单元素那个硬核玩法。这几种法子都跑一遍,以后碰见提示框小三角,心里就有数了。