用 CSS 从零写一个时间轴效果

用 CSS 从零写一个时间轴效果

时间轴效果介绍

在前端开发中,我们会遇到一些场景特别适合用时间轴来展示,例如下面按照日期时间来记录发生的事件:

用 CSS 从零写一个时间轴效果

还可以做成下面这种比较流行的左右对称卡片风格的时间轴:

用 CSS 从零写一个时间轴效果

如果再稍加装饰,还能有更加高大上的效果:

用 CSS 从零写一个时间轴效果

时间轴效果本质

从上面的图中,我们可以看出来,其实时间轴效果的本质无非就是下面两点:

  • 用一条自上而下的分隔线把可视区域划分为左右两块
  • 分隔线中间有一些圆点代表时间节点,并在其左右展示相关信息

只要掌握了如何画分隔线,并在分隔线上面画小圆点,其实就能够做出来各种各样的时间轴效果了。今天就带领大家实现下面这种可以滚动的时间轴,在项目中非常实用,学会了之后,再也不怕产品提类似的视觉需求了。

用 CSS 从零写一个时间轴效果

从零开始写时间轴

初始状态

为了方便教学,我们先定义下面的空白模板:

用 CSS 从零写一个时间轴效果

其中 HTML 结构如下:

<body>
  <div class="container">
    <div class="debug-area">
      <button onclick="add()">添加活动</button>
    </div>
    <div class="timeline-area">
      <div class="timeline-wrapper">
        <div class="timeline">
        </div>
      </div>
    </div>
  </div>
</body>

初始 CSS 样式为:

.container {
  margin: auto;
  width350px;
  height350px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding0;
  --left62px;
  --color#0d5dff;
}

.debug-area {
  margin30px 0;
}

.timeline-area {
  flex1;
  position: relative;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding-bottom10px;
  padding-right5px;
  border1px solid #ccc;
}

布局结构其实没什么好说的,就是一个固定宽高的 div,用 flex 布局分成上下两部分,上面的区域放了一个 button 用于调试,下面的区域就是用于展示时间轴内容的 div,并加了一个 border 方便大家查看。

你可能注意到上面定义了两个变量 –left和 –color,这是用来作什么的呢?接下来就知道了,请继续往下看。

添加分隔线

分隔线非常关键,应该怎么实现呢?我们先不着急写 CSS 代码,先看 HTML 结构:

<div class="timeline-wrapper">
  <div class="timeline">
  </div>
</div>

timeline-wrapper 是包裹容器,它的宽高是固定的,timeline 是真正的时间轴,当时间轴内容超出容器高度的时候可以上下自由滚动。所以添加下面的 CSS 代码:

.timeline-wrapper {
  flex1;
  overflow-y: auto;
  padding15px 5px 0 0;
}

.timeline {
  position: relative;
}

最关键的地方来了,用伪元素 before来实现分隔线:

