概要
由于个人项目是基于Element-Plus风格进行开发,在写前端代码时候使用官方组件总感觉达不到心里的预期,索性就自己写了一个Timeline组件。
效果展示
整体效果展示:
Timeline组件展示录屏
使用语言
- Vue 3
- Css
- JavaScript
- Html
实现代码
HTML:
<div
style="
position: relative;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
"
>
<div style="display: flex" class="timeline">
<div
class="timeline-card"
v-for="(timeline, index) in timelines"
:key="timeline"
:ref="index"
>
<div
class="milestone-card"
@mouseover="TimelineAnimateIn(index)"
@mouseleave="TimelineAnimateOut(index)"
>
<img
:src="timeline.picture"
style="width: 100%; height: 100%; border-radius: 5px"
/>
<div class="dark-mask" ref="darkMask"></div>
<div class="milestone-time">
<span class="milestone-data">{{ timeline.data }}</span>
<div
:ref="timeline.name"
class="animate__animated"
style="opacity: 0"
>
<p>{{ timeline.text }}</p>
</div>
<div
:ref="timeline.name"
class="animate__animated"
style="margin-top: 30%; margin-bottom: 8%; opacity: 0"
>
<el-button type="primary" size="small">了解更多</el-button>
</div>
</div>
</div>
<div
style="position: relative; display: flex; justify-content: center"
>
<div class="line-node"></div>
<div class="grey-line"></div>
</div>
</div>
</div>
</div>
CSS:
:root {
--timeline-card-height: 300px;
}
.milestone-card {
width: var(--timeline-card-height);
height: 375px;
position: relative;
margin-bottom: 30px;
overflow: hidden;
border-radius: 5px;
}
.milestone-time {
position: absolute;
display: flex;
align-items: center;
width: 100%;
bottom: 10px;
color: white;
/* z-index: 1; */
line-height: 18px;
transition: all 0.1s linear;
flex-direction: column;
}
.milestone-animate-expand {
animation: expand 0.2s forwards ease-in;
}
@keyframes expand {
100% {
transform: translateY(50%);
}
}
.grey-line {
--margin-width: 20px;
background-color: #e4e7ed;
border-radius: 5px;
position: absolute;
height: 2px;
left: 50%;
bottom: 6px;
}
.line-node {
border-radius: 50%;
height: 12px;
width: 12px;
background-color: #e4e7ed;
position: absolute;
bottom: 0px;
transition: all 0.5 linear;
cursor: pointer;
z-index: 10;
}
.line-node:hover {
transform: scale(1.1);
}
.timeline {
position: relative;
}
.timeline .timeline-card {
position: absolute;
transform: translate(-50%, -50%);
z-index: 1;
}
.timeline .timeline-card:last-child .grey-line {
display: none;
}
.dark-mask {
position: absolute;
background: rgba(101, 101, 101, 0.6);
height: 0%;
width: 100%;
opacity: 0;
bottom: 0px;
transition: all 0.5s linear;
}
.milestone-card:hover .dark-mask {
height: 100%;
opacity: 1;
}
JavaScript:
data() {
return {
timelines: [
{
name: "c1",
data: "2022.12.12",
picture: require("@/assets/image/c-1.jpg"),
text: "但行前路,不负韶华!",
},
{
name: "c2",
data: "2022.02.06",
picture: require("@/assets/image/c-2.jpg"),
text: "既然选择远方,当不负青春,砥砺前行。",
},
{
name: "c3",
data: "2022.04.25",
picture: require("@/assets/image/c-3.jpg"),
text: "以蝼蚁之行,展鸿鹄之志。",
},
{
name: "c4",
data: "2022.06.29",
picture: require("@/assets/image/c-4.jpg"),
text: "抱怨身处黑暗,不如提灯前行",
},
],
};
},
mounted() {
this.ResizeTimeline();
window.onresize = (val) => {
this.ResizeTimeline();
};
methods: {
ResizeTimeline() {
var _screenWidth =
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;
let timeline_list = document.getElementsByClassName("timeline-card");
var _card_width_px = getComputedStyle(
document.documentElement
).getPropertyValue("--timeline-card-height");
var _card_width = _card_width_px.substr(0, _card_width_px.length - 2);
var valid_area = _screenWidth * 0.75;
var offset = (valid_area - _card_width) / (this.timelines.length - 1);
var is_odd = this.timelines.length % 2 == 1 ? true : false;
if (is_odd) {
var middle_index = parseInt(this.timelines.length / 2);
for (var i = 0; i < timeline_list.length; i++) {
timeline_list[i].style.left =
String((i - middle_index) * offset) + "px";
timeline_list[i].getElementsByClassName("grey-line")[0].style.width =
String(offset) + "px";
}
} else {
var middle_index = parseInt(this.timelines.length / 2);
for (var i = 0; i < timeline_list.length; i++) {
timeline_list[i].style.left =
String((i - middle_index + 0.5) * offset) + "px";
timeline_list[i].getElementsByClassName("grey-line")[0].style.width =
String(offset) + "px";
}
}
},
TimelineAnimateIn(index) {
var c_list = this.$refs[this.timelines[index].name];
for (var i = 0; i < c_list.length; i++) {
c_list[i].classList.add("animate__fadeInUp");
c_list[i].classList.remove("animate__fadeOutDown");
}
},
TimelineAnimateOut(index) {
var c_list = this.$refs[this.timelines[index].name];
for (var i = 0; i < c_list.length; i++) {
c_list[i].classList.remove("animate__fadeInUp");
c_list[i].classList.add("animate__fadeOutDown");
}
},}
},
小结
这个组件灵感也是参考了几个codepen的大神作品,结合自己的想法进行实现(有一说一,codepen的作品挺好,但是代码搬运太麻烦了,有能力还是自己写免得改来改去)。该组件还是有个缺点,在窗口缩小到一点程度时候,图片底部的spot不可选,虽然有思路要怎么改,但是想想改起来有点麻烦,索性算了。