Cách tạo một Header Cố định và Hoạt hình khi Cuộn Trang

Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách tạo một dạng thường được thấy trên nhiều trang web ngày này: một header cố định và hoạt hình thành một trạng thái thu nhỏ hơn khi chúng ta cuộn trang xuống. Chúng ta sẽ bắt đầu với cấu trúc cơ bản, sau đó làm cho mọi thứ hoạt động bằng cách sử dụng CSS và JavaScript thuần tuý. Trước khi kết thúc, chúng ta tìm hiểu nhanh cách chúng ta có thể tối ưu hóa mã của chúng ta cũng như thảo luận về những thách thức hiện tại khi áp dụng điều này trên các thiết bị cảm ứng.

Để hiểu rõ về những gì chúng ta sẽ xây dựng, dưới đây là bản demo (bạn có thể xem toàn màn hình):

Chúng ta sẽ bắt đầu bài tập này với mã đánh dấu sau đây – một tiêu đề, có chứa một <nav> và một số các phần tử lồng nhau:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<header>
  <nav>
    <h1>
      <a href="" class="logo">Logo</a>
    </h1>
    <ul>
      <li>
        <a href="">About</a>
      </li>
      <li>
        <a href="">Services</a>
      </li>
      <li>
        <a href="">Portfolio</a>
      </li>
      <li>
        <a href="">Contact</a>
      </li>
    </ul>
    <button class="toggle-menu" aria-label="Responsive Navigation Menu">☰</button>
  </nav>
</header>
<main>
  <!-- content here -->
</main>

Phần tử nav, là một phần của header, chứa ba phần tử; logo, menu chính, và một nút để kích hoạt một menu đáp ứng (responsive) (dưới 1061px).

Lưu ý: Nếu bạn nhấp vào nút này, sẽ không có gì xảy ra. Tạo menu đáp ứng nằm ngoài phạm vi của hướng dẫn này.

Bây giờ chúng ta hãy xem một số phong cách CSS cho các thứ:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
header {
  position: fixed;
  top: 0;
  width: 100%;
  padding: 20px;
  box-sizing: border-box;
  background: #DD3543;
}
nav {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  transition: align-items .2s;
}
.logo {
  font-size: 2rem;
  display: inline-block;
  padding: 20px 30px;
  background: #F35B66;
  color: #fff;
  margin: 50px 0 0 50px;
  transition: all .2s;
}
ul {
  display: flex;
  margin: 50px 50px 0 0;
  padding: 0;
  transition: margin .2s;
}
li:not(:last-child) {
  margin-right: 20px;
}
li a {
  display: block;
  padding: 10px 20px;
}
.toggle-menu {
  display: none;
  font-size: 2rem;
  color: #fff;
  margin: 10px 10px 0 0;
  transition: margin .2s;
}
main {
  display: block;
  padding: 0 20px;
}

Dưới đây là một giải thích ngắn về các thuộc tính CSS quan trọng nhất:

  • Phần tử header là một phần tử có vị trí cố định.
  • Chúng ta sử dụng flexbox để bố cục phần tử nav.
  • Logo có margin-top: 50px và margin-left: 50px. Ngoài ra, chúng ta cho nó padding: 20px 30px.
  • Menu chính nằm về một bên so với logo, với margin-top: 50px và margin-right: 50px.
  • Nút bấm được ẩn đi. Nó được hiển thị khi chiều rộng viewport nhỏ hơn 1061px. Hơn nữa, chúng ta thiết lập margin bên trên và bên phải là 10px.
  • Chúng ta thêm thuộc tính transition vào các phần tử mà các giá trị thuộc tính của chúng sẽ thay đổi trong tương lai. Bằng cách này, chúng ta đạt được một hiệu ứng chuyển tiếp mượt mà giữa trạng thái ban đầu và trạng thái cuối cùng.

Với các luật này, thì header trông giống như thế này:

Cách tạo một Header Cố định và Hoạt hình khi Cuộn Trang
Advertisement

Đến lúc này chúng ta đã xây dựng cấu trúc cơ bản của header. Đây là lúc đề cập đến các bước tiếp theo:

  • Phần tử main nên được đặt ngay phía dưới header. Hãy nhớ rằng header có positioned: fixed, và do đó vị trí của nó là ở trên đầu của phần tử main.
  • Header sẽ được hoạt hình khi chúng ta cuộn trang web xuống.

Để giải quyết nhiệm vụ đầu tiên, chúng ta thêm một thuộc tính padding-top vào phần tử main. Giá trị của thuộc tính này nên bằng với chiều cao của header. Trong trường hợp của chúng ta, chúng ta vẫn chưa thiết lập một chiều cao cố định cho header của chúng ta, vì thế chúng ta sẽ sử dụng một số JavaScript để tính toán nó, và sau đó thêm padding tương ứng vào trong phần tử main.

