[ 基础系列 ] - CSS 小测 02

391 阅读7分钟

系列文章

说在前面

本篇是张鑫旭老师的 CSS基础测试2 的阅后笔记。(另外感谢 XboxYan 的解答,逻辑非常清晰)

题目

话不多说,先上题,HTML 如下:

<a
  href="javascript:"
  class="icon-delete css-tips"
  data-title="删除"
  role="button"
>
  删除
</a>

现需实现如下效果:

img-00

要求:

  • 纯 css 实现
  • 考虑边界情况

思路

分析完题目之后,可以看到,出题者很贴心的给了 a 标签两个 class:

  • icon-delete
  • css-tips

意图很明显,icon-delete 用于实现把文字替换成图标的功能,而 css-tips 用于实现气泡提示效果。 那么首先把比较简单的功能完成。

icon-delete

这里可以通过把字体大小设置为 0,然后通过设置背景的方式来完成图标化:

.icon-delete{
    display: block;
    width: 30px;
    height: 30px;
    font-size: 0px;
    background: url(./del.png) no-repeat center;
    background-size: cover;
}

这样就完成了这个简单的功能,效果如下:

img-01

css-tips

这个功能较为复杂,我们先不考虑各种场景,只单独的把这个气泡提示做出来。

给元素附加挂件的效果,这里我们可以通过 beforeafter 伪类来做。

由于气泡浮于元素上方,所以这里第一反应是要用到绝对定位:

.css-tips {
    position: relative;
}

.css-tips[data-title]::before,
.css-tips[data-title]::after {
    content: "";
    width: 20px;
    height: 20px;
    display: block;
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: 100%;
    transform: translate(-50%, -20px);
    background: #373c42;
}

到这里,我们成功的将 beforeafter 定位到了按钮的正上方:

img-02

接下来分别对 beforeafter 进行调整,这里我们使用 before 来做为装填文字的容器,使用 after 作为气泡下方的箭头。

before

首先由于我们并不知道提示文字长度,所以不必设置高宽。 那么 before 就没什么好注意的了。(由于提示消息长度不固定,所以可能出现提示文本过长的情况,这样应该能够将文本换行,这个情景后面再提)

.css-tips {
    position: relative;
}

.css-tips[data-title]::before,
.css-tips[data-title]::after {
    content: "";
    display: block;
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: 100%;
    transform: translate(-50%, -20px);
    background: #373c42;
}

.css-tips[data-title]:before {
    content: attr(data-title);
    border-radius: 3px;
    padding: 6px 10px;
    line-height: 18px;
    text-align: left;
    background-color: #373c42;
    color: #fff;
    font-size: 12px;
    font-style: normal;
    white-space: nowrap;
  }

效果如下:

img-03

after

上图可以看到,在气泡中间有一块黑色的遮挡,这个就是 after 了,只需要将它画成倒三角,并且贴在 before 的下方即可。

画倒三角的方法有很多,这里简单介绍一个最省事的。

我们给一个元素添加一个 60px 的 border,然后将上下左右设置成不同的颜色:

.border{
    border: 6px solid #fff;
    border-top-color: black;
    border-left-color: red;
    border-bottom-color: blue;
    border-right-color: yellow;
}

img-04

可以看到,四个角刚好是四个三角形,那么需要什么样的三角形,将其他的 color 设置为 transparent 即可,这里也用这种方法来画 after

.css-tips[data-title]::before,
.css-tips[data-title]::after {
    content: "";
    display: block;
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: 100%;
    transform: translate(-50%, -20px);
    background: #373c42;
}

.css-tips[data-title]:after {
    content: "";
    width: 0;
    height: 0;
    margin-bottom: -12px;
    overflow: hidden;
    border: 6px solid transparent;
    border-top-color: currentColor;
  }

效果如图:

img-05

接下来需要做的就是在 hover 的时候显示气泡,别的时候隐藏即可,没什么好说,最终代码如下:

.icon-delete {
    font-size: 0;
    width: 30px;
    height: 30px;
    background: url(./del.png) no-repeat center;
    background-size: cover;
  }

  .css-tips {
    position: relative;
  }

  .css-tips[data-title]:before,
  .css-tips[data-title]:after {
    content: "";
    display: block;
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: 100%;
    transform: translate(-50%, -20px);
    color: #373c42;
    opacity: 0;
    visibility: hidden;
  }

  .css-tips[data-title]:before {
    content: attr(data-title);
    border-radius: 3px;
    padding: 6px 10px;
    line-height: 18px;
    text-align: left;
    background-color: #373c42;
    color: #fff;
    font-size: 12px;
    font-style: normal;
    white-space: nowrap;
  }

  .css-tips[data-title]:after {
    content: "";
    width: 0;
    height: 0;
    margin-bottom: -12px;
    overflow: hidden;
    border: 6px solid transparent;
    border-top-color: currentColor;
  }

  .css-tips[data-title]:hover:before,
  .css-tips[data-title]:hover:after {
    visibility: visible;
    opacity: 1;
  }

效果如下:

