فوائد استخدام الجدول الزمني (Timeline) باستخدام CSS
📊 تحسين تجربة المستخدم (UX)
- تقديم المعلومات بطريقة زمنية مرتبة ومنطقية
- تسهيل فهم التسلسل الزمني للأحداث
- جعل المحتوى أكثر جاذبية وتنظيمًا
⚙️ فوائد تقنية
- أداء عالي: لا يحتاج إلى مكتبات خارجية أو JavaScript معقد
- سرعة التحميل: تأثيرات CSS أخف من الصور أو الفيديوهات
- سهولة الصيانة: تعديل المحتوى دون الحاجة لتغيير الهيكل الأساسي
🎨 فوائد تصميمية
- مرونة في التصميم: يمكن تخصيص الشكل والألوان بسهولة
- تأثيرات بصرية جذابة: حركات وتغيرات عند التحويم أو التمرير
- تنسيق متسق: مظهر موحد لجميع العناصر
📱 فوائد للتوافقية
- تجاوب كامل: يعمل على جميع أحجام الشاشات
- توافق مع المتصفحات: يدعمه جميع المتصفحات الحديثة
- إمكانية الوصول: يمكن تحسينه لذوي الاحتياجات الخاصة
💻 فوائد للتطوير
- سهولة التنفيذ: لا يحتاج لمهارات برمجة متقدمة
- إعادة الاستخدام: يمكن استخدام النموذج لمشاريع متعددة
- تحديثات سهلة: إضافة أو حذف عناصر دون التأثير على التصميم
🔍 حالات الاستخدام الشائعة:
الجداول الزمنية باستخدام CSS توفر حلًا مثاليًا لعرض المعلومات الزمنية بطريقة جذابة وسهلة الفهم مع الحفاظ على أداء الموقع وسرعته.
شرح الجدول الزمني العمودي (Vertical Timeline)
البنية الأساسية (HTML)
قسم المقدمة (Intro Section)
<section class="intro">
<div class="container">
<h1>Vertical Timeline>
</div>
</section>
- يعرض عنوان رئيسي مع سهم لأسفل
- يحتوي على رابط للإصدار السابق من الجدول الزمني
قسم الجدول الزمني (Timeline Section)
<section class="timeline">
<ol>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1934</time>
</div>
<div class="details">
<h6>Heading Here</h6>
<p>Content here...</p>
</div>
</div>
</li>
<!-- عناصر أخرى مماثلة -->
</ol>
</section>
- يستخدم قائمة مرتبة (
<ol>
) لتمثيل العناصر الزمنية - كل عنصر زمني (
<li>
) يحتوي على:- قسم للوقت (
time-wrapper
) يعرض السنة - قسم للتفاصيل (
details
) يعرض العنوان والمحتوى
- قسم للوقت (
التنسيقات (CSS Styles)
المتغيرات العامة
:root {
--red: #f45b69;
--dark-blue: #227c9d;
--turquoise: #17c3b2;
--white: #fff;
}
- تعريف ألوان ثابتة لإعادة استخدامها
تنسيق الخط الزمني الأساسي
.timeline ol {
position: relative;
list-style: none;
}
.timeline ol::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background: var(--turquoise);
}
- إنشاء خط عمودي على الجانب الأيسر يمثل الخط الزمني
- إزالة علامات القائمة الافتراضية
تأثيرات الرسم
.timeline ol li .time-wrapper::before,
.timeline ol li .time-wrapper::after {
content: "";
position: absolute;
background: var(--turquoise);
}
.timeline ol li .time-wrapper::before {
/* خط أفقي */
}
.timeline ol li .time-wrapper::after {
/* دائرة صغيرة */
}
- إنشاء خط أفقي ودائرة صغيرة لكل عنصر زمني
تأثيرات الحركة (Animations)
الحالة الأولية (قبل الظهور)
.timeline ol li time,
.timeline ol li .details > * {
opacity: 0;
}
.timeline ol li time {
transform: translateY(-30px);
}
.timeline ol li .details > * {
transform: translateY(30px);
}
- إخفاء المحتوى في البداية
- تحريك العناصر لأعلى أو لأسفل
الحالة النشطة (عند الظهور)
.timeline ol li.in-view .time-wrapper::before {
width: 120px; /* توسيع الخط الأفقي */
}
.timeline ol li.in-view .time-wrapper::after {
transform: scale(1.5); /* تكبير الدائرة */
}
.timeline ol li.in-view time,
.timeline ol li.in-view .details > * {
opacity: 1;
transform: none; /* إعادة العناصر لمكانها */
}
- تأثيرات متتابعة مع تأخير زمني:
- الخط الأفقي أولاً
- ثم الدائرة
- ثم التاريخ
- ثم العنوان
- وأخيراً المحتوى
جافاسكريبت (JavaScript)
كود المراقبة (Intersection Observer)
const targets = document.querySelectorAll(".timeline ol li");
const observer = new IntersectionObserver(callback, { threshold: 0.5 });
function callback(entries) {
entries.forEach(entry => {
if (entry.intersectionRatio >= 0.5) {
entry.target.classList.add("in-view");
observer.unobserve(entry.target);
} else {
entry.target.classList.remove("in-view");
}
});
}
targets.forEach(target => observer.observe(target));
- يراقب متى يصبح كل عنصر زمني مرئياً بنسبة 50% أو أكثر
- يضيف/يزيل كلاس
in-view
لتفعيل/إلغاء تأثيرات الحركة - يتوقف عن المراقبة بعد تفعيل الحركة للمرة الأولى
فوائد هذا التصميم
- تجربة مستخدم جذابة: تأثيرات حركية متتابعة تجعل التصفح ممتعاً
- تصميم نظيف: استخدام مساحات بيضاء وخطوط واضحة
- سهولة التخصيص: يمكن تغيير الألوان والمسافات بسهولة
- أداء عالي: استخدام CSS للحركات بدلاً من JavaScript
- تجاوب كامل: يعمل على جميع أحجام الشاشات
الكــود كاملا
<section class="timeline">
<ol>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1934</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1937</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>Proin quam velit, efficitur vel neque vitae, rhoncus commodo mi. Suspendisse finibus mauris et bibendum molestie. Aenean ex augue, varius et pulvinar in, pretium non nisi.</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1940</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>Proin iaculis, nibh eget efficitur varius, libero tellus porta dolor, at pulvinar tortor ex eget ligula. Integer eu dapibus arcu, sit amet sollicitudin eros.</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1943</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>In mattis elit vitae odio posuere, nec maximus massa varius. Suspendisse varius volutpat mattis. Vestibulum id magna est.</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1946</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>In mattis elit vitae odio posuere, nec maximus massa varius. Suspendisse varius volutpat mattis. Vestibulum id magna est.</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1956</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>Proin iaculis, nibh eget efficitur varius, libero tellus porta dolor, at pulvinar tortor ex eget ligula. Integer eu dapibus arcu, sit amet sollicitudin eros</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1967</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>In mattis elit vitae odio posuere, nec maximus massa varius. Suspendisse varius volutpat mattis. Vestibulum id magna est.</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>1985</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>Proin iaculis, nibh eget efficitur varius, libero tellus porta dolor, at pulvinar tortor ex eget ligula. Integer eu dapibus arcu, sit amet sollicitudin eros</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>2000</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>In mattis elit vitae odio posuere, nec maximus massa varius. Suspendisse varius volutpat mattis. Vestibulum id magna est.</p>
</div>
</div>
</li>
<li>
<div class="item-inner">
<div class="time-wrapper">
<time>2012</time>
</div>
<div class="details">
<h3>Heading Here</h3>
<p>Proin iaculis, nibh eget efficitur varius, libero tellus porta dolor, at pulvinar tortor ex eget ligula. Integer eu dapibus arcu, sit amet sollicitudin eros</p>
</div>
</div>
</li>
</ol>
</section>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");
:root {
--red: #f45b69;
--dark-blue: #227c9d;
--turquoise: #17c3b2;
--white: #fff;
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
color: inherit;
}
body {
font: normal 16px/1.5 "Inter", sans-serif;
padding-bottom: 50px;
}
/* INTRO SECTION
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.intro {
color: var(--white);
background: var(--red);
padding: 100px 0;
}
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
text-align: center;
}
h1 {
font-size: 2.5rem;
}
/* TIMELINE
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.timeline {
padding: 50px 20px;
margin: 0 auto;
max-width: 1000px;
color: var(--dark-blue);
}
.timeline ol {
position: relative;
list-style: none;
}
.timeline ol::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background: var(--turquoise);
}
.timeline ol li .item-inner {
padding: 20px 180px;
margin-bottom: 8vh;
}
.timeline ol li .time-wrapper {
position: relative;
font-size: 4.5rem;
font-weight: bold;
line-height: 0.9;
}
.timeline ol li .time-wrapper::before,
.timeline ol li .time-wrapper::after {
content: "";
position: absolute;
background: var(--turquoise);
}
.timeline ol li .time-wrapper::before {
top: 50%;
left: -180px;
transform: translateY(-50%);
width: 0;
height: 3px;
transition: width 0.8s linear;
}
.timeline ol li .time-wrapper::after {
top: calc(50% - 8px);
left: -60px;
width: 15px;
height: 15px;
border-radius: 50%;
transform: scale(0);
transform-origin: left center;
transition: all 0.4s linear;
}
.timeline ol li time,
.timeline ol li .details > * {
opacity: 0;
transition: 0.5s;
}
.timeline ol li time {
display: inline-block;
transform: translateY(-30px);
}
.timeline ol li .details > * {
transform: translateY(30px);
}
.timeline ol li .details h3 {
font-size: 2rem;
line-height: 1;
margin: 15px 0;
}
/* ANIMATION STYLES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.timeline ol li.in-view .time-wrapper::before {
width: 120px;
}
.timeline ol li.in-view .time-wrapper::after {
transition-delay: 0.8s;
transform: scale(1.5);
}
.timeline ol li.in-view time,
.timeline ol li.in-view .details > * {
opacity: 1;
transform: none;
}
.timeline ol li.in-view time {
transition-delay: 1.3s;
}
.timeline ol li.in-view .details h3 {
transition-delay: 1.5s;
}
.timeline ol li.in-view .details p {
transition-delay: 1.7s;
}
/* MQ STYLES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
@media (max-width: 700px) {
.timeline ol li .item-inner {
padding: 20px 40px;
}
.timeline ol li .time-wrapper::before {
display: none;
}
.timeline ol li .time-wrapper::after {
left: -45px;
transform-origin: center;
}
.timeline ol li.in-view .time-wrapper::after {
transition-delay: 0s;
}
.timeline ol li.in-view time {
transition-delay: 0.5s;
}
.timeline ol li.in-view .details h3 {
transition-delay: 0.7s;
}
.timeline ol li.in-view .details p {
transition-delay: 0.9s;
}
}
/* FOOTER STYLES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.page-footer {
position: fixed;
right: 0;
bottom: 20px;
display: flex;
align-items: center;
padding: 5px;
background: rgba(255, 255, 255, 0.65);
}
.page-footer a {
display: flex;
margin-left: 4px;
}
const targets = document.querySelectorAll(".timeline ol li");
const threshold = 0.5;
const ANIMATED_CLASS = "in-view";
function callback(entries, observer) {
entries.forEach((entry) => {
const elem = entry.target;
if (entry.intersectionRatio >= threshold) {
elem.classList.add(ANIMATED_CLASS);
observer.unobserve(elem);
} else {
elem.classList.remove(ANIMATED_CLASS);
}
});
}
const observer = new IntersectionObserver(callback, { threshold });
for (const target of targets) {
observer.observe(target);
}