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

news2024/11/23 15:46:52

时间轴效果介绍

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

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

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

时间轴效果本质

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

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

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

从零开始写时间轴

初始状态

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

其中 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;width: 350px;height: 350px;overflow: hidden;display: flex;flex-direction: column;padding: 0;--left: 62px;--color: #0d5dff;
}

.debug-area {margin: 30px 0;
}

.timeline-area {flex: 1;position: relative;display: flex;flex-direction: column;overflow-y: auto;padding-bottom: 10px;padding-right: 5px;border: 1px 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 {flex: 1;overflow-y: auto;padding: 15px 5px 0 0;
}

.timeline {position: relative;
} 

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

.timeline::before {content: "";position: absolute;left: var(--left);width: 1px;top: 20px;bottom: 0;background-image: linear-gradient(to bottom,rgba(144, 156, 173, 0.6) 60%,rgba(255, 255, 255, 0) 0%);background-position: left;background-size: 1px 5px;background-repeat: repeat-y;
} 

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

.timeline {height: 500px;width: 100%;
} 

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

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

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

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

background-image: linear-gradient(to bottom,rgba(144, 156, 173, 0.6) 60%,rgba(255, 255, 255, 0) 0%
);
background-position: left;
background-size: 1px 5px;
background-repeat: repeat-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 {left: var(--left);width: 7px;height: 7px;position: absolute;border-radius: 50%;box-shadow: 0 0 0 1px #d8d8d8;background: white;text-align: center;top: 0;line-height: 40px;margin-left: -3.5px;
} 

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

.timeline-item.current .timeline-dot {width: 10px;height: 10px;background-color: var(--color);box-shadow: 0 0 4px var(--color);border: 1px solid white;margin-left: -5px;
} 

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

设置左右容器

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

.timeline-left {display: block;width: calc(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;height: 50px;
}
.timeline-right {background: greenyellow;height: 50px;
} 

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

填充左右内容

为了做到最开始动图里面的效果,我们丰富一下 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-size: 16px;}.duration {font-size: 14px;display: flex;align-items: center;justify-content: flex-end;}
}

