<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- https://codepen.io/jkantner/pen/ZEqvpWd -->
<title>时间线</title>
<style>
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--hue: 3;
--bg: hsl(var(--hue),10%,90%);
--fg: hsl(var(--hue),10%,10%);
--primary: hsl(var(--hue),90%,50%);
--primaryT: hsla(var(--hue),90%,50%,0);
--trans-dur: 0.3s;
font-size: calc(16px + (24 - 16) * (100vw - 320px) / (2560 - 320));
}
body {
background-color: var(--bg);
color: var(--fg);
display: flex;
font: 1em/1.5 "DM Sans", sans-serif;
height: 100vh;
transition:
background-color var(--trans-dur),
color var(--trans-dur);
}
a {
transition: color var(--trans-dur);
}
h1 {
font-size: 2em;
margin: 0 0 1.5rem;
text-align: center;
}
.timeline {
margin: auto;
padding: 3em 0;
}
.timeline__items {
list-style: none;
margin-left: 0.75em;
}
.timeline__item {
padding: 0 0 1.5em 1.25em;
position: relative;
}
.timeline__item br {
display: none;
}
.timeline__item:before,
.timeline__item:after,
.timeline__item .timeline__item-pub,
.timeline__item .timeline__item-time,
.timeline__item .timeline__item-link {
transition:
background-color var(--trans-dur),
opacity var(--trans-dur) cubic-bezier(0.65,0,0.35,1),
transform var(--trans-dur) cubic-bezier(0.65,0,0.35,1);
}
.timeline__item:before,
.timeline__item:after {
background-color: var(--primary);
content: "";
display: block;
position: absolute;
left: 0;
}
.timeline__item:before {
border-radius: 50%;
top: 0.25em;
width: 1em;
height: 1em;
transform: translateX(-50%) scale(0);
z-index: 1;
}
.timeline__item:after {
top: 0.75em;
width: 0.125em;
height: 100%;
transform: translateX(-50%);
}
.timeline__item:last-child:after {
display: none;
}
.timeline__item-pub,
.timeline__item-link,
.timeline__item-time {
display: block;
opacity: 0;
transform: translateX(-0.75em);
}
.timeline__item-link,
.timeline__item-link:visited {
color: var(--primary);
}
.timeline__item-link {
transition: color var(--trans-dur);
}
.timeline__item-link:hover {
text-decoration: underline;
}
.timeline__item-pub,
.timeline__item-time {
font-size: 0.833em;
line-height: 1.8;
}
.timeline__item-time {
font-weight: bold;
}
.timeline__loading {
text-align: center;
}
/* Observed items */
.timeline__item--in .timeline__item-pub,
.timeline__item--in .timeline__item-link,
.timeline__item--in .timeline__item-time {
opacity: 1;
transform: translateX(0);
}
.timeline__item--in:before {
transform: translateX(-50%) scale(1);
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(var(--hue),10%,10%);
--fg: hsl(var(--hue),10%,90%);
--primary: hsl(var(--hue),90%,70%);
}
}
/* Beyond mobile */
@media (min-width: 768px) {
.timeline__items {
margin-left: 17em;
width: 17em;
}
.timeline__item-time {
position: absolute;
top: 0;
right: calc(100% + 1.25rem);
text-align: right;
width: 17rem;
transform: translateX(0.75em);
}
}
</style>
</head>
<body>
<div class="timeline">
<h1>Writing</h1>
<ul class="timeline__items" data-items></ul>
<div class="timeline__loading" data-loading>Loading timeline…</div>
</div>
<script type="module">
import { faker } from "https://cdn.skypack.dev/@faker-js/faker@7.6.0"
window.addEventListener("DOMContentLoaded",() => {
const t = new Timeline(".timeline");
});
class Timeline {
articles = [];
constructor(el) {
this.el = document.querySelector(el);
this.init();
}
init() {
this.generateArticles();
this.removeLoading();
this.build();
this.observeArticles();
}
build() {
const itemContainer = this.el.querySelector("[data-items]");
if (!itemContainer) return;
const localeCode = "en-US";
this.articles.forEach(article => {
const time = document.createElement("time");
time.className = "timeline__item-time";
const DRaw = new Date(article.date);
const D = {
y: DRaw.getFullYear(),
m: DRaw.getMonth() + 1,
d: DRaw.getDate()
};
if (D.m < 10) D.m = `0${D.m}`;
if (D.d < 10) D.d = `0${D.d}`;
time.setAttribute("datetime", `${D.y}-${D.m}-${D.d}`);
const articleDateLocal = DRaw.toLocaleDateString(localeCode,{
year: "numeric",
month: "long",
day: "numeric"
});
time.innerText = articleDateLocal;
const link = document.createElement("a");
link.className = "timeline__item-link";
link.href = "#";
link.innerText = article.title;
const published = document.createElement("small");
published.className = "timeline__item-pub";
published.innerText = article.publisher;
const item = document.createElement("li");
item.className = "timeline__item";
item.appendChild(time);
item.appendChild(document.createElement("br"));
item.appendChild(link);
item.appendChild(document.createElement("br"));
item.appendChild(published);
itemContainer.appendChild(item);
});
}
generateArticles() {
const articleCount = 30;
try {
for (let a = 0; a < articleCount; ++a) {
const adjective = faker.company.catchPhraseAdjective();
const noun = faker.company.catchPhraseNoun();
this.articles.push({
title: this.toTitleCase(`${adjective} ${noun}`),
date: faker.date.past(10),
publisher: faker.company.name(),
});
}
} catch (err) {
console.log("Faker unavailable");
}
// reverse chronological order
this.articles.sort((a,b) => b.date - a.date);
}
observeArticles() {
this.observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
const { target } = entry;
const itemIn = "timeline__item--in";
if (entry.isIntersecting) target.classList.add(itemIn);
else target.classList.remove(itemIn);
});
}, {
root: null,
rootMargin: "0px",
threshold: 1
}
);
// observe the items
const items = document.querySelectorAll(".timeline__item");
Array.from(items).forEach(item => {
this.observer.observe(item);
});
}
removeLoading() {
const loading = this.el.querySelector("[data-loading]");
if (!loading) return;
this.el.removeChild(loading);
}
toTitleCase(title) {
return title.split(" ").map(word => word[0].toUpperCase() + word.substring(1)).join(" ");
}
}
</script>
</body>
</html>
转载请注明:有爱前端 » 时间线