img-06

优化

优化要从两个方面说起:

  • 适应极端场景
  • 体验优化

(其实说白了都是体验优化)

体验优化

我们先来说说体验优化,可以看到上面最后的动图效果非常僵硬,并且鼠标移动过来就立刻出现气泡,假设现在有一个这样的场景:

一个表格,每一行都可以删除,用户从下往上移动鼠标,想要删除第 n 行,但由于鼠标放上去,气泡立马弹出来了,所以这个时候有可能第 n+1 行的气泡弹了出来,并且挡住了第 n 行的按钮,这个时候用户只能重新以不同角度移动过来,这样的体验是相当不友好的。

解决上述场景,我们有一个小技巧:延时

我们让气泡延迟一点出现,这样当鼠标划过的时候就并不会出现气泡,而是停留的时候才会出现,这样就不会造成上述场景了:

.css-tips[data-title]:before,
  .css-tips[data-title]:after {
    content: "";
    display: block;
    position: absolute;
    z-index: 1;
    left: 50%;
    bottom: 100%;
    transform: translate(-50%, -20px);
    color: #373c42;
    opacity: 0;
    transition: 0.15s 0.15s;
    visibility: hidden;
  }

效果如下:

img-07

到这里其实气泡也显得并不那么生硬了,当然如果愿意的话可以再增加一个过渡,显得更加灵动:

.css-tips[data-title]:hover:before,
.css-tips[data-title]:hover:after {
    visibility: visible;
    transform: translate(-50%, -10px);
    opacity: 1;
  }

效果如下:

img-08

适应极端场景

接下来我们聊聊极端场景,及题目中提到的边界问题。

当这个按钮出现在屏幕顶端的时候,如果气泡依然是从顶部出现,很明显将会部分或者完全不可见。解决这个问题,我想到的方案是给气泡添加方向属性,通过从不同方向出现来避免对应的边界。

我们可以为元素添加一个 data-title-dir 属性,默认从上方弹出气泡,如果设置方向,则从指定方向弹出:

属性 描述 使用
data-title-dir 规定提示的方向,可以取值top(默认)、right、bottom、left <span class="css-tips" data-title="删除" data-title-dir="right">提示右</span>

实现原理上面已经分析过了,就不再赘述:

/* right */
.css-tips[data-title][data-title-dir="right"]:before,
.css-tips[data-title][data-title-dir="right"]:after{
    left: 100%;
    top: 50%;
    bottom: auto;
    transform: translate(20px, -50%);
}
.css-tips[data-title][data-title-dir="right"]:after {
    margin: 0;
    margin-left: -12px;
    border-color: transparent;
    border-right-color: currentColor;
}
.css-tips[data-title][data-title-dir="right"]:hover:before,
.css-tips[data-title][data-title-dir="right"]:hover:after {
    visibility: visible;
    transform: translate(10px, -50%);
    opacity: 1;
}

/* bottom */
.css-tips[data-title][data-title-dir="bottom"]:before,
.css-tips[data-title][data-title-dir="bottom"]:after{
    left: 50%;
    top: 100%;
    bottom: auto;
    transform: translate(-50%, 20px);
}
.css-tips[data-title][data-title-dir="bottom"]:after {
    margin: 0;
    margin-top: -12px;
    border-color: transparent;
    border-bottom-color: currentColor;
}
.css-tips[data-title][data-title-dir="bottom"]:hover:before,
.css-tips[data-title][data-title-dir="bottom"]:hover:after {
    visibility: visible;
    transform: translate(-50%, 10px);
    opacity: 1;
}

/* left */
.css-tips[data-title][data-title-dir="left"]:before,
.css-tips[data-title][data-title-dir="left"]:after{
    left: auto;
    right: 100%;
    top: 50%;
    bottom: auto;
    transform: translate(-20px, -50%);
}
.css-tips[data-title][data-title-dir="left"]:after {
    margin: 0;
    margin-right: -12px;
    border-color: transparent;
    border-left-color: currentColor;
}
.css-tips[data-title][data-title-dir="left"]:hover:before,
.css-tips[data-title][data-title-dir="left"]:hover:after {
    visibility: visible;
    transform: translate(-10px, -50%);
    opacity: 1;
}

效果如下:

img-09

还有一个场景就是提示文本过长。这个问题我们也可以像边界问题那样给元素添加一个 data-title-muti 属性来指定是否换行:

属性 描述 使用
data-title-muti 适用于提示文本较多的情况,自动换行 <span class="css-tips" data-title="删除" data-title-muti>提示</span>
/* muti */
.css-tips[data-title][data-title-muti]:before {
    width: 150px;
    white-space: normal;
}

效果如图:

img-10

结束语

对于这个需求,其实还可以继续优化扩展,比如添加状态,不同状态有不同的颜色来提示,比如结合 js 添加回调等等。

当然这些功能早就有很多开源库做了,而且做得很好很强壮,所以在日常开发中,便不必在重复造轮子了,作为学习了解原理和设计思路可以,但切记不要舍本逐末。

这里是在线 demo