Hướng dẫn css timeline vertical

Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article)

Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách xây dựng một timeline đứng từ đầu cho đến cuối. Trước tiên, chúng ta sẽ tạo cấu trúc cơ bản với mã ít mã html nhất và tận dụng sức mạnh của phần tử giả CSS. Sau đó, chúng ta sẽ sử dụng một số JavaScript để thêm một số hiệu ứng chuyển tiếp như khi chúng ta cuộn trang xuống.

Hãy xem trước những gì chúng ta sẽ xây dựng (kiểm tra phiên bản lớn hơn trên CodePen).

1. Mã đánh dấu HTML

Mã đánh dấu mà chúng ta sẽ sử dụng là khá đơn giản; một danh sách không thứ tự đơn giản với một phần tử div bên trong mỗi mỗi phần tử của danh sách. Khi chúng ta làm việc với các sự kiện dọc theo một timeline, chúng ta sẽ cung cấp cho mỗi phần tử của danh sách một phần tử time để hiển thị năm.

Ngoài ra, chúng ta sẽ bao quanh toàn bộ mọi thứ bên trong một phần tử section với tên lớp là timeline:

  • Some content here

Điều này cho chúng ta kết quả chưa định hình phong cách như dưới đây:

2. Thêm các phong cách CSS ban đầu

Sau một số màu cơ bản, vân vân (xem phần trên của CSS pen dưới đây), chúng ta sẽ xác định một số thuộc tính cấu trúc CSS cho các phần tử danh sách. Chúng ta cũng sẽ định phong cách cho phần tử giả ::after của các phần tử này:

.timeline ul li {
  list-style-type: none;
  position: relative;
  width: 6px;
  margin: 0 auto;
  padding-top: 50px;
  background: #fff;
}

.timeline ul li::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: inherit;
}

Tôi đã xoá nội dung trong các phần tử của danh sách để đảm bảo bước này rõ ràng hơn, mang lại cho chúng ta những điều sau đây:

3. Các phong cách cho phần tử Timeline

Bây giờ chúng ta hãy định phong cách phần tử div (chúng ta sẽ gọi chúng là "các phần tử timeline" từ giờ trở đi) là một phần của các phần tử của danh sách. Một lần nữa, chúng ta định phong cách phần tử giả ::before của các phần tử này.

Ngoài ra, như chúng ta sẽ thấy trong một lúc nữa, không phải tất cả phần tử div đều chia sẻ chung một phong cách. Cảm ơn các phần tử giả CSS :nth-child(odd) và :nth-child(even), giúp chúng ta có thể tách riêng các phong cách khác nhau của chúng.

Hãy xem các thuộc tính CSS tương ứng dưới đây:

.timeline ul li div {
  position: relative;
  bottom: 0;
  width: 400px;
  padding: 15px;
  background: #F45B69;
}

.timeline ul li div::before {
  content: '';
  position: absolute;
  bottom: 7px;
  width: 0;
  height: 0;
  border-style: solid;
}

Sau đó một số phong cách cho các phần tử lẻ của chúng ta:

.timeline ul li:nth-child(odd) div {
  left: 45px;
}

.timeline ul li:nth-child(odd) div::before {
  left: -15px;
  border-width: 8px 16px 8px 0;
  border-color: transparent #F45B69 transparent transparent;
}

Sau cùng, là các phong cách cho các phần tử chẵn:

.timeline ul li:nth-child(even) div {
  left: -439px;
}

.timeline ul li:nth-child(even) div::before {
  right: -15px;
  border-width: 8px 0 8px 16px;
  border-color: transparent transparent transparent #F45B69;
}

Với những thuộc tính này đã sẵn sàng (và HTML của chúng ta một lần nữa đã có nội dung) timeline của chúng ta trông như sau:

Sự khác nhau chính giữa các thẻ div "lẻ" và "chẵn" là vị trí của chúng. Những cái đầu tiên có left: 45px; trong khi những cái thứ hai left: -439px;. Để hiểu vị trí của các thẻ div chẵn của chúng ta, hãy làm một số công thức toán học đơn giản:

Chiều rộng của mỗi div + khoảng cách mong muốn - Chiều rộng của mỗi phần tử của danh sách = 400px + 45px -6px = 439px

Sự khác biệt thứ hai, ít quan trọng hơn là tạo mũi tên bằng phần tử giả của chúng. Điều đó có nghĩa là, các phần tử giả của mỗi div "lẻ" có một mũi tên hướng về bên trái, trong khi các phần tử giả của các div "chẵn" sẽ hiển thị như một mũi tên hướng về bên phải.