.timeline::before {
  content"";
  position: absolute;
  leftvar(--left);
  width1px;
  top20px;
  bottom0;
  background-imagelinear-gradient(
    to bottom,
    rgba(1441561730.660%,
    rgba(25525525500%
  );
  background-position: left;
  background-size1px 5px;
  background-repeat: repeat-y;
}

设置了一个绝对定位,然后距离左边的宽度就是上面用 CSS 变量 –left定义的距离,将其宽度设置为 1px,然后我们给 timeline 加上样式:

.timeline {
  height500px;
  width100%;
}

可以看到,中间的分隔线已经出来了:

用 CSS 从零写一个时间轴效果

可能很多人不知道怎么就出来线了呢?实现虚线的效果有两种,第一种利用 border 设置 dotted 或 dashed 属性:

border-left: 1px dashed rgba(144, 156, 173, 0.6);

另外一种就是利用 background-image 来模拟:

background-imagelinear-gradient(
  to bottom,
  rgba(144, 156, 173, 0.6) 60%,
  rgba(255, 255, 255, 0) 0%
);
background-positionleft;
background-size: 1px 5px;
background-repeatrepeat-y;

后者的可定性更强,可以非常方便的控制虚线的间距。

添加小圆点

线出来之后,怎么加小圆点呢?这个时候就需要补充 HTML 结构了,我们得布局时间轴分隔线两侧的内容,为了方便用 JS 动态的插入内容,我们用 template 来定义时间轴每个项目的 HTML 结构:

<template>
  <div class="timeline-item">
    <div class="timeline-left"></div>
    <div class="timeline-dot"></div>
    <div class="timeline-right"></div>
  </div>
</template>

然后在 head 中增加 add 函数:

<script>
  const nodes = []
  function add({
    const tpl = document.querySelector('template')
    const item = tpl.content.children[0]
    const timeline = document.querySelector('.timeline')
    nodes.forEach(it => it.classList.remove('current'))
    const node = item.cloneNode(true)
    node.classList.add('current')
    nodes.push(node)
    timeline.appendChild(node)
    node.scrollIntoView({ behavior'smooth'block'nearest'inline'center' })
  }
</script>

这个时候,每当我们点击上面的添加活动按钮,就可以动态的复制并插入上面的 DOM 啦!

可以看到 timeline-item 内容区被分为了 timeline-left、timeline-dot 和 timeline-right 三块内容,顾名思义,分别是时间轴内容的左边、圆点和右边,我们先来写圆点的样式:

.timeline-dot {
  leftvar(--left);
  width7px;
  height7px;
  position: absolute;
  border-radius50%;
  box-shadow0 0 0 1px #d8d8d8;
  background: white;
  text-align: center;
  top0;
  line-height40px;
  margin-left: -3.5px;
}

然后对当前处于活动状态的小圆点增加高亮样式:

.timeline-item.current .timeline-dot {
  width10px;
  height10px;
  background-colorvar(--color);
  box-shadow0 0 4px var(--color);
  border1px solid white;
  margin-left: -5px;
}

这个时候 –color变量的作用就清楚了:用于控制主题色。我们反复点击添加活动按钮,可以看到小圆点的效果已经出来了!

用 CSS 从零写一个时间轴效果

设置左右容器

其实到这里,时间轴的雏形已经完成了,剩下的就是根据业务来定义左右两侧的内容。左侧区域我们需要定义其宽度小于 left 的值,否则会超过分隔线:

.timeline-left {
  display: block;
  widthcalc(var(--left) - 7px);
  position: absolute;
  margin-top: -5px;
  text-align: right;
  color#8492a5;
}

右侧区域我们要定义 margin-left 的值大于 left 的值,否则也会超过分隔线:

.timeline-right {
  position: relative;
  margin: -3px 0 10px calc(var(--left) + 15px);
}

为了能让大家看到效果,这里临时给左右两侧都设置了背景色和高度:

.timeline-left {
  background: yellowgreen;
  height50px;
}
.timeline-right {
  background: greenyellow;
  height50px;
}

可以看到效果已经出来了:

用 CSS 从零写一个时间轴效果

填充左右内容

为了做到最开始动图里面的效果,我们丰富一下 template 里面的内容:

<template>
  <div class="timeline-item">
    <div class="timeline-left">
      <div class="start-time">14:00</div>
      <div class="duration">1h</div>
    </div>
    <div class="timeline-dot"></div>
    <div class="timeline-right">
      <div class="title">和詹姆斯打羽毛球</div>
      <div class="content">
        <div class="info">
          <div class="info-no">
            <img src="clock.svg" />
            <span class="info-content">14:00 ~ 15:00</span>
          </div>
          <div class="info-location">
            <img src="location.svg" />
            <span class="info-content">市中心羽毛球场</span>
          </div>
        </div>
        <div class="join">
          <button>
            报名
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

然后补充一些样式:

.timeline-left {
  .start-time {
    font-size16px;
  }
  .duration {
    font-size14px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }
}

.timeline-right {
  .title {
    font-size15px;
    font-weight: bold;
    @extend .ellipsis2;
  }
  .content {
    display: flex;
    flex-direction: row;
    padding5px 0;
    .info {
      font-size15px;
      colorrgba($color: #1f3858, $alpha: 0.6);
      flex1;
      img {
        margin-right5px;
        margin-top: -2px;
        height15px;
      }
      .info-no,
      .info-location {
        display: flex;
        align-items: center;
        margin5px 0;
        .info-icon {
          margin-right5px;
        }
        .info-content {
          flex1;
          @extend .ellipsis2;
        }
        &.hidden {
          display: none;
        }
      }
    }
    .join {
      display: flex;
      justify-content: flex-end;
      align-items: flex-start;
      &.hidden {
        display: none;
      }
    }
  }
}

注意上面的代码是 SCSS ,这是为了方便使用嵌套语法书写,如果想看转换后的 CSS 源码的话可以用下面的命令全局安装 sass 预处理器,然后将上面的 SCSS 代码生成为 CSS:

$ yarn global add sass

$ sass timeline.scss > timeline.css

再加以修饰,最终就能实现最开始的效果啦!

用 CSS 从零写一个时间轴效果

完整代码

HTML 代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Timeline</title>
  <link rel="stylesheet" href="./timeline.css">
  <script>
    const nodes = []
    function add({
      const tpl = document.querySelector('template')
      const item = tpl.content.children[0]
      const timeline = document.querySelector('.timeline')
      nodes.forEach(it => it.classList.remove('current'))
      const node = item.cloneNode(true)
      node.classList.add('current')
      nodes.push(node)
      console.log(node, node.classList)
      timeline.appendChild(node)
      node.scrollIntoView({ behavior'smooth'block'nearest'inline'center' })
    }
  
</script>
</head>

<body>
  <div class="container">
    <div class="debug-area">
      <button onclick="add()">添加活动</button>
    </div>
    <div class="timeline-area">
      <div class="timeline-wrapper">
        <div class="timeline">
        </div>
      </div>
    </div>
  </div>
</body>

<template>
  <div class="timeline-item">
    <div class="timeline-left">
      <div class="start-time">14:00</div>
      <div class="duration">1h</div>
    </div>
    <div class="timeline-dot"></div>
    <div class="timeline-right">
      <div class="title">和詹姆斯打羽毛球</div>
      <div class="content">
        <div class="info">
          <div class="info-no">
            <img src="clock.svg" />
            <span class="info-content">14:00 ~ 15:00</span>
          </div>
          <div class="info-location">
            <img src="location.svg" />
            <span class="info-content">市中心羽毛球场</span>
          </div>
        </div>
        <div class="join">
          <button>
            报名
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

</html>

SCSS 代码:

.ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ellipsis2 {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  @supports (-webkit-line-clamp2) {
    white-space: initial;
    display: -webkit-box;
    -webkit-line-clamp2;
    -webkit-box-orient: vertical;
  }
}

button {
  display: flex;
  justify-content: center;
  align-items: center;
  padding0 12px;
  font-size14px;
  font-weight: bold;
  height30px;
  border-style: solid;
  background#0d5dff;
  border-color: transparent;
  color: white;
  cursor: pointer;
}

.container {
  margin: auto;
  width350px;
  height350px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding0;
  --left60px;
  --color#0d5dff;
}

.debug-area {
  margin30px 0;
}

.timeline-area {
  flex1;
  position: relative;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding-bottom10px;
  padding-right5px;
  border1px solid #ccc;
}


.timeline-area::before {
  content"";
  display: block;
  position: absolute;
  z-index2;
  left0;
  right12px;
  top0;
  height25px;
  backgroundlinear-gradient(
    to bottom,
    rgba(2552552551),
    rgba(2552552550)
  );
}

.timeline-area::after {
  content"";
  display: block;
  position: absolute;
  z-index2;
  left0;
  right12px;
  bottom10px;
  height25px;
  backgroundlinear-gradient(
    to top,
    rgba(2552552551),
    rgba(2552552550)
  );
}

.timeline-wrapper {
  flex1;
  overflow-y: auto;
  padding15px 5px 0 0;
}


.timeline-wrapper::-webkit-scrollbar {
  width6px;
  background-color: transparent;
}

.timeline-wrapper::-webkit-scrollbar-track-piece {
  margin20px;
}

.timeline-wrapper::-webkit-scrollbar-thumb {
  background-colorrgba($color: #000000, $alpha: 0.08);
}


.timeline {
  position: relative;
}

.timeline::before {
  content"";
  position: absolute;
  leftvar(--left);
  width1px;
  top20px;
  bottom0;
  background-imagelinear-gradient(
    to bottom,
    rgba(1441561730.660%,
    rgba(25525525500%
  );
  background-position: left;
  background-size1px 5px;
  background-repeat: repeat-y;
}

.timeline-item {
  position: relative;
  display: inline-block;
  width100%;
  margin-top15px;
}

.timeline-dot {
  leftvar(--left);
  width7px;
  height7px;
  position: absolute;
  border-radius50%;
  box-shadow0 0 0 1px #d8d8d8;
  background: white;
  text-align: center;
  top0;
  line-height40px;
  margin-left: -3.5px;
}

.timeline-item.current .timeline-dot {
  width10px;
  height10px;
  background-colorvar(--color);
  box-shadow0 0 4px var(--color);
  border1px solid white;
  margin-left: -5px;
}

.timeline-left {
  display: block;
  widthcalc(var(--left) - 7px);
  position: absolute;
  margin-top: -5px;
  text-align: right;
  color#8492a5;
}

.timeline-right {
  position: relative;
  margin: -3px 0 10px calc(var(--left) + 15px);
}

.timeline-item.current .title {
  colorvar(--color);
}

.timeline-left {
  .start-time {
    font-size16px;
  }
  .duration {
    font-size14px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }
}

.timeline-right {
  .title {
    font-size15px;
    font-weight: bold;
    @extend .ellipsis2;
  }
  .content {
    display: flex;
    flex-direction: row;
    padding5px 0;
    .info {
      font-size15px;
      colorrgba($color: #1f3858, $alpha: 0.6);
      flex1;
      img {
        margin-right5px;
        margin-top: -2px;
        height15px;
      }
      .info-no,
      .info-location {
        display: flex;
        align-items: center;
        margin5px 0;
        .info-icon {
          margin-right5px;
        }
        .info-content {
          flex1;
          @extend .ellipsis2;
        }
        &.hidden {
          display: none;
        }
      }
    }
    .join {
      display: flex;
      justify-content: flex-end;
      align-items: flex-start;
      &.hidden {
        display: none;
      }
    }
  }
}

文章出自:https://juejin.cn/post/7146236746068393997

作者:乔珂力


原文始发于微信公众号(前端24):用 CSS 从零写一个时间轴效果

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/216625.html

(0)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!