Hướng dẫn timeline horizontal css
Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article) Show
Trong bài viết trước, tôi đã hướng dẫn cho bạn cách xây dựng một responsive vertical timeline từ đầu. Hôm nay, tôi sẽ trình bày quá trình tạo horizontal timeline. Như thường lệ, để lên ý tưởng ban đầu về những gì chúng ta sẽ xây dựng, hãy xem bản demo liên quan trên CodePen (xem phiên bản lớn hơn để có trải nghiệm tốt hơn): Chúng ta có rất nhiều thứ để tìm hiểu, vì vậy chúng ta hãy bắt đầu nào! 1. Phần HTMLMã html mà chúng ta định nghĩa giống với mã html mà chúng ta định nghĩa cho vertical timeline, ngoài ba thứ nhỏ này:
Đây là markup cần thiết:
Trạng thái ban đầu của timeline trông giống như sau: 2. Thêm các style CSS ban đầuSau đó một số phông chữ cơ bản, màu sắc, v.v. Những thứ này tôi đã bỏ qua ở đây vì để cho đơn giản, chúng ta chỉ định một số
quy tắc CSS có cấu trúc: .timeline { white-space: nowrap; overflow-x: hidden; } .timeline ol { font-size: 0; width: 100vw; padding: 250px 0; transition: all 1s; } .timeline ol li { position: relative; display: inline-block; list-style-type: none; width: 160px; height: 3px; background: #fff; } .timeline ol li:last-child { width: 280px; } .timeline ol li:not(:first-child) { margin-left: 14px; } .timeline ol li:not(:last-child)::after { content: ''; position: absolute; top: 50%; left: calc(100% + 1px); bottom: 0; width: 12px; height: 12px; transform: translateY(-50%); border-radius: 50%; background: #F45B69; } Quan trọng nhất ở đây, bạn sẽ nhận thấy hai thứ:
Sau khi đã có các quy tắc này, thì đây là trạng thái hiện tại của timeline (chưa có bất kỳ nội dung thật sự nào, để giữ cho mọi thứ rõ ràng): 3. Style cho Phần tử TimelineTại thời điểm này, chúng ta sẽ thêm style cho các phần tử Ngoài
ra, chúng ta sẽ sử dụng các lớp giả CSS Dưới đây là các style chung cho các phần tử timeline: .timeline ol li div { position: absolute; left: calc(100% + 7px); width: 280px; padding: 15px; font-size: 1rem; white-space: normal; color: black; background: white; } .timeline ol li div::before { content: ''; position: absolute; top: 100%; left: 0; width: 0; height: 0; border-style: solid; } Sau đó, một số style cho những phần tử lẻ: .timeline ol li:nth-child(odd) div { top: -16px; transform: translateY(-100%); } .timeline ol li:nth-child(odd) div::before { top: 100%; border-width: 8px 8px 0 0; border-color: white transparent transparent transparent; } Và cuối cùng là một số style cho những phần tử chẵn: .timeline ol li:nth-child(even) div { top: calc(100% + 16px); } .timeline ol li:nth-child(even) div::before { top: -8px; border-width: 8px 0 0 8px; border-color: transparent transparent transparent white; } Dưới đây là trạng thái mới của timeline, với nội dung được thêm vào: Như bạn có thể nhận thấy, các phần tử timeline được thiết lập là absolute. Điều đó có nghĩa là chúng nằm ngoài dòng tài liệu bình thường. Như vậy, để đảm bảo rằng toàn bộ timeline xuất hiện, chúng ta phải thiết lập các giá trị padding top và bottom lớn cho danh sách. Nếu chúng ta không áp dụng bất kỳ padding nào, timeline sẽ bị cắt: 4. Style cho Điều hướng TimelineBây giờ là lúc để thêm style cho các nút điều hướng. Hãy nhớ rằng mặc định, chúng ta vô hiệu hóa mũi tên về trước và thêm lớp Dưới đây là các stye CSS đi kèm: .timeline .arrows { display: flex; justify-content: center; margin-bottom: 20px; } .timeline .arrows .arrow__prev { margin-right: 20px; } .timeline .disabled { opacity: .5; } .timeline .arrows img { width: 45px; height: 45px; } Các quy tắc ở trên cho chúng ta timeline này: 5. Thêm tương tácCấu trúc cơ bản của timeline đã sẵn sàng. Hãy thêm một số tương tác cho nó! BiếnTrước tiên, chúng ta thiết lập một loạt các biến mà chúng ta sẽ sử dụng sau này. const timeline = document.querySelector(".timeline ol"), elH = document.querySelectorAll(".timeline li > div"), arrows = document.querySelectorAll(".timeline .arrows .arrow"), arrowPrev = document.querySelector(".timeline .arrows .arrow__prev"), arrowNext = document.querySelector(".timeline .arrows .arrow__next"), firstItem = document.querySelector(".timeline li:first-child"), lastItem = document.querySelector(".timeline li:last-child"), xScrolling = 280, disabledClass = "disabled"; Khởi tạo mọi thứKhi tất cả nội dung của trang đã sẵn sàng, hàm window.addEventListener("load", init); Hàm này kích hoạt bốn hàm con: function init() { setEqualHeights(elH); animateTl(xScrolling, arrows, timeline); setSwipeFn(timeline, arrowPrev, arrowNext); setKeyboardFn(arrowPrev, arrowNext); } Như chúng ta sẽ thấy một lát nữa, mỗi hàm này thực hiện một nhiệm vụ nhất định. Các phần tử timeline cao ngang nhauNếu bạn quay trở lại bản demo gần nhất, bạn sẽ nhận thấy rằng các phần tử timeline không có chiều cao bằng nhau. Điều này không ảnh hưởng đến chức năng chính của timeline của chúng ta, nhưng bạn có thể thích hơn nếu tất cả các phần tử có cùng chiều cao. Để làm được điều này, chúng ta có thể gán cho chúng độ cao cố định thông qua CSS (giải pháp dễ) hoặc chiều cao động tương ứng với chiều cao của phần tử cao nhất thông qua JavaScript. Cách thứ hai linh hoạt hơn và ổn định hơn, do đó, đây là một hàm cài đặt hành vi này: function setEqualHeights(el) { let counter = 0; for (let i = 0; i < el.length; i++) { const singleHeight = el[i].offsetHeight; if (counter < singleHeight) { counter = singleHeight; } } for (let i = 0; i < el.length; i++) { el[i].style.height = `${counter}px`; } } Hàm này lấy chiều cao của phần timeline cao nhất và thiết lập nó làm chiều cao mặc định cho tất cả các phần tử. Bản demo sẽ trông giống như sau: 6. Thêm hiệu ứng vào TimelineBây giờ hãy tập trung vào hiệu ứng cho timeline. Chúng ta sẽ xây dựng hàm để cài đặt hành vi này theo từng bước. Trước tiên, chúng ta đăng ký một event listener cho các nút của timeline: function animateTl(scrolling, el, tl) { for (let i = 0; i < el.length; i++) { el[i].addEventListener("click", function() { // code here }); } } Mỗi khi nút được nhấp, chúng ta kiểm tra trạng thái disabled của các nút trên timeline và nếu chúng không disabled, chúng ta sẽ disabled chúng. Điều này đảm bảo rằng cả hai nút sẽ chỉ được nhấp một lần cho đến khi hiệu ứng kết thúc. Vì vậy, về mặt code, trình xử lý nhấp chuột lúc đầu chứa các dòng này: if (!arrowPrev.disabled) { arrowPrev.disabled = true; } if (!arrowNext.disabled) { arrowNext.disabled = true; } Các bước tiếp theo như sau:
Code để cài đặt chức năng này như sau: let counter = 0; for (let i = 0; i < el.length; i++) { el[i].addEventListener("click", function() { // other code here const sign = (this.classList.contains("arrow__prev")) ? "" : "-"; if (counter === 0) { tl.style.transform = `translateX(-${scrolling}px)`; } else { const tlStyle = getComputedStyle(tl); // add more browser prefixes if needed here const tlTransform = tlStyle.getPropertyValue("-webkit-transform") || tlStyle.getPropertyValue("transform"); const values = parseInt(tlTransform.split(",")[4]) + parseInt(`${sign}${scrolling}`); tl.style.transform = `translateX(${values}px)`; } counter++; }); } Tốt lắm! Chúng ta vừa xác định được một cách tạo hiệu ứng cho timeline. Thách thức tiếp theo là tìm ra khi nào hiệu ứng này sẽ dừng lại. Đây là cách làm của chúng ta:
Hãy nhớ rằng phần tử cuối cùng là phần tử rỗng có chiều rộng bằng với chiều rộng của các phần tử timeline (tức là 280px). Chúng ta gán cho nó giá trị này (hoặc giá trị cao hơn) bởi vì chúng ta muốn đảm bảo rằng phần tử dòng cuối cùng của timeline sẽ hiển thị trước khi vô hiệu hóa nút next. Để phát hiện xem các phần tử có hiển thị hoàn toàn trong viewport hiện tại hay không, chúng ta sẽ tận dụng code mà chúng ta đã sử dụng cho vertical timeline. Code yêu cầu xuất phát từ chủ đề trên Stack Overflow như sau: function isElementInViewport(el) { const rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } Ngoài các hàm ở trên, chúng ta định nghĩa một trình trợ giúp khác: function setBtnState(el, flag = true) { if (flag) { el.classList.add(disabledClass); } else { if (el.classList.contains(disabledClass)) { el.classList.remove(disabledClass); } el.disabled = false; } } Hàm này thêm hoặc xoá bỏ lớp Với những gì chúng ta đã mô tả ở trên, dưới đây là code mà chúng ta định nghĩa để kiểm tra xem hiệu ứng có nên dừng hay không: for (let i = 0; i < el.length; i++) { el[i].addEventListener("click", function() { // other code here // code for stopping the animation setTimeout(() => { isElementInViewport(firstItem) ? setBtnState(arrowPrev) : setBtnState(arrowPrev, false); isElementInViewport(lastItem) ? setBtnState(arrowNext) : setBtnState(arrowNext, false); }, 1100); // other code here }); } Lưu ý rằng có độ trễ 1.1 giây trước khi thực thi code này. Tại sao điều này xảy ra? Nếu chúng ta quay lại CSS, chúng ta sẽ thấy quy tắc này: .timeline ol { transition: all 1s; } Vì vậy, hiệu ứng của timeline cần 1 giây để hoàn thành. Khi nó hoàn thành, chúng ta chờ đợi 100 mili giây và sau đó, chúng ta thực hiện công việc kiểm tra. Dưới đây timeline với hiệu ứng động: 7. Thêm hỗ trợ cử chỉ vuốtCho đến lúc này, timeline không phản hồi các sự kiện chạm. Sẽ rất tuyệt nếu chúng ta có thể thêm chức năng này. Để thực hiện việc đó, chúng ta có thể cài đặt bằng JavaScript của riêng mình hoặc sử dụng một trong các thư viện hỗ trợ (ví dụ, Hammer.js, TouchSwipe.js). Đối với bản demo của chúng ta, chúng ta sẽ làm đơn giản và sử dụng Hammer.js, vì vậy trước tiên, chúng ta bao gồm thư viện này vào pen của chúng ta: Sau đó, chúng ta khai báo hàm liên quan: function setSwipeFn(tl, prev, next) { const hammer = new Hammer(tl); hammer.on("swipeleft", () => next.click()); hammer.on("swiperight", () => prev.click()); } Bên trong hàm ở trên, chúng ta làm những việc sau đây:
Timeline với hỗ trợ vuốt: Thêm điều hướng bằng bàn phímHãy nâng cao hơn nữa trải nghiệm người dùng bằng cách cung cấp hỗ trợ cho điều hướng bằng bàn phím. Mục tiêu của chúng ta là:
Hàm cài đặt hành vi này sẽ như sau: function setKeyboardFn(prev, next) { document.addEventListener("keydown", (e) => { if ((e.which === 37) || (e.which === 39)) { const timelineOfTop = timeline.offsetTop; const y = window.pageYOffset; if (timelineOfTop !== y) { window.scrollTo(0, timelineOfTop); } if (e.which === 37) { prev.click(); } else if (e.which === 39) { next.click(); } } }); } Timeline với hỗ trợ bàn phím: 8. Làm cho nó ResponsiveChúng ta gần như xong! Cuối cùng nhưng không kém phần quan trọng, hãy làm cho timeline trở nên responsive. Khi viewport nhỏ hơn 600px, nó nên có bố cục xếp chồng như sau: Vì chúng ta đang sử dụng cách tiếp cận desktop-first, nên đây là các quy tắc CSS mà chúng ta phải thay thế: @media screen and (max-width: 599px) { .timeline ol, .timeline ol li { width: auto; } .timeline ol { padding: 0; transform: none !important; } .timeline ol li { display: block; height: auto; background: transparent; } .timeline ol li:first-child { margin-top: 25px; } .timeline ol li:not(:first-child) { margin-left: auto; } .timeline ol li div { width: 94%; height: auto !important; margin: 0 auto 25px; } .timeline ol li:nth-child div { position: static; } .timeline ol li:nth-child(odd) div { transform: none; } .timeline ol li:nth-child(odd) div::before, .timeline ol li:nth-child(even) div::before { left: 50%; top: 100%; transform: translateX(-50%); border: none; border-left: 1px solid white; height: 25px; } .timeline ol li:last-child, .timeline ol li:nth-last-child(2) div::before, .timeline ol li:not(:last-child)::after, .timeline .arrows { display: none; } } Lưu ý: Đối với hai trong số các quy tắc ở trên, chúng ta phải sử dụng
quy tắc Trạng thái cuối cùng của timeline của chúng ta: Trình duyệt Hỗ trợBản demo hoạt động tốt trên tất cả các trình duyệt và thiết bị mới. Ngoài ra, như bạn có thể nhận ra, chúng ta sử dụng Babel để biên dịch mã ES6 xuống ES5. Một vấn đề nhỏ mà tôi gặp phải trong khi thử nghiệm đó là thay đổi kết xuất văn bản xảy ra khi timeline đang chạy hiệu ứng. Mặc dù tôi đã thử các cách tiếp cận khác nhau được đề xuất trong các chủ đề trên Stack Overflow, tôi vẫn chưa tìm thấy giải pháp đơn giản cho tất cả các hệ điều hành và trình duyệt. Vì vậy, hãy nhớ rằng bạn có thể thấy các vấn đề về hiển thị phông chữ nhỏ khi timeline đang chạy hiệu ứng. Phần kết luậnTrong bài hướng dẫn khá thực tế này, chúng ta bắt đầu với một danh sách có thứ tự đơn giản và tạo ra một responsive horzontal timeline. Không nghi ngờ gì nữa, chúng ta đã bao quát rất nhiều điều thú vị, nhưng tôi hy vọng bạn thích làm việc hướng tới kết quả cuối cùng và điều đó giúp bạn học được một số kiến thức mới. Nếu bạn có bất kỳ câu hỏi nào hoặc nếu có bất kỳ điều gì bạn không hiểu, hãy cho tôi biết trong phần bình luận bên dưới nhé! Làm gì Tiếp theo?Nếu bạn muốn cải tiến thêm hoặc mở rộng timeline này, thì đây là một vài điều bạn có thể làm:
|