Để giải quyết nhiệm vụ thứ hai, chúng ta sẽ làm những thứ sau đây:

  1. Truy vấn số pixel mà trang web đã được cuộn theo chiều dọc.
  2. Nếu con số này lớn hơn 150px, chúng ta thêm lớp scroll vào header.

Dưới đây là các mã JavaScript cần thiết – chúng ta bắt đầu bằng cách xác định một số biến, tính chiều cao của header, sau đó gắn giá trị đó cho  padding-top của phần tử main:

1
2
3
4
5
6
7
8
var m = document.querySelector("main"),
    h = document.querySelector("header"),
    hHeight;
function setTopPadding() {
  hHeight = h.offsetHeight;
  m.style.paddingTop = hHeight + "px";
}

Để minh hoạ, chúng ta sử dụng các thuộc tính offsetHeight để lấy chiều cao của header. Hãy nhớ rằng chúng ta có thể làm tương tự bằng cách sử dụng phương thức getBoundingClientRect(). Cần lưu ý rằng phương thức này có thể trả về các giá trị thập phân.

Bây giờ vào sự kiện cuộn trang:

01
02
03
04
05
06
07
08
09
10
11
function onScroll() {
  window.addEventListener("scroll", callbackFunc);
  function callbackFunc() {
    var y = window.pageYOffset;
    if (y > 150) {
      h.classList.add("scroll");
    } else {
      h.classList.remove("scroll");
    }
  }
}

Ở đây chúng ta tận dụng thuộc tính pageYOffset để tính toán số pixel mà trang của chúng ta đã cuộn. Lưu ý rằng thuộc tính này không hoạt động trong các phiên bản IE cũ (<9). Tuy nhiên, nếu bạn muốn hỗ trợ bất kỳ những phiên bản này, một giải pháp có sẵn ở đây.

Sau đó, chúng ta sử dụng thuộc tính classList để thêm và loại bỏ lớp scroll khỏi header của chúng ta. Không phải tất cả các trình duyệt đều hỗ trợ thuộc tính này, vì vậy nếu bạn muốn hỗ trợ bất kỳ trình duyệt nào hãy xem qua các polyfill classList.js và classie.js. Ví dụ, chúng ta có thể sử dụng thuộc tính className để điều khiển class đơn của chúng ta, nhưng trong một trường hợp thực tế điều này có thể không phải là một giải pháp lý tưởng (trong trường hợp chúng ta có nhiều lớp).

Để tổng hợp mọi thứ, chúng ta gọi các hàm của chúng ta trong hai trường hợp khác nhau:

  • Khi trang web được nạp
  • và khi chúng ta thay đổi kích thước cửa sổ trình duyệt.
1
2
3
4
5
6
7
8
window.onload = function() {
  setTopPadding();
  onScroll();
};
window.onresize = function() {
  setTopPadding();
};

Một khi chúng ta cuộn xuống vượt quá giới hạn của 150px, một vài thuộc tính CSS bổ sung sẽ được áp dụng:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
.scroll {
  box-shadow: 0 7px 0 0 rgba(0, 0, 0, .1);
}
.scroll .logo {
  padding: 10px 20px;
  font-size: 1.5rem;
}
.scroll nav {
  align-items: center;
}
.scroll .logo,
.scroll ul,
.scroll .toggle-menu {
  margin: 0;
}

Cụ thể, chúng ta tạo ra các thay đổi sau:

  • Thêm một bóng đổ vào header.
  • Giảm padding và kích thước phông chữ của logo.
  • Thay đổi việc sắp xếp các phần tử flex theo cross-axis (trục dọc).
  • Xoá margin khỏi logo, menu, và nút liên kết.

Các thuộc tính nói trên dẫn đến cách bố trí header mới này:

How the header looks like when our scrolling exceeds the limit of 150px

Như chúng ta đã đề cập trong phần trước, khi chiều rộng viewport nhỏ hơn 1061px, chúng ta ẩn menu và hiển thị nút liên kết (mà không thực sự làm bất cứ điều gì). Thêm vào đó, chúng ta tạo ra một số thay đổi khác trong các phần tử đích.

Bên dưới bạn có thể thấy hình ảnh ban đầu của header có tính ứng:

How the responsive header initially looks like

Dưới đây là các thuộc tính CSS cần thiết:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@media screen and (max-width: 1060px) {
  header {
    padding: 10px;
  }
  nav {
    align-items: center;
  }
  ul {
    display: none;
  }
  .logo {
    font-size: 1.8rem;
    margin: 10px 0 0 10px;
  }
  .toggle-menu {
    display: block;
  }
}

Và dưới đây là hình ảnh của header một khi nó đã được hoạt hình:

How the responsive header looks like when our scrolling exceeds the limit of 150px

Bây giờ chúng ta đã xoay sở để tạo cho header của chúng ta có hành vi như mong muốn, hãy tiến thêm bước nữa và thảo luận một số thứ về hiệu năng.

Trong ví dụ của chúng ta, mã để thực hiện hoạt hình của header (đó là callbackFunc) được thực thi khi sự kiện scroll được thực thi. Điều này có nghĩa là nó có thể được kích hoạt hàng trăm lần hoặc hơn. Điều này dẫn đến các vấn đề về hiệu năng, đặc biệt khi hàm callbackFunc chứa rất nhiều thứ mà sẽ được chạy khi chúng ta cuộn trang lên hoặc xuống. Trong trường hợp cụ thể của chúng ta, chúng ta đang đối diện với một hoạt hình đơn giản, nhưng hãy tưởng tượng một kịch bản thực tế, nơi mà chúng ta muốn làm những điều phức tạp hơn như tái định vị các phần tử và vân vân.

Do đó chúng ta có thể làm gì với điều này? À, có rất nhiều giải pháp có thể, nhưng bây giờ chúng ta hãy thảo luận ngắn về một trong số chúng. Cụ thể, chúng ta muốn cho phép hàm của chúng ta thực thi tối đa một lần mỗi 200 mili giây (đây là một giá trị tùy ý). Để cài đặt hàm này, chúng ta sẽ tận dụng lợi thế của Lodash, một thư viện tiện ích JavaScript hiện đại. Thư viện này cung cấp hàm throttle mà chúng ta cần.

Trước tiên, chúng ta thêm thư viện (mừng là cũng có sự lựa chọn để kết hợp chỉ các chức năng mà mình mong muốn) vào trong dự án của chúng ta, sau đó chúng ta thay thế mã này:

window.addEventListener("scroll", callbackFunc);

bằng cái này:

window.addEventListener("scroll", _.throttle(callbackFunc, 200));

Để hiểu sự khác biệt, chúng ta hãy làm một bài kiểm tra đơn giản. Chúng ta sẽ khởi tạo một bộ đếm mà tăng lên mỗi khi hàm callbackFunc được chạy.

Bây giờ thử cuộn trang lên và xuống. Khi bạn làm vậy, bạn sẽ nhận thấy rằng giá trị của số lượt đếm thay đổi khoảng mỗi 200ms. Tiếp theo, lặp lại quá trình này mà không sử dụng Lodash. Bạn sẽ nhận thấy rằng giá trị của số lượt đếm thay đổi nhanh hơn nhiều.

Một lần nữa, mặc dù sự tối ưu hóa này có vẻ không cần thiết cho ví dụ đơn giản của chúng ta, nhưng bạn nên cân nhắc nó khi bạn đang làm việc với các tác vụ tốn kém và lặp đi lặp lại.

Tương tự, chúng ta có thể tối ưu hóa sự kiện lắng nghe resize.

Hiệu ứng này hoạt động trong hầu hết các trình duyệt và thiết bị hiện đại. Tuy nhiên, trải nghiệm không phải là lý tưởng ở tất cả các thiết bị di động (ví dụ như các thiết bị iOS). Điều này xảy ra bởi vì sự kiện scroll cư xử khác nhau trên các trình duyệt máy tính để bàn so với thiết bị di động. Ví dụ, trên một trình duyệt máy tính để bàn các sự kiện scroll xảy ra liên tục, trong khi đó trên iPad nó xảy ra khi bạn vuốt và nhấc ngón tay của bạn lên.

Hiểu biết về các vấn đề nêu trên, bạn sẽ quyết định nó có phải là một mô hình hợp lý hay không để sử dụng trên trang web của bạn.

Trong hướng dẫn này, chúng ta tạo một header cố định, được hoạt hình khi chúng ta cuộn trang xuống. Tôi hy vọng bạn thích bản demo và bạn sẽ sử dụng nó như là nguồn cảm hứng trong dự án sắp tới của bạn!

Bài viết liên quan

Ý KIẾN KHÁCH HÀNG

Anh ơi tranh vẽ quá tuyệt luôn ạ, em cứ ngắm mãi không chán, có dịp lại đặt anh vẽ nữa nha. cảm ơn anh rất nhiều ạ!

 

Quỳnh Kool / Facebook

Qúa tuyệt vời bạn ơi, hơn cả sự mong đợi của mình luôn, không ngờ tranh vẽ lại đẹp hơn hình chụp luôn, mình rất hài lòng. Cảm ơn bạn rất nhiều!

Việt Anh / Facebook

Mình đặt gấp mà các anh vẫn giao rất nhanh chỉ một hai hôm. Tranh cực đẹp luôn. Cảm ơn các anh rất nhiều!

 

Đinh Minh Phương / Fakebook
Gọi ngay
Chat với chúng tôi qua Zalo
Facebook Messenger