.timeline-right {.title {font-size: 15px;font-weight: bold;@extend .ellipsis2;}.content {display: flex;flex-direction: row;padding: 5px 0;.info {font-size: 15px;color: rgba($color: #1f3858, $alpha: 0.6);flex: 1;img {margin-right: 5px;margin-top: -2px;height: 15px;}.info-no,.info-location {display: flex;align-items: center;margin: 5px 0;.info-icon {margin-right: 5px;}.info-content {flex: 1;@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 

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

完整代码

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-clamp: 2) {white-space: initial;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}
}

button {display: flex;justify-content: center;align-items: center;padding: 0 12px;font-size: 14px;font-weight: bold;height: 30px;border-style: solid;background: #0d5dff;border-color: transparent;color: white;cursor: pointer;
}

.container {margin: auto;width: 350px;height: 350px;overflow: hidden;display: flex;flex-direction: column;padding: 0;--left: 60px;--color: #0d5dff;
}

.debug-area {margin: 30px 0;
}

.timeline-area {flex: 1;position: relative;display: flex;flex-direction: column;overflow-y: auto;padding-bottom: 10px;padding-right: 5px;border: 1px solid #ccc;
}

.timeline-area::before {content: "";display: block;position: absolute;z-index: 2;left: 0;right: 12px;top: 0;height: 25px;background: linear-gradient(to bottom,rgba(255, 255, 255, 1),rgba(255, 255, 255, 0));
}

.timeline-area::after {content: "";display: block;position: absolute;z-index: 2;left: 0;right: 12px;bottom: 10px;height: 25px;background: linear-gradient(to top,rgba(255, 255, 255, 1),rgba(255, 255, 255, 0));
}

.timeline-wrapper {flex: 1;overflow-y: auto;padding: 15px 5px 0 0;
}

.timeline-wrapper::-webkit-scrollbar {width: 6px;background-color: transparent;
}

.timeline-wrapper::-webkit-scrollbar-track-piece {margin: 20px;
}

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

.timeline {position: relative;
}

.timeline::before {content: "";position: absolute;left: var(--left);width: 1px;top: 20px;bottom: 0;background-image: linear-gradient(to bottom,rgba(144, 156, 173, 0.6) 60%,rgba(255, 255, 255, 0) 0%);background-position: left;background-size: 1px 5px;background-repeat: repeat-y;
}

.timeline-item {position: relative;display: inline-block;width: 100%;margin-top: 15px;
}

.timeline-dot {left: var(--left);width: 7px;height: 7px;position: absolute;border-radius: 50%;box-shadow: 0 0 0 1px #d8d8d8;background: white;text-align: center;top: 0;line-height: 40px;margin-left: -3.5px;
}

.timeline-item.current .timeline-dot {width: 10px;height: 10px;background-color: var(--color);box-shadow: 0 0 4px var(--color);border: 1px solid white;margin-left: -5px;
}

.timeline-left {display: block;width: calc(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 {color: var(--color);
}

.timeline-left {.start-time {font-size: 16px;}.duration {font-size: 14px;display: flex;align-items: center;justify-content: flex-end;}
}

.timeline-right {.title {font-size: 15px;font-weight: bold;@extend .ellipsis2;}.content {display: flex;flex-direction: row;padding: 5px 0;.info {font-size: 15px;color: rgba($color: #1f3858, $alpha: 0.6);flex: 1;img {margin-right: 5px;margin-top: -2px;height: 15px;}.info-no,.info-location {display: flex;align-items: center;margin: 5px 0;.info-icon {margin-right: 5px;}.info-content {flex: 1;@extend .ellipsis2;}&.hidden {display: none;}}}.join {display: flex;justify-content: flex-end;align-items: flex-start;&.hidden {display: none;}}}
} 

这里面有很多细节跟时间轴无关,我就没在文章中讲解,但其实也非常实用,例如 ellipsis 和 ellipsis2 用于设置当内容超出1行或2行之后的溢出效果,-webkit-scrollbar、-webkit-scrollbar-track-piece 和 -webkit-scrollbar-thumb 用于自定义滚动条

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/89911.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跨域的部分理解

1、跨域的原理 跨域&#xff0c;是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的。 同源策略&#xff0c;是浏览器对 JavaScript 实施的安全限制&#xff0c;只要协议、域名、端口有任何一个不同&#xff0c;都被当作是不同的域。 跨域原理&#xff0c;即是通…

量子计算(十四):超导量子芯片

文章目录 超导量子芯片 超导量子芯片 超导量子计算是基于超导电路的量子计算方案&#xff0c;其核心器件是超导约瑟夫森结。超导量子电路在设计、制备和测量等方面&#xff0c;与现有的集成电路技术具有较高的兼容性&#xff0c;对量子比特的能级与耦合可以实现非常灵活的设计…

node.js按照和配置环境变量

下载地址 选择自己适合的版本 Download | Node.js 安装 傻瓜式安装就好了 安装完成后 查看是否安装成功 打开黑窗口 node -v npm -v 配置全局安装的模块路径和缓存路径 在nodejs根目录,创建node_global&#xff0c;node_cache文件夹 命令提示符 以管理员方式运行 我的目…

基于springboot疫情防控期间某村外出务工人员信息管理系统设计与实现的源码+文档

摘 要 网络的广泛应用给生活带来了十分的便利。所以把疫情防控期间某村外出务工人员信息管理与现在网络相结合&#xff0c;利用java技术建设疫情防控期间某村外出务工人员信息管理系统&#xff0c;实现疫情防控期间某村外出务工人员信息的信息化。则对于进一步提高疫情防控期…

Oracle建表与创建序列

目录 一、简单建表 二、查看表结构(在Xshell中输入才能够执行) 三、设置自增序列 (一)普通自增序列 1.创建序列&#xff0c;oracle中没有自增&#xff0c;创建序列相当于等差数列自增 2.删除序列 3.建表的同时设置主键自增的步骤 (二)复杂序列 1.复杂序列模板 2.复杂…

架构设计(二):数据库复制

架构设计&#xff08;二&#xff09;&#xff1a;数据库复制 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;架构设计&#xff08;二&#xff09;&#xff1a;数据库复制 CSDN&#xff1a;架构设计&#xff08;二&#xff09;&#xff1a;数据库复制 在架…

100-数据结构与算法(下篇)

现在我们续写上一章博客的内容&#xff08;即99章博客的内容&#xff09; 快速排序&#xff1a; 同冒泡排序一样&#xff0c;快速排序也属于交换排序&#xff0c;通过元素之间的比较和交换位置来达到排序的目的 不同的是&#xff0c;冒泡排序在每一轮中只把1个元素冒泡到数列…

彻底搞懂JavaScript防抖与节流

今天为大家带来一篇JS重难点的知识体系&#xff0c;这也是前端高薪必备的重难点知识&#xff0c;而且防抖与节流在各大企业前端面试过程中经常会考到的高频面试题&#xff01; 为了更好的帮助大家理解防抖与节流&#xff0c;我们特意从我们的系统班&#xff08;星辰班&#xff…

[阶段4 企业开发进阶] 6. Dubbo

文章目录1 基础理论1.1分布式基础理论1.2 发展演变1.3 RPC2 Dubbo2.1 基本概念2.2 Dubbo架构2.3 环境搭建Windows环境安装教程Linux环境安装教程3 案例演示3.1 需求分析3.2 工程架构分包粒度3.3 创建模块3.4 使用Dubbo配置版注解版4 监控中心4.1 安装4.2 监控中心配置5 整合Spr…

Java中的成员内部类

一、什么是成员内部类 成员内部类就是定义在外部类成员位置(属性/方法的位置)的类。成员内部类就是个成员。 二、为什么要有成员内部类 使用场景&#xff1a;除了它的外部类&#xff0c;不会被其他类使用&#xff0c;就可以使用成员内部类。有两种情况&#xff1a;1.不可能有…

大数据面试之Spark Core常见题目

大数据面试之Spark Core常见题目 1 Spark任务的划分 1、Application&#xff1a;初始化一个SparkContext即生成一个Application。 2、Job&#xff1a;一个Action算子会生成一个Job&#xff0c;有多个Action算子就有多个Job。 3、Stage&#xff1a;Stage等于宽依赖的个数加1…

特殊符号(一)—反斜杠 ” \ “(旋转光标和倒计时的实现)

特殊符号一.功能1.功能一&#xff1a;续航符2.功能二&#xff1a;转义符二.旋转光标和倒计时一.功能 1.功能一&#xff1a;续航符 顾名思义&#xff0c;就是连续的意思&#xff0c;主要用于换行的时候&#xff0c;看例子 以上是一个简单的判断语句&#xff0c;如果if里面的判断…

Win10十二月更新系统讲了什么?

微软今天凌晨发布了win10系统12月最新的累积更新补丁&#xff0c;根据该公司的公告&#xff0c;更新 KB5021233 将版本号增加到构建 19042.2364 &#xff08;20H2&#xff09;、19043.2364 &#xff08;21H1&#xff09;、19044.2364 &#xff08;21H2&#xff09; 和 19045.23…

01-35-springcloud-zk-eureka-consul-cap-父工程

01-springcloud-入门理论等&#xff1a; 微服务 1、什么是微服务 微服务是一种架构风格一个应用拆分为一组小型服务每个服务运行在自己的进程内&#xff0c;也就是可独立部署和升级服务之间使用轻量级HTTP交互服务围绕业务功能拆分可以由全自动部署机制独立部署去中心化&…

产品外观设计一一素描

在反映产品外观设计时&#xff0c;应根据产品的不同功能选择反映方法&#xff0c;以便更清晰地表达创作者的设计理念。首先&#xff0c;我们应该运用透视图的规律性来构建空间框架结构&#xff0c;将镜头中的许多外观元素有机地结合起来&#xff0c;并根据设计科学地安排镜头中…

Portal数据清理过程说明

Portal门户集成平台作为统一的门户搭建和展现平台&#xff0c;提供综合门户、信息门户、应用门户、数据门户等不同类型门户的建设能力&#xff0c;可以为企业构建门户网站、内部业务中心&#xff0c;同时对企业文档资料进行整合、统一归档&#xff0c;对内部、外部提供资料。通…

基于微信小程序的劳务咨询系统设计与实现-计算机毕业设计

项目介绍 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于劳务咨询服务平台小程序当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了劳务咨询服务平台小程序&#xff0…

[附源码]计算机毕业设计第三方游戏零售平台Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

互联网电商大厂的分布式事务使用案例

事务的原子性、持久性可确保在一个事务内&#xff0c;更新多条数据都成功/失败。在一个系统内部&#xff0c;我们可以使用数据库事务来保证数据一致性。那如果一笔交易&#xff0c;涉及到跨多个系统、多个数据库的时候&#xff0c;用单一的数据库事务就没办法解决了。 在之前大…

Altova MissionKit Enterprise 2023 SP1 Crack

屡获殊荣的 XML、JSON、SQL 和 UML 工具套件 以低于 2 个的价格获得 7 个 XML、JSON SQL 和 UML 工具&#xff01; Altova MissionKit 是面向信息架构师和应用程序开发人员的企业级 XML、JSON、SQL 和 UML 工具的软件开发套件。MissionKit 包括 Altova XMLSpy、MapForce、Styl…