Việc xây dựng các mục chọn Select (Select Field) đẹp về hình thức và hoạt động tốt cả về tính năng là một bài toán rất cơ bản dành cho các lập trình viên khi bắt đầu vào lập trình Frontend. Code Tốt xin hướng dẫn cách xây dựng một tính năng hoàn thiện và hoạt động tương thích đúng chuẩn responsive.
Các bạn hẳn đã biết khi nhấp vào một mục chọn và xổ ra danh sách, trông nó sẽ như thế này:
Câu trả lời đó chính là bài tập xây dựng Custom Select Field mà hôm nay Code Tốt sẽ chia sẻ. Do đây là một bài viết theo hướng training, kết quả code sẽ không được công bố ở cuối bài. Nhiệm vụ của bạn là hoàn thành theo hướng dẫn trên nhé!
Chuẩn bị
Để xây dựng được tính năng Custom Select Field, bạn cần:
- HTML5 để dựng markup
- CSS3 để dựng layout và style
- Javascript để xác định và đồng bộ dữ liệu hiển thị tới người dùng
Bắt đầu
Xác định các đối tượng chính trong markup (HTML5 + CSS3)
Đầu tiên, ta phải nắm rõ các thành phần sẽ xuất hiện và tương tác với nhau.
Trên mobile: Ta cần 1 <select>
field hoạt động như bình thường.
Trên desktop: Ta cần 1 <ul>
chứa các dữ liệu hệt như trên mobile.
Quan trọng hơn, ta cần 1 <div>
giả lập giúp hiển thị dữ liệu đã chọn bất kể là trên mobile hay desktop.
Tiếp theo, để responsive thì cần ẩn hiện các element này tương ứng các breakpoint bằng @media
queries.
Trạng thái hoạt động (Javascript)
Sự xuất hiện và thay thế trên màn hình khi người dùng click chuột vào select này sẽ được sync bởi Javascript. Khi click vào select, ta tìm giá trị của select field, từ đó tìm ra <option>
nào đang chứa value hiện tại, tiếp theo ta lấy hai giá trị value
và innerHTML
của option để tìm <li>
tương ứng và bật class active
cho item này (thì như vậy trên desktop sẽ xuất hiện trạng thái tương đương sau khi resize window). Thêm nữa, để giá trị hiển thị thì lấy innerHTML
của <option>
để hiển thị trong <div>
chứa markup hiện tại.
Mô phỏng Markup Select Field ban đầu (HTML5)
<div class="select-field">
<!-- Chứa "text" hiện tại" hoặc placeholder ban đầu -->
<div class="select-field__current"></div>
<!-- Hiển thị chỉ trên mobile -->
<div class="select-field__mobile">
<select></select>
</div>
<!-- Hiển thị chỉ trên desktop -->
<div class="select-field__desktop">
<ul></ul>
</div>
</div>
(Các class được xác định theo BEM Class, các bạn tự tìm hiểu thêm ở đây).
Xác định các thành phần ẩn hiện trong từng breakpoint (SCSS)
Code bằng SCSS, các bạn nào chưa quen có thể paste vào codepen rồi chọn compile code để hiện ra CSS nhé.
$mobile-breakpoint: 480px;
.fade-out {
opacity: 0;
visibility: hidden;
}
.fade-in {
opacity: 1;
visibility: visible
}
.select-field__mobile {
display: block;
@media (min-width: $mobile-breakpoint) {
display: none;
}
}
.select-field__desktop {
@extend .fade-out; // Ẩn ngay cả khi trên desktop và chỉ hiện ra nếu có class 'active'
display: none;
@media (min-width: $mobile-breakpoint) {
display: block;
}
}
Lý do của việc dùng opacity: 0
là để tạo hiệu ứng mượt hơn khi hiện dropdown menu ra trên desktop.
Triển khai style cho các đối tượng (SCSS)
Element current
luôn hiển thị
Trên mobile, để <select>
hoạt động trong khi .select-field__current
vẫn hiển thị, ta cần set pointer-events: none
cho select-field__current
.
.select-field__current {
pointer-events: none; // Để click xuyên qua div này vào được .select-field__mobile
cursor: pointer; // Để người dùng biết là có thể click
@media (min-width: $mobile-breakpoint) {
pointer-events: visible;
}
}
Mobile Select
Đối với field Mobile, ta cần đảm bảo nó nằm chồng lên markup current.
.select-field__mobile {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 45px;
select {
width: 100%;
height: 45px;
}
}
Dropdown trên Desktop
Với dropdown trên Desktop, ta cần giả lập cách nó hiển thị (thông thường sẽ nằm dưới dropdown như một menu):
.select-field__desktop {
position: absolute;
top: 45px;
left: 0;
width: 100%;
max-height: 0; // Dùng transition max-height sẽ không bị giật
transition: all .3s ease;
// Trạng thái khi click vào field current và hiện ra trên desktop
.dropdown-active & {
max-height: 9999px;
}
ul {
width: 100%;
background: #000;
}
ul li {
padding: 10px;
color: rgba(#fff, 0.8);
transition: all 0.3s ease; // Animation mượt hơn
&:hover {
color: #fff;
}
&.active {
color: #fff;
}
}
}
Triển khai hoạt động đồng bộ dữ liệu với Javascript
Sau khi dựng markup, ta cần xem cách thức dữ liệu được đồng bộ như thế nào. Mô phỏng trạng thái có thể hiểu như sau:
Ta sẽ tách ra các function hoạt động khác nhau trên mobile và desktop. Function làm việc trên mobile sẽ làm việc với select.value
. Nhóm function desktop lại chia ra làm 2 tình huống: làm việc với li
trong dropdown và làm việc với __desktop
khi click vào __current
Toàn bộ phần code sẽ sử dụng ES5 Javascript không cần thư viện nào cả nhé. Hãy bắt đầu!
Xác định các object ta sẽ sử dụng để tương tác
Đầu tiên, bạn nên gắn các JS-class, ám chỉ việc sử dụng các class chỉ để gọi trong Javascript như markup thay đổi sau:
var el = document.querySelector('.select-field');
var select = el.querySelector('select');
var items = Array.prototype.slice.call(el.querySelectorAll('li'));
var current = el.querySelector('.js-current');
Nếu bạn nào chưa rõ tại sao dùng Array.prototype.slice.call
thì thực chất đây là 1 protype giúp object bên trong có thể được dùng với forEach
. Nếu không dùng thì bạn mở Safari lên sẽ thấy báo lỗi đấy nhé!
Tiếp theo,bạn xác định các class Active sẽ add vào dropdown và item trong dropdown (tiện thay đổi về sau nếu cần đỡ phải lò dò đi tìm):
var dropdownActiveClass = 'dropdown-active';
var dropdownItemActiveClass = 'item-active';
Mô phỏng trạng thái TRUYỀN DỮ LIỆU trên mobile
Bước tiếp theo, ta tìm logic để xây dựng việc truyền dữ liệu từ select mobile lên current field hiển thị và active item <li>
tương ứng:
function selectOnMobile() {
select.addEventListener('click', function() {
// Ta cần tìm "value" và "text" là đủ
var currentValue = this.value;
var matchingItem = document.querySelector('[data-value="' + currentValue + '"]');
var currentText = matchingItem.innerHTML;
// Giờ là lúc thay đổi markup của current field
current.innerHTML = currentText;
// Để tránh bug, ta cần tìm các item khác đang active và remove class active đi đã
items.forEach(function( item ) {
item.classList.remove(dropdownItemActiveClass);
});
// Sau đó mới add active state vào item trên desktop
matchingItem.classList.add(dropdownItemActiveClass);
});
}
Bạn thấy có khó không? Thực ra không quá khó phải không???
Mô phỏng trạng thái active của Dropdown trên khung màn hình Desktop
Giờ thì ta cần làm việc để khi click vào __current
thì bật lên dropdown
mà chúng ta đã dựng markup. Nên set 1 class dropdownActiveClass cho nó tắt/bật ngang hàng với .select-field
là đơn giản nhất.
function toggleDropdown() {
current.addEventListener('click', function() {
el.classList.toggle(dropdownActiveClass);
});
}
Còn chuyện xử lý style hiển thị hãy để CSS lo. Xem lại mục style cho Dropdown nhé.
Đồng bộ dữ liệu sau khi click vào item desktop
Chuyện vẫn chưa kết thúc đâu. Giờ ta cần xác định khi click vào <li>
thì chuyện gì sẽ xảy ra? Cách tốt nhất là ta nên kết hợp sử dụng data-attribute
trong tình huống này. Markup sẽ có dạng như sau:
<ul>
<li data-value="thoi-trang">Thời trang</li>
<li data-value="nha-cua">Nhà cửa</li>
</ul>
Lý do là các giá trị value sẽ dễ tìm và không chứa các kí tự lạ thì khi set giá trị select.value
của mobile sẽ rất dễ.
Giờ ta bắt đầu viết function để hoạt động khi click vào 1 item <li>
nhé:
function clickOnDesktop() {
items.forEach( item ) {
item.addEventListener('click', function() {
// Đi tìm các giá trị cần thiết là text và value
var currentText = item.innerHTML;
var currentValue = item.getAttribute('data-value');
// Giờ ta cần làm vài việc.
// Nếu item vừa click là item active thì bỏ qua không cần làm gì cả
if( !this.hasClass(dropdownItemActiveClass) ) {
// Remove class active nếu có của các item khác.
items.forEach(
function( item ) {
item.classList.remove(dropdownItemActiveClass);
}
);
this.classList.add(dropdownItemActiveClass)
// Add text vào field current
current.innerHTML =currentText;
// Set giá trị cho <select>, thì khi resize window mở ra trên mobile sẽ thấy đang select đúng item rồi.
select.value = currentValue;
}
});
}
}
Code trên hoàn toàn là đang viết theo logic mà chưa có kiểm thử nên bạn lưu ý là làm lần lượt các bước theo như Code Tốt hướng dẫn nhé. Nếu có gì đó sai, bạn có thể comment trong bài viết này.
Bài tập dành cho bạn
Nếu các function trên đã hoạt động, bạn có thể viết 1 function để khi load web thì sẽ tự động lựa chọn giá trị đầu tiên hiển thị cho người dùng không?
Các bạn nào comment link codepen các bạn đã làm thành công sẽ nhận được ngay giao diện WordPress trả phí Total (trị giá $60.00) được chia sẻ miễn phí để bạn làm website cực dễ dàng nhé!