[ 基础系列 ] - CSS 小测 05

429 阅读6分钟

系列文章

说在前面

本篇是张鑫旭老师的 CSS基础测试6 的阅后笔记。(Wing MengXboxYanSeasonley 同学的回答都很优雅)

题目

先来看看题目。

下图是 chrome 浏览器下 <audio> 播放器效果,请尝试使用纯 CSS 完成样式与布局效果(切换状态无需实现)。

img-00

需求:

  • 布局自适应
  • 尺寸和取色
  • 纯 CSS 图标
  • 代码友好

思路

说实话,这题目看上去还是有一定难度的,特别是通过纯 CSS 实现图标。

那么很明显,今天的重点就在这里了:纯 CSS 绘图。

播放按钮

在前面的 CSS 小测 - 02 中有简单提到过通过 border 来绘制三角形的方法,我们先画出来看看:

.triangle {
    border: 14px solid transparent;
    border-left-color: #000;
}

效果如下:

img-01

可以看到,这个三角形和 <audio> 中的三角形有一定的区别,这是一个等腰直角三角形,而 <audio> 中的三角形是一个等边三角形。

我们再来看看一个正方形的 border

.triangle {
    border-top: 80px solid deeppink;
    border-left: 80px solid deepskyblue;
    border-right: 80px solid yellowgreen;
    border-bottom: 80px solid #000;
}

效果如下:

img-02

现在我们去掉其中一个 border,这里选择 border-right,再来看看效果:

img-03

到这里就比较明了了,只要让 border-left 的长度等于等腰三角形的高(由于这里的高是个无理数,只能取近似值),然后隐藏上下的 border即可。

.triangle {
    border-top: 80px solid transparent;
    border-bottom: 80px solid transparent;
    border-left: 138.56px solid #000;
}

效果如下:

img-04

进度按钮

实现这样的进度条按钮,用 input[type="range"] 是非常好的,仅需要自己手动重置样式即可:

<input type="range" class="slidebar" />
.slidbar {
    -webkit-appearance: none;
    height: 0.25em;
    font-size: 1em;
    color: inherit;
    background: currentColor;
    border-radius: 0.25em;
}
  .slidbar::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 0;
    height: 0;
    border: 0.375em solid;
    border-radius: 50%;
    box-shadow: 50vw 0 0 50vw rgba(255, 255, 255, 0.5);
}

效果如下:

img-05

声音按钮

纯 css 实现图标里,声音按钮应该是本题中最难的部分了。

开发这种不规则的图标,一般来说,以下几种技巧比较常用:

  • box-shadow
  • linear-gradient/radial-radient
  • clip-path

其中由于 box-shadowlinear-gradient/radial-radient 可以无限叠加的特性,理论上来说可以实现任何图案。这里我们结合 box-shadowclip-path 来实现声音按钮图标。

首先我们来绘制声音图标的这个喇叭,我们把它等比放大一下,相信看过前面通过 border 绘制三角形的同学应该对这个图形的绘制一目了然了。

img-07

没错,我们只需要一个块,然后让其只显示右边的 border 即可:

.volume::after {
    content: "";
    width: 4px;
    height: 6px;
    background: black;
    border: 5px solid #fff;
    border-right-color: black;
  }

效果如下:

img-08

接下来我们通过 box-shadow 来实现右边的音量部分,同样,我们把这个部分等比放大来看看:

img-09

很明显,我们可以先画一个正圆,然后让通过 box-shadow inset 画出一个圆环:

.volume::before {
    content: "";
    background: black;
    border-radius: 50%;
    width: 180px;
    height: 180px;
    box-shadow: 0 0 0 20px black inset, 0 0 0 50px #fff inset;
}

效果如下:

img-10

接下来通过 clip-path 将左半边裁剪掉,再等比缩小回原来的大小,就实现了目标效果:

.volume::before {
    content: "";
    background: black;
    border-radius: 50%;
    width: 18px;
    height: 18px;
    clip-path: inset(0% 0% 0% 50%);
    box-shadow: 0 0 0 2px black inset, 0 0 0 5px #fff inset;
}

效果如下:

img-11

最终将二者拼接起来即可:

<div class="box">
    <div class="volume"></div>
</div>
.box {
    line-height: 56px;
}
.volume::after {
    content: "";
    display: inline-block;
    width: 4px;
    height: 6px;
    background: black;
    border: 5px solid #fff;
    border-right-color: black;
    vertical-align: middle;
}
.volume::before {
    content: "";
    display: inline-block;
    background: black;
    border-radius: 50%;
    width: 18px;
    height: 18px;
    position: absolute;
    margin: 20px 0 0 7px;
    clip-path: inset(0% 0% 0% 50%);
    box-shadow: 0 0 0 2px black inset, 0 0 0 5px #fff inset;
}

效果如下:

img-12

菜单按钮

这个图标没有什么实现上的难度,思路是通过元素本身和两个伪类画三个小点,然后通过定位让其排列成目标状态即可:

<div class="menu"></div>
.menu {
    width: 4px;
    height: 4px;
    background: black;
}
.menu::before,
.menu::after {
    content: "";
    width: 4px;
    height: 4px;
    background: black;
    position: absolute;
}
.menu::before {
    margin-top: -6px;
}
.menu::after {
    margin-top: 6px;
}

效果如下:

img-13

播放容器

到这里,只需要把前面画的按钮拼接起来即可,当然,要注意命名规范,事实上,一开始就应该以模块化的思路进行开发,这里是为了尽量减少各个按钮之间的联系,方便学习思路清晰才从点到面进行开发,实际开发不应以此为例。

<div class="audio">
    <a class="audio-play"></a>
    <span class="audio-time" data-audio-time="now">0:05</span>
    <span class="audio-time" data-audio-time="all">3:27</span>
    <input class="audio-timeline" type="range" />
    <a class="audio-volume"></a>
    <a class="audio-menu"></a>
</div>
body {
    margin: 0;
    height: 100vh;
}

.audio {
    box-sizing: border-box;
    min-width: 330px;
    height: 56px;
    border-radius: 28px;
    padding: 0 14px;
    background: #f1f3f4;
    color: black;
    display: flex;
    align-items: center;
}

.audio-play {
    margin: 0 14px;
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    border-left: 11px solid #000;
}

.audio-time {
    font-size: 15px;
}

.audio-time[data-audio-time="all"]::before {
    content: "/";
    margin: 0 4px;
}

.audio-timeline {
    -webkit-appearance: none;
    cursor: ew-resize;
    height: 14px;
    background: black;
    border: 7px solid #f1f3f4;
    overflow: hidden;
    box-shadow: 0 0 0 5px #ffffff inset;
    mix-blend-mode: darken;
    flex-grow: 1;
}

.audio-timeline::-webkit-slider-thumb {
    -webkit-appearance: none;
    height: 0;
    width: 0;
    border-radius: 50%;
    border: 6px solid black;
    box-shadow: 50vw 0 0 50vw rgba(255, 255, 255, 0.5);
}

.audio-volume {
    width: 4px;
    height: 6px;
    background: black;
    border: 5px solid #f1f3f4;
    border-right-color: black;
    margin: 8px 20px 8px 0px;
}

.audio-volume::after {
    content: "";
    display: block;
    background: black;
    border-radius: 50%;
    width: 18px;
    height: 18px;
    position: absolute;
    clip-path: inset(0% 0% 0% 50%);
    box-shadow: 0 0 0 2px black inset, 0 0 0 5px #f1f3f4 inset;
    margin: -6px 0 0 2px;
    mix-blend-mode: darken;
}

.audio-menu {
    width: 4px;
    height: 4px;
    background: black;
    border-radius: 50%;
    border: 14px solid #f1f3f4;
}

.audio-menu::before,
.audio-menu::after {
    content: "";
    display: block;
    width: 4px;
    height: 4px;
    background: black;
    border-radius: 50%;
    position: absolute;
}

.audio-menu::before {
    margin-top: -6px;
}

.audio-menu::after {
    margin-top: 6px;
}

效果如下:

img-14

优化

CSS 说起优化,首要考虑的都是用户体验,可以考虑键盘和辅助设备,被选中的时候给出提示。

首先重置按钮的 outline

/* reset outline */
[class^="audio-"] {
    outline: none;
}

然后将按钮的指针改变成手指:

/* reset cursor */
.audio-play,
.audio-volume,
.audio-menu {
    cursor: pointer;
}

接下来为可以选中的按钮添加 hoverfocus 效果即可:

/* play hover, focus */
.audio-play::after {
    content: "";
    display: block;
    width: 32px;
    height: 32px;
    position: absolute;
    border-radius: 50%;
    margin: -16px 0 0 -23px;
}

.audio-play:hover::after,
.audio-play:focus::after {
    background: rgba(0, 0, 0, 0.1);
}

/* timeline hover, focus */
.audio-timeline:focus,
.audio-timeline:hover {
    outline: 4px solid rgba(0, 0, 0, 0.1);
    outline-offset: -3px;
}

/* volume hover, focus */
.audio-volume::after {
    content: "";
    display: block;
    width: 32px;
    height: 32px;
    position: absolute;
    border-radius: 16px;
    margin: -13px 0 0 -5px;
    pointer-events: none;
}

.audio-volume:hover::after,
.audio-volume:focus::after {
    background: rgba(0, 0, 0, 0.1);
}

/* menu hover, focus */
.audio-menu:focus,
.audio-menu:hover {
    filter: brightness(0.9);
}

最终效果如下:

img-15

结束语

首先看看张老师总结的关键点:

  • 自适应使用 flex 是比较好的方法,图标按钮尺寸固定,进度条宽度自适应;
  • 进度条使用 input[type="range"] 是非常棒的实现与想法;
  • 自适应不是等比例缩放,而是进度条宽度自适应;
  • 注意视觉还原;
  • 命名上注意上下文,例如 audio- 开头;
  • CSS 图标尽快使用伪元素,更好维护;
  • CSS 图标常用技巧:万能,box-shadow,linear-gradient/radial-radient,clip-path。

然后这里是 在线 demo