4. Thêm tương tác

Bây giờ thì cấu trúc cơ bản của timeline đã sẵn sàng, hãy xem xét những yêu cầu mới:

  • Mặc định, các phần tử của timeline (div) sẽ được ẩn.
  • Chúng sẽ xuất hiện khi phần tử cha của chúng (phần tử của dánh sách) đi vào viewport.

Nhiệm vụ đầu tiên là tương đối đơn giản. Dù vậy, cái thứ hai, phức tạp hơn một chút. Chúng ta cần phải xác định nếu các phần tử mục tiêu (các phần tử của danh mục) là hoàn toàn hiển thị trong viewport hiện tại, sau đó nếu điều đó xảy ra chúng ta hiển thị phần tử con của chúng. Để cài đặt tính năng này, chúng ta sẽ không sử dụng bất kỳ thư viện JavaScript bên ngoài nào (ví dụ như WOW.js hoặc ScrollReveal.js) hoặc viết mã riêng của chúng ta, khá phức tạp. Rất may là có một chủ đề rất phổ biến trên StackOverlow về vấn đề này. Vì vậy, trước tiên chúng ta hãy tận dụng lợi thế của câu trả lời được đề xuất để kiểm tra xem một phần tử được nhìn thấy trong viewport hiện tại hay không.

Dưới đây là hàm được đơn giản hoá mà chúng ta sẽ sử dụng:

function isElementInViewport(el) {
  var rect = el.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

Thêm lớp

Tiếp theo chúng ta thêm lớp in-view vào các phần tử của danh sách mà được hiển thị trong viewport hiện hành.

Lưu ý: điều quan trọng là chúng ta kiểm tra nếu chúng có thể hiển thị trong các trường hợp sau đây:

  • Khi tải trang
  • Khi chúng ta cuộn trang

Nếu cần thiết, chúng ta có thể làm một số bài test bổ sung (chẳng hạn như khi thay đổi kích cỡ của cửa sổ trình duyệt).

Trong ví dụ của chúng ta, đây là mã mà chúng ta sử dụng:

var items = document.querySelectorAll(".timeline li");

// code for the isElementInViewport function

function callbackFunc() {
  for (var i = 0; i < items.length; i++) {
    if (isElementInViewport(items[i])) {
      items[i].classList.add("in-view");
    }
  }
}

window.addEventListener("load", callbackFunc);
window.addEventListener("scroll", callbackFunc);

Bây giờ thì chúng ta đã thêm JavaScript của chúng ta, nếu chúng ta tải lại trang web, chúng ta sẽ thấy một kết quả tương tự như thế này:

Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical

Ẩn đi và hiển thị trở lại

Bây giờ hãy xem xét lại yêu cầu đầu tiên của chúng ta. Hãy nhớ rằng, mặc định, tất cả div sẽ được ẩn. Để đạt được điều này, chúng ta sử dụng các thuộc tính visibilityopacity của CSS. Hơn nữa, chúng ta sử dụng translate3d() để di chuyển 200px ra khỏi vị trí ban đầu của chúng. Miễn là phần tử cha của chúng còn ở trong viewport, chúng ta sẽ hiển thị chúng và xoá khoảng cách (khoảng lệch so với vị trí ban đầu của phần tử) mà chúng ta đã định nghĩa trước. Bằng cách này, chúng ta tạo ra các hiệu ứng trượt tuyệt đẹp.

Cuối cùng, một vấn đề nhỏ khác, những gì chúng ta sẽ làm khi một phần tử li ở bên trong viewport là thay đổi màu nền của phần tử giả ::before.

Các phong cách sau giải quyết tất cả điều đó:

.timeline ul li::after {
  background: #fff;
  transition: background .5s ease-in-out;
}

.timeline ul li.in-view::after {
  background: #F45B69;
}

.timeline ul li div {
  visibility: hidden;
  opacity: 0;
  transition: all .5s ease-in-out;
}

.timeline ul li:nth-child(odd) div {
  transform: translate3d(200px,0,0);
}

.timeline ul li:nth-child(even) div {
  transform: translate3d(-200px,0,0);
}

.timeline ul li.in-view div {
  transform: none;
  visibility: visible;
  opacity: 1;
}

Hình minh hoạt dưới đây thể hiện trạng thái ban đầu của timeline. Ở đây bạn sẽ nhìn thấy các phần tử của timeline bởi vì tôi đã cho chúng một ít opacity để minh họa vị trí ban đầu của chúng:

Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical

Và đây là trạng thái sau cùng của timeline:

Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical

5. Tạo khả năng đáp ứng (Responsive)

Chúng ta đã gần xong! Điều cuối cùng mà chúng ta phải làm là làm cho timeline của chúng ta có khả năng đáp ứng.

Đầu tiên, trên các màn hình mà chúng ta cho là "các màn hình trung bình" (>600px và ≤900px), chúng ta chỉ tạo một thay đổi nhỏ. Cụ thể, chúng ta giảm chiều rộng của các phần tử div.

Dưới đây là các thuộc tính mà chúng ta phải thay đổi:

@media screen and (max-width: 900px) {
  .timeline ul li div {
    width: 250px;
  }
  .timeline ul li:nth-child(even) div {
    left: -289px; /*250+45-6*/
  }
}

Trong trường hợp này, timeline sẽ như trông như sau:

Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical

Tuy nhiên trên các màn hình nhỏ (≤600px), tất cả các phần tử của timeline đều giống nhau; không có sự khác biệt giữa các thẻ div "lẻ" và "chẵn". Một lần nữa, chúng ta phải thay thế một số thuộc tính CSS:

@media screen and (max-width: 600px) {
  .timeline ul li {
    margin-left: 20px;
  }
  
  .timeline ul li div {
    width: calc(100vw - 91px);
  }
  
  .timeline ul li:nth-child(even) div {
    left: 45px;
  }
  
  .timeline ul li:nth-child(even) div::before {
    left: -15px;
    border-width: 8px 16px 8px 0;
    border-color: transparent #F45B69 transparent transparent;
  }
}

Trên các màn hình nhỏ thì timeline sẽ trông giống như sau:

Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical
Hướng dẫn css timeline vertical

Lưu ý: trên các màn hình nhỏ chúng ta sử dụng đơn vị vw để thiết lập chiều rộng của các phần tử timeline. Không có bất kỳ lý do đặc biệt nào đằng sau cách tiếp cận này. Chúng ta hoàn toàn có thể sử dụng tỷ lệ phần trăm hoặc pixel.

Trình duyệt hỗ trợ

Bản demo làm việc tốt trong hầu hết các trình duyệt và thiết bị hiện đại. Tuy nhiên, trên các thiết bị iOS, các phần tử của timeline luôn luôn hiển thị, thay vì xuất hiện khi phần tử cha của chúng đi vào viewport.

Từ thử nghiệm của tôi, tôi đã thấy trên những thiết bị mà thuộc tính window.innerHeightdocument.documentElement.clientHeight không trả về chiều cao thực tế của viewport. Cụ thể, chúng trả về một số lớn hơn nhiều. Kết quả của sự bất nhất đó là, tất cả các phần tử của danh sách được thêm lớp in-view khi nạp trang.

Mặc dù nó không phải là một vấn đề lớn (bạn có thể muốn những hiệu ứng động chỉ có ở trên màn hình lớn), nếu bạn biết thêm về vấn đề này hoặc bạn đã nhìn thấy nó trước, đừng quên để lại ý kiến trong phần bình luận.

Kết luận

Trong hướng dẫn này, chúng ta đã tạo ra một timeline đứng có tính đáp ứng cao. Chúng ta đã tìm hiểu rất nhiều điều, vì vậy chúng ta hãy tóm tắt lại:

  • Bằng cách sử dụng một danh sách không thứ tự đơn giản và các phần tử giả của CSS, chúng ta đã xoay sở để xây dựng cấu trúc chính của timeline. Một hạn chế của cách làm này là, như tôi đã đề cập trong một bài viết khác, là các phần tử giả CSS là hoàn toàn không có khả năng truy xuất, vì vậy hãy nhớ kỹ điều này.
  • Chúng ta đã tận dụng một đoạn mã được thực hiện từ một chủ đề phổ biến trên StackOverflow để kiểm tra xem các phần tử của danh sách có ở trong viewport hay không. Sau đó, chúng ta đã viết mã CSS của chúng ta để tạo hiệu ứng động cho các phần tử con của chúng. Ngoài ra, chúng ta có thể đã được sử dụng một thư viện JavaScript hoặc viết mã riêng của chúng ta.

Tôi hy vọng bạn thích hướng dẫn này và bạn sẽ sử dụng timeline này làm cơ sở cho việc xây dựng một cái gì đó thú vị. Nếu bạn có bất kỳ câu hỏi nào, hãy cho tôi biết trong phần bình luận dưới đây!