vortesnail / blog

:blue_book: 个人技术小文章,旨在对知识的总结,能帮助到别人就更好啦。
551 stars 45 forks source link

图文并茂总结7个工作中常用的css3案例,带你了解冷门却实用的特性! #15

Open vortesnail opened 4 years ago

vortesnail commented 4 years ago

前言

最近看了《css 揭秘》这本神书,学到了很多技巧,工作中遇到的一些问题在这本书中得到了很好的解决。这篇文章也不是把书中的内容随便抄一下就拿来给大家说,我会在此基础上向外扩展一些,请求大家理性评论!另外,有几个案例是我工作中遇到过的比较棘手的问题的解决方案,总结出来让大家有个印象,万一哪天你也要实现同样的需求呢?😁

如果对大家有帮助,请各位老爷务必留下你宝贵的 star🌟,这是我的 github/blog

我会从以下 4 个纬度介绍各个案例:

DJ, drop the beat! 🎤

使用变量 currentColor 减少重复代码

需求描述

1

尝试方案

我相信任何一个前端开发者都能很快实现这个需求,不知道大家怎么样的,我在之前一直都是以下代码快速实现:

index.html 文件 :

<div class='good'>请给我点赞</div>

index.scss 文件 :

.good {
  padding: 3px 6px;
  color: #333;
  background: rgba(#333, 0.1);
  border: 1px solid #333;
  border-radius: 3px;
  cursor: pointer;

  &:hover {
    color: #0069ff;
    background: rgba(#0069ff, 0.1);
    border: 1px solid #0069ff;
  }

  &.good-click {
    color: #0069ff;
    background: rgba(#0069ff, 0.1);
    border: 1px solid #0069ff;
  }
}

index.js 文件 :

const goodBtn = document.querySelector('.good')

goodBtn.addEventListener('click', () => {
  if (goodBtn.classList.contains('good-click')) {
    goodBtn.classList.remove(['good-click'])
    return
  }
  goodBtn.classList.add(['good-click'])
})

是的,就是那么朴实无华,缺点也暴露无遗:

针对这个问题我们直接使用预处理器(SASS/LESS)的变量就完事了:

index.scss 文件 :

$color: #333;
$hoverColor: #0069ff;

.good {
  padding: 3px 6px;
  color: $color;
  background: rgba($color, 0.1);
  border: 1px solid $color;
  border-radius: 3px;
  cursor: pointer;

  &:hover {
    color: $hoverColor;
    background: rgba($hoverColor, 0.1);
    border: 1px solid $hoverColor;
  }

  &.good-click {
    color: $hoverColor;
    background: rgba($hoverColor, 0.1);
    border-color: $hoverColor;
  }
}

咋一看已经是很好的实现方式了,但是也有缺点:

这个时候,css 原生变量 currentColor 即可大显身手了。

改进方案

变量 currentColor 能拿到本元素的 color 属性的值,如果没有显示设置,拿的将会是父元素的 color 属性的值,由此类推。借助这个特性,我们即可优化上述代码:

index.scss 文件 :


.good {
  position: relative;
  padding: 3px 6px;
  color: #333;
  border: 1px solid currentColor;
  border-radius: 3px;
  cursor: pointer;

  &::before {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: currentColor;
    opacity: 0.1;
    content: '';
  }

  &:hover {
    color: #0069ff;
  }

  &.good-click {
    color: #0069ff;
  }
}

现在看起来是不是好多了,我每次要更改颜色,只需要将此元素的 color 属性更改即可,不需要再重新写一堆重复的属性,当然,原生的 css 以及功能强大的 sass/less 都还是无法支持 rgba(currentColor, 0.1) 这种写法,我还去官方提了个 issue ,官方也给了很好的回复,有兴趣的同学可以看看。

所以现在我只能添加一个 ::before 来模拟背景色块,真正做到只改 color 属性,即可改全部颜色。

现在大家就可以在 React 或 Vue 中通过状态来控制改变颜色的类名添加与否并设置 color 属性,以此来完美地进行颜色的快速变换了~

多说一句,如果我们直接使用 ele.style.color = '#fff' 这种操作 dom 的形式来改变字体颜色,在未使用 currentColor 的情况下,我们是没法操作伪元素的,也就改变不了伪元素的 background 、 border-color 等其他与字体颜色一致的属性,所以这时候 currentColor 的优势就更明显了~

在线演示

使用变量 currentColor 减少重复代码 - codepen

完美的带小箭头的聊天框

需求描述

4

尝试方案

给该元素加个伪元素,背景色与聊天框背景色一致,再给该伪元素添加上、左同色边框,绝对定位调整位置,再来个 border-top-left-radius: 3px ,最后 transform: rotate(-45deg) 旋转一下,代码如下:

index.html 文件 :

<div class="chat-box">大家好,我是 vortesnail,如果大家喜欢我的文章,对大家有所帮助,麻烦给个小小的赞支持一下,谢谢😊</div>

index.scss 文件 :


.chat-box {
  position: relative;
  max-width: 200px;
  padding: 10px;
  color: #faae43;
  background: #fff9ed;
  border: 1px solid #ffc16b;
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(250, 174, 67, 0.8);
}

.chat-box::before {
  position: absolute;
  top: 20px;
  left: -6px;
  width: 10px;
  height: 10px;
  background: #fff9ed;
  border-color: #ffc16b;
  border-style: solid;
  border-width: 1px 0 0 1px;
  transform: rotate(-45deg);
  content: '';
  /* box-shadow: 0 2px 6px rgba(250, 174, 67, 0.8); */
}

可以达到现在下面的效果:

5

细心的你一定发现了,这个小三角指示箭头是没有阴影的,如果我给其加上与主体元素一致的 box-shadow ,又因为这个属性不能像 border-color 一样分别给各边设置为透明,结果就会像下面这样:

6

这已经是无法满足具有相同阴影的要求了,而且大家如过想一下就知道,在我主体元素不设 padding 或设的很小的情况下,小三角的背景色会将我们的文字挡住,这种方案直接宣布失败!

改进方案

针对以上的问题,我们进行一步步改造。

首先,我们考虑到主体元素不设置 padding 的情况,为了防止内容被我们的小三角背景色覆盖,我们可通过加一个伪元素 ::before ,利用 border 来画成一个三角形,代码如下:

.chat-box {
  // 其他样式

  &::before {
    position: absolute;
    top: 20px;
    left: -8px; // 注意,这里做了略微调整
    width: 0;
    height: 0;
    border-color: transparent #fff9ed transparent transparent;
    border-style: solid;
    border-width: 8px 8px 8px 0;
    content: '';
  }
}

现在是这个样子:

7

注意,这里的小三角已经是没有右边部分的了,解决了我们不设置 padding 时导致内容被遮挡的问题。但是这样就没有办法实现边框,毕竟你已经是使用边框做出来的三角形了。

那我们就再使用一个伪元素呗, ::after 安排上了。接下来为大家提供一个思路:采用尝试方案中的方式再画一个正方形做旋转,但是不为其设置背景色,只设置其 border ,调整下位置即可。

.chat-box {
  // 其他样式

  &::before {}

  &::after {
    position: absolute;
    top: 22px;
    left: -7px;
    width: 10px;
    height: 10px;
    /* border-color: inherit transparent transparent inherit; */
    border-color: transparent;
    border-style: solid;
    border-width: 1px;
    border-top-color: inherit;
    border-left-color: inherit;
    border-top-left-radius: 3px;
    transform: rotate(-45deg);
    content: '';
  }
}

可以看到,代码中我设置上和左的 border-color 为 inherit ,表示继承父级元素的 border-color ,因我注释那部分的写法不被识别,所以我们新增了几行代码实现,利用 inherit 可以在颜色更改时少写颜色值的重复代码,与 currentColor 想要达到的目的是一致的。

现在,越来越接近我们的目标:

8

这里小三角还是没有阴影,因为 box-shadow 并不会作用于伪元素,解决方案就是使用 filter 属性, drop-shadow 接受的参数和 box-shadow 基本一致,我们替代它即可:

// box-shadow: 0 2px 6px rgba(250, 174, 67, 0.8);
filter: drop-shadow(0 2px 6px rgba(250, 174, 67, 0.8));

现在已经完美实现~

在线演示

实现一个完美的带小箭头的聊天框 - codepen

利用 grid 实现完美的水平铺满、间隔一致的自适应布局

需求描述

2

尝试方案

这个问题从我入职第一份工作之后困扰了我接近半年,我基本还是惯性思维,一眼看过去就是 flex弹性盒子 一把梭,于是我有了以下这种方案:

index.html 文件 :

<body>
  <div class="father">
    <div class="child">Child1</div>
    <div class="child">Child2</div>
    <div class="child">Child3</div>
    <div class="child">Child4</div>
    <div class="child">Child5</div>
    <div class="child">Child6</div>
    <div class="child">Child7</div>
    <div class="child">Child8</div>
    <div class="child">Child9</div>
    <div class="child">Child10</div>
  </div>
</body>

index.scss 文件 :


.father {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: flex-start;
  width: 100%;
  padding: 10px 0 10px 20px;

  .child {
    margin-right: 14px;
    margin-bottom: 14px;
    // 其他卡片样式
  }
}

可以看到,我会为每个子元素都设置 margin-top 以及 margin-right 来固定他们之间的间距,但是因为每一行最右边的子元素也有 margin-right ,为了补偿这个,我就将父元素的 padding-right 去掉了,这样做的坏处太多了,需要自己去计算,做补偿,而且右边有时候容纳不下一个完整的子元素,就会导致换行而留下一大片白。。 3

为了能用弹性盒子做到想要的效果,我已经把阮一峰老师的Flex 布局教程:语法篇看烂了。。根本没法实现最佳最想要的效果,以上只是我多次尝试之后唯一能接受的方案,我就这么个方案用了好多次。

直到有一天,我又遇到了这种布局需求,我辛辛苦苦用 js 去硬算他们之间的间距,算是实现了想要的效果,但是真的非常繁琐,我就受不了了。这个时候我又偶遇了阮一峰老师的CSS Grid 网格布局教程,谢天谢地,采用 Grid 可完美实现以上需求!

改进方案

Flex 布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是一维布局。Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是二维布局。Grid 布局远比 Flex 布局强大

首先我们需要给容器指定为 grid 网格布局,就像 flex 一样:

.father {
  display: grid;
}

接着要为其划分列数, grid-template-columns 属性可定义每一列的列宽,假如代码如下,我们将容器划分成 3 列,每列宽度为容器的 100px :

.father {
  grid-template-columns: 100px 100px 100px;
}

但是这个时候我们看到的效果会是下面这样: 3 0

子元素并没有把父元素占满,这显然不是我们想要的效果,幸亏有 repeat() 函数帮助我们简化重复值, 它接受两个参数,第一个参数是重复的次数,第二个参数是所要重复的值 。上面的代码完全可用以下代码代替:

.father {
  grid-template-columns: repeat(3, 100px);
}

当然,这只是第一步,我们还需要借助 auto-fill 关键字,在我们需要容器能尽可能容纳子元素时,就需要用到它,表示自动填充,我的理解是 repeat() 接受了这个 auto-fill 的参数时,会去自动计算容纳的数量,就好像你事先算出来这个容器能容纳多少子元素,然后把这个“多少”传给该函数一样。这时候代码如下:

.father {
  display: grid;
  grid-template-columns: repeat(auto-fill, 100px);
}

现在图形如下,已经越来越接近我们的目标了: 3 1

但是很显然,右边有一个空隙, justify-content 属性拯救我们,它整个内容区域在容器里面的水平位置,当我设置其为 space-between 时,意味着子元素之间的间隔相等,而子元素与容器边框之间没有间隔

不过子元素之间还是没有间隔,简单设置一下属性 gap 即可,它是 column-gap 和 row-gap 的合并简写,分别表示列与列行与行之间的间距,现在代码如下:

.father {
  display: grid;
  grid-template-columns: repeat(auto-fill, 100px);
  justify-content: space-between;
  gap: 14px 4px;
}

由此简单的几行代码就已经完美实现了我们想要的效果: 3 2

不过 grid 网格布局的兼容性不是很好,点此查看支持的浏览器列表~

在线演示

利用 grid 实现完美的水平铺满、间隔一致的自适应布局 - codepen

间距可调整的虚线框

需求描述

尝试方案

其实我一直很迷惑为什么 css3 不提供一些能调整虚线框的必要属性,默认的 dash-border 经常会和 ui 所需要的虚线框要求会不一致,既然官方不支持,我们只能自己寻找一些解决方案。

这个解决方案看似很多,其实每一种解决方案都会有一定的局限性,选择最合适的就是最好的,具体我列出了以下几条:

index.html 文件 :

<body>
  <div id="box">
    <div class="border-horizontal top"></div>
    <div class="border-vertical right"></div>
    <div class="border-horizontal  bottom"></div>
    <div class="border-vertical left"></div>
    I am vortesnail, now i try to make a custom dashed border!
  </div>
</body>

index.scss 文件 :

$border-color: #ccc;
$border-dashed-unit-width: 8px;
$border-dashed-unit-height: 1px;
$stroke-rate: 50%;

body {
  #box {
    width: 400px;
    background: #fff;
    padding: 10px;
    box-sizing: border-box;
    position: relative;

    .border-horizontal {
      position: absolute;
      width: 100%;
      height: $border-dashed-unit-height;
      left: 0;
      background-image: linear-gradient(
        to right,
        $border-color 0%,
        $border-color $stroke-rate,
        transparent $stroke-rate
      );
      background-size: $border-dashed-unit-width $border-dashed-unit-height;
      background-repeat: repeat-x;
    }

    .border-vertical {
      position: absolute;
      width: $border-dashed-unit-height;
      height: 100%;
      top: 0;
      background-image: linear-gradient(
        to bottom,
        $border-color $stroke-rate,
        $border-color $stroke-rate,
        transparent $stroke-rate
      );
      background-size: $border-dashed-unit-height $border-dashed-unit-width;
      background-repeat: repeat-y;
    }

    .top {
      top: 0;
    }

    .right {
      right: 0;
    }

    .bottom {
      bottom: 0;
    }

    .left {
      left: 0;
    }
  }
}

其实其思想很简单,就是 4 个矩形,每个矩形加上渐变背景,并 repeat 即可模拟虚线效果,其间距、比例可根据我们设定的变量去调整。

但是它的弊端非常大,就是无法调整 border-radius ,即没有圆角!这里向大家展示只是为了抛砖引玉,万一你有更好的想法,或者你不需要圆角,那就可以用这个方案。效果如下: 10

改进方案

其实我们借助 svg 就能比较不错的实现自定义虚线框,如果不想自己写 svg 的朋友可以直接在这个网站进行调整和生成:Customize your CSS Border ,但如果你稍微了解一些 svg 的用法,也完全可以自己实现,如果你想了解一下,可阅读这篇文章:SVG入门—如何手写SVG

代码如下: index.html 文件

<body>
  <div id="box">I am vortesnail, now i try to draw a dashed border box.</div>
</body>

index.scss 文件 :

body {
  #box {
    width: 400px;
    border-radius: 4px;
    padding: 10px;
    background-image: url('data:image/svg+xml,\
    <svg xmlns="http://www.w3.org/2000/svg">\
      <rect width="100%" height="100%" rx="4" ry="4" style="stroke: black; stroke-width: 2px; fill: none; stroke-dasharray: 8px 5px; stroke-dashoffset: 10px;"/>\
    </svg>');
  }
}

可通过 stroke-width 调整虚线框宽度, stroke-dasharray 调整比例及长度、间距, stroke-dashoffset 调整偏移值。

tips:svg 方案在一些比较老的安卓机上会不兼容,即使在新的机型上,也会出现一些表现差异,虽然在 web 端支持 svg 的浏览器上表现是正常的,但若考虑到移动端用户群体时,使用请慎重。

在线演示

实现一个间距可调的虚线框 - codepen

自定义复选框

需求描述

尝试方案

很显然,当我们使用默认的 input.checkbox 方案时,是没有办法改变其样式的,而且在不同浏览器之间,其表现也不一致,代码如下:

index.html 文件 :

<body>
  <div class="checkbox-container">
    <input type="checkbox" id="apple">  
    <label for="apple">苹果</label>
  </div>
  <div class="checkbox-container">
    <input type="checkbox" id="banana" disabled>  
    <label for="banana">香蕉</label>
  </div>
  <div class="checkbox-container">
    <input type="checkbox" id="watermelon">  
    <label for="watermelon">西瓜</label>
  </div>
</body>

index.scss 文件 :

.checkbox-container {
  display: flex;
  align-items: center;

  input[type='checkbox'] {
    & + label {
      color: #333;
    }
  }

  input[type='checkbox']:disabled {
    &+ label {
      color: #c6c6c6;
    }
  }
}

在 chrome 下表现为: 12

在 firefox 下表现为: 13

在 safari 下与在 firefox 下一致。

如果任由这种情况的发生,你们 ui 可能会找产品经理和你打一架~🐶

改进方案

我们可以在不改变上面尝试方案中的 html 结构,只需 css 即可做到!给每一个 label 标签添加一个伪元素 ::before 作为复选框!

首先,我们给这个伪元素添加必要样式,使其符合 ui 的设计:

input[type="checkbox"] {
  & + label {
    display: flex;
    align-items: center;
  }

  & + label::before {
    box-sizing: border-box;
    content: "\a0"; /* 不换行空格 */
    width: 13px;
    height: 13px;
    margin-right: 4px;
    border-radius: 2px;
    border: 1px solid #333;
  }
}

现在的拙劣效果如下: 14

我们发现,默认的复选框还是存在的,我们怎么做到将其隐藏而不破坏其可访问性呢(即不能使用 display: none )?

input[type="checkbox"] {
  position: absolute;
  clip: rect(0, 0, 0, 0);

  & + label {...}

  & + label::before {...}
}

以上隐藏的方案引用至 css揭秘151页

现在点击我们自定义的复选框是没有任何效果的,接下来借助 css 的相邻兄弟选择器对 checked 状态、disabled 状态分别设置样式即可:

input[type="checkbox"]:checked {
  & + label::before {
    background-color: #1890ff;
    background-image: url("https://s1.ax1x.com/2020/10/11/0cUbi4.png");
    background-repeat: no-repeat;
    background-size: 100% 100%;
    border: none;
  }
}

input[type="checkbox"]:disabled {
  & + label {
    color: #868686;
    cursor: not-allowed;
  }

  & + label::before {
    border-color: #868686;
  }
}

可以看到,经过此番处理后,我们可以完全自主地去设计复选框样式,比如我在上面选中时,呈现了一张对勾的图片: background-image: url("https://s1.ax1x.com/2020/10/11/0cUbi4.png"); ,这极大地方便了我们对其呈现形式的掌控。

除此之外,你还可以设置 input[type="checkbox"]:focus 时的样式哦,赶快试试吧!

在线演示

自定义复选框 - codepen

交互式图片对比效果

需求描述

尝试方案

css3 中引入了 resize 属性,该属性可以不通过 js 就可以改变设置该属性的元素的宽度 width ,大家一定使用过 textarea 标签把?那个右下角的可拖拽更改长宽的东西就是该属性的功劳。实际上,所有标签都可以设置该属性!

于是,简单的几段代码就可以达到交互式图片对比效果,虽然和我们想要实现的效果有点差异,但如果要求不高的话,就采用它吧:

index.html 文件 :

<div class="image-slider">
  <div class="before-container">
    <img src="https://img3.doubanio.com/view/photo/l/public/p2622600072.webp" alt="before">
  </div>
  <img src="https://img9.doubanio.com/view/photo/l/public/p2380745925.webp" alt="after">
</div>

index.scss 文件 :

.image-slider {
  position: relative;

  img {
    display: block;
    width: 720px;
    user-select: none;
  }

  .before-container {
    position: absolute;
    top: 0;
    left: 0;
    width: 50%;
    max-width: 100%; /* 防止容器宽度拉长至比图片还宽 */
    overflow: hidden; /* 必须不可见 */
    resize: horizontal; /* 赋予水平宽度可拉伸功能 */

    &::before {
      position: absolute;
      right: 0;
      bottom: 0;
      width: 12px;
      height: 12px;
      background: linear-gradient(-45deg, #000 50%, transparent 0);
      background-clip: content-box;
      cursor: ew-resize;
      content: '';
    }
  }
}

我们利用一个伪元素 ::before 来对右下角的拉伸图标进行覆盖,以便于自定义样式,现在效果如下: 15

这个方案弊端就是右下角的可拖拽图标无法更改位置和大小,即使我们利用伪元素去覆盖,但是和我们需求中所需要的效果也相差甚远,于是我们不得不借助 js 了!

改进方案

在上述方案中增加一个 span 标签用于画我们的拖拽竖条,紧接着按照上述方案先将两张照片的位置和大小调好:

index.html 文件 :

<body>
  <div class="image-slider">
    <div class="before-container">
      <img src="https://img3.doubanio.com/view/photo/l/public/p2622600072.webp" alt="before">
    </div>
    <img src="https://img9.doubanio.com/view/photo/l/public/p2380745925.webp" alt="after">
    <span class="handler"></span>
  </div>
</body>

index.scss 文件 :

body {
  .image-slider {
    position: relative;

    img {
      display: block;
      width: 520px;
      user-select: none;
      pointer-events: none;
    }

    .before-container {
      position: absolute;
      left: 0;
      top: 0;
      width: 50%;
      overflow: hidden;
    }
  }
}

现在效果如下:

16

初步的效果出来了,接下来增加可拖拽改变水平宽度的功能。首先需要先在两张图片交际出添加一个竖形的条状,用于拖拽位置,更改 class='handler' 样式:

.handler {
  position: absolute;
  top: 0;
  left: 50%;
  display: block;
  width: 4px;
  height: 100%;
  background: rgba(0, 0, 0, 0.4);
  transform: translateX(-50%);
  cursor: ew-resize;
}

注意中间的透明竖形条状即是我们可拖拽的位置:

17

接下来写我们的 js 脚本,首先通过原生 js 方法找到三个 dom 节点:

index.js 文件 :

const imageSlider = document.querySelector(".image-slider");
const beforeContainer = document.querySelector(".before-container");
const handler = document.querySelector(".handler");

然后我们还需要获得 image-slider 这个最外层元素相对页面左边的距离,我们定义为变量 leftX ,并在鼠标于 handler 元素上按下时计算该值:

let leftX;

handler.onmousedown = (e) => {
  leftX = e.pageX - handler.offsetLeft;
};

用一张图来解释说明下:

18

然后在给 window 对象添加一个 mousemove 的监听事件,该回调用于改变 handler 位置和 before-image 的宽度:

handler.onmousedown = (e) => {
  leftX = e.pageX - handler.offsetLeft;
  window.addEventListener('mousemove', moveHandler)
};

const moveHandler = e => {
  const beforeWidth = e.pageX - leftX;
  const imageSliderWidth = imageSlider.offsetWidth;

  if (beforeWidth >= 0 && beforeWidth <= imageSliderWidth) {
    handler.style.left = beforeWidth + 'px';
    beforeContainer.style.width = beforeWidth + 'px';
  }
}

目前为止,我们拖拽 handler 已经能实现所需的效果,但是无法停止,我们需要添加一个鼠标按键抬起的监听事件,需要注意的是不能在 handler 上添加,比如: handler.onmouseup ,必须在 window 对象上添加,具体为什么,大家可以动手试试就知道了:

window.onmouseup = (e) => {
  window.removeEventListener('mousemove', moveHandler)
};

到此为止,就已经完美实现了该效果,大家还可以在 handler 上做更多工作,使其用户体验达到更好~

在线演示

交互式图片对比效果 - codepen

透明度渐变层代替滚动条提示

需求描述

19

尝试方案

首先很明确的是,先将内容滚动条搞出来:

index.html 文件 :

<body>
  <div class="content-wrapper">
    <header>目录</header>
    <div class="list-wrapper">
      <ul>
        <li>如何长高</li>
        ...省略
      </ul>
    </div>
  </div>
</body>

index.scss 文件 :

body {
  .content-wrapper {
    width: 248px;
    padding: 20px 0;
    background: #fafafa;

    header {
      padding: 0 20px 0 24px;
      font-weight: 500;
      font-size: 16px;
    }

    .list-wrapper {
      ul {
        height: 400px;
        padding: 0 20px 0 24px;
        overflow-y: auto;
        color: #595959;

        li {
          padding: 6px 0;
          font-size: 14px;
          list-style: none;
        }
      }
    }
  }
}

现在效果如下: 20

接下来为滚动范围的顶部和底部都先加上我们所需要的渐变层,通过父容器的 ::before 和 ::after 伪元素来实现,同时动态为 list-wrapper 这个元素增加两个类名,用于控制渐变层的显隐:

.list-wrapper {
  position: relative;

  &::before {
    position: absolute;
    right: 0;
    left: 0;
    z-index: 1;
    height: 60px;
    content: '';
    pointer-events: none;
  }

  &.top-gradient::before {
    top: 0;
    background: linear-gradient(to bottom,#fafafa,hsla(0,0%,98%,.5) 84%,hsla(0,0%,98%,.13));
  }

  &::after {
    position: absolute;
    right: 0;
    left: 0;
    z-index: 1;
    height: 60px;
    content: '';
    pointer-events: none;
  }

  &.bottom-gradient::after {
    bottom: 0;    
    background: linear-gradient(to top,#fafafa,hsla(0,0%,98%,.5) 84%,hsla(0,0%,98%,.13));
  }
}

但是我们想要达到的效果是:一旦滚动条不是最顶部,顶部就要有渐变层;一旦滚动条不是最底部,底部就要有渐变层。现在完全是写死在两头,需要通过简单的 js 脚本来判断 ul 元素的滚动条的位置:

index.js 文件 :

const listWrapper = document.querySelector(".list-wrapper");
const ul = document.querySelector("ul");

const onScroll = (e) => {
  // 滚动条是否在顶部
  if (e.target.scrollTop > 0) {
    listWrapper.classList.add("top-gradient");
  } else {
    listWrapper.classList.remove("top-gradient");
  }
  // 滚动条是否在底部
  if (e.target.scrollHeight - e.target.scrollTop !== ul.offsetHeight) {
    listWrapper.classList.add("bottom-gradient");
  } else {
    listWrapper.classList.remove("bottom-gradient");
  }
};

ul.addEventListener("scroll", onScroll);

最后再将原生滚动条隐藏,OK!

ul {
  ...
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE10+ */

  &::-webkit-scrollbar { 
    display: none; /* Chrome */
  }
}

顺带说一句,在 codepen 中滚动条隐藏不了,本地调试时可以,我也不晓得啥问题~

改进方案

实际上上述方案是我看《css揭秘》之后想到的,在这本书中,讲到了利用两层 background 以及 background-attachment 属性来进行渐变层的实现,但是我按书中实现之后,发现效果并不完美,甚至可以说有很大缺陷!我想了好久还是觉得用 js 方便,css 看起来是无法实现我想要的效果的!

所以上述方案就是最终改进方案,《css揭秘》中的方法我实在不敢认同,不过关于 background-attachment 属性的介绍倒是给我学到了~

在线演示

透明度渐变层代替滚动条提示 - codepen

结语

虽然标题写了是 css3,但是还是难免涉及到了 js,我的目的是希望有同类需求的小伙伴能通过本篇文章得到帮助。欢迎各位理性讨论~如果有更好的方法,请大佬们务必不吝赐教!如果你已经看到此处,干脆点个赞再走吧~

如果对大家有帮助,请各位老爷务必留下你宝贵的 star🌟,这是我的 github/blog

ytJoinNow commented 3 years ago

大拇指

Brandy2333 commented 2 years ago

太牛了

521sy commented 1 year ago

简直就是救星,正好项目中需要。

vortesnail commented 1 year ago

简直就是救星,正好项目中需要。

哪个场景啊?