noUiSlider là gì và vì sao nên dùng
noUiSlider là thư viện JavaScript nhẹ, không phụ thuộc jQuery, giúp bạn tạo các thanh trượt (slider) đơn hoặc đôi để chọn giá trị hoặc khoảng giá trị. Thư viện nổi bật nhờ:
- Kích thước nhỏ, hiệu năng cao, hoạt động tốt trên di động và desktop
- API nhất quán, dễ tích hợp framework hoặc thuần JavaScript
- Tùy biến mạnh: nhiều kiểu range (tuyến tính/phi tuyến), pips (vạch chia), tooltip, behaviour (tap/drag/snap), vertical/horizontal, RTL
- Hỗ trợ accessibility (ARIA) và thao tác bàn phím
Ứng dụng phổ biến: lọc khoảng giá trong thương mại điện tử, chọn ngưỡng dữ liệu, điều khiển âm lượng/độ sáng, điều chỉnh thông số trực quan.
Cài đặt noUiSlider
Bạn có thể cài qua CDN hoặc npm. Nhớ import cả CSS để slider hiển thị đúng.
-
CDN:
-
npm:
npm i nouislider
Import trong module:
import ‘nouislider/dist/nouislider.css’;
import noUiSlider from ‘nouislider’;
Khởi tạo cơ bản
HTML:
JavaScript:
const slider = document.getElementById(‘price-slider’);
noUiSlider.create(slider, {
start: [20, 80], // hai tay cầm
connect: true, // tô màu phần giữa
range: { min: 0, max: 100 }, // khoảng giá trị
step: 1, // bước nhảy
orientation: ‘horizontal’,
direction: ‘ltr’,
tooltips: true
});
- start: có thể là một số (1 tay cầm) hoặc mảng (2 tay cầm).
- connect: true để tô màu giữa hai tay cầm; ‘lower’ hoặc ‘upper’ cho slider một tay cầm.
- step: bước nhảy; bỏ qua nếu cần trượt mượt tuyệt đối.
- orientation: ‘horizontal’ hoặc ‘vertical’.
- direction: ‘ltr’ (trái sang phải) hoặc ‘rtl’ (phải sang trái).
- tooltips: hiển thị tooltip theo format.
Định dạng giá trị và tooltip theo ngôn ngữ/tiền tệ
Sử dụng Intl.NumberFormat để hiển thị tiền tệ VND có phân cách hàng nghìn.
const vnd = new Intl.NumberFormat(‘vi-VN’, { style: ‘currency’, currency: ‘VND’, maximumFractionDigits: 0 });
noUiSlider.create(slider, {
start: [300000, 2500000],
connect: true,
range: { min: 0, max: 10000000 },
tooltips: {
to: (v) => vnd.format(Number(v)),
from: (str) => Number(str.replace(/[^d.-]/g, ”))
},
format: {
to: (v) => Math.round(v), // giá trị get/set
from: (v) => Number(v)
}
});
- format: ảnh hưởng đến get()/set(), phù hợp khi bạn muốn làm tròn trước khi đọc/ghi.
- tooltips: kiểm soát riêng cách hiển thị trong tooltip (nên tách khỏi format nếu cần khác nhau).
Range tuyến tính và phi tuyến (non-linear)
Non-linear hữu ích khi bạn cần độ nhạy khác nhau theo vùng (ví dụ khoảng giá cao cần bước nhảy lớn hơn).
noUiSlider.create(slider, {
start: [10, 200],
connect: true,
range: {
min: 0,
‘20%’: 50,
‘50%’: 200,
max: 1000
},
snap: false
});
- range dùng nhiều mốc phần trăm để ánh xạ sang giá trị thực.
- snap: true sẽ “hít” vào các bước; false để trượt mượt.
Sự kiện quan trọng
const api = slider.noUiSlider;
api.on(‘update’, (values, handle, unencoded, tap, positions) => {
// update: gọi liên tục khi trượt (thích hợp cập nhật UI nhẹ)
});
api.on(‘slide’, (values) => {
// slide: khi tay cầm đang trượt
});
api.on(‘set’, (values) => {
// set: sau khi giá trị được “chốt”
});
api.on(‘change’, (values) => {
// change: khi giá trị thực sự thay đổi so với trước
});
api.on(‘start’, () => {});
api.on(‘end’, () => {});
- values: mảng giá trị (đã format.to).
- unencoded: giá trị nguyên thủy dạng số.
- positions: phần trăm vị trí tay cầm trên thanh.
Mẹo hiệu năng: hạn chế logic nặng trong update; nếu cần gọi API, hãy debounce hoặc chỉ thực hiện ở sự kiện set/change.
Đọc/ghi giá trị bằng code
-
Lấy giá trị:
const current = api.get(); // chuỗi hoặc mảng chuỗi
const numeric = Array.isArray(current) ? current.map(Number) : Number(current); -
Gán giá trị:
api.set([15, 90]); // gán cả hai tay cầm
api.set(50); // slider một tay cầm
api.setHandle(0, 20); // gán cho tay cầm chỉ định (0 hoặc 1) -
Reset:
api.reset();
Cập nhật tùy chọn động
api.updateOptions(
{
range: { min: 0, max: 200 },
step: 5
},
true // fireSetEvent: true để phát sự kiện set sau cập nhật
);
Ghi chú: Không phải mọi tùy chọn đều có thể cập nhật động; xem tài liệu bản bạn dùng để biết danh sách hỗ trợ.
Khoảng cách giữa tay cầm và padding biên
- margin: khoảng cách tối thiểu giữa hai tay cầm
- limit: khoảng cách tối đa giữa hai tay cầm
- padding: khoảng đệm không cho tay cầm chạm sát mép thanh
noUiSlider.create(slider, {
start: [20, 80],
connect: true,
range: { min: 0, max: 100 },
margin: 10, // cách nhau tối thiểu 10
limit: 60, // tối đa cách nhau 60
padding: 5 // không chạm sát biên
});
Pips (vạch chia) và nhãn
api.pips({
mode: ‘positions’, // ‘range’ | ‘steps’ | ‘positions’ | ‘count’ | ‘values’
values: [0, 25, 50, 75, 100],
density: 3,
format: {
to: (v) => ${Math.round(v)}%
}
});
- density càng lớn, vạch phụ càng dày.
- format điều khiển nhãn hiển thị ở pips.
Vertical slider và hỗ trợ RTL
-
Vertical:
noUiSlider.create(slider, {
start: 40,
orientation: ‘vertical’,
range: { min: 0, max: 100 },
connect: ‘lower’,
tooltips: true
}); -
RTL (hữu ích với giao diện tiếng Ả Rập/Hebrew):
noUiSlider.create(slider, {
start: [70, 90],
direction: ‘rtl’,
range: { min: 0, max: 100 },
connect: true
});
Accessibility và bàn phím
noUiSlider đặt ARIA role=”slider” và thuộc tính aria-* phù hợp. Bạn có thể:
- Đảm bảo contrast tốt giữa handle và nền
- Bật focus styles rõ ràng (outline)
- Kiểm tra thao tác bằng phím mũi tên, PageUp/PageDown, Home/End
- Cung cấp nhãn (aria-label hoặc aria-labelledby) cho từng tay cầm
Ví dụ:
const handles = slider.querySelectorAll(‘.noUi-handle’);
handles[0].setAttribute(‘aria-label’, ‘Giá tối thiểu’);
handles[1]?.setAttribute(‘aria-label’, ‘Giá tối đa’);
Tích hợp form và SEO on-page
- Đồng bộ input ẩn để form submit:
const minInput = document.querySelector(‘#min’);
const maxInput = document.querySelector(‘#max’);
api.on(‘set’, (values) => {
[minInput.value, maxInput.value] = values.map(v => Math.round(Number(v)));
});
- Tránh render-blocking: tải CSS noUiSlider sớm, defer JS nếu cần.
- Nội dung mô tả xung quanh slider (ví dụ “Lọc theo khoảng giá”) giúp công cụ tìm kiếm hiểu ngữ cảnh tốt hơn.
Các lỗi thường gặp và cách khắc phục
- Slider không hiển thị đúng: quên import CSS noUiSlider.
- Giá trị trả về là chuỗi: dùng Number() hoặc format để ép số.
- Tooltip hiển thị sai định dạng tiền tệ: dùng Intl.NumberFormat hoặc wNumb đúng ngôn ngữ.
- Trượt giật hoặc lag: sự kiện update chứa logic nặng; debounce hoặc chuyển sang set/change.
- Bước nhảy không khớp: kiểm tra step và range (đặc biệt non-linear); snap có thể làm “hít” vào mốc.
- Không kéo được trên di động: kiểm tra overlay CSS, z-index, hoặc phần tử chồng lên handle.
- Hướng hiển thị ngược: cấu hình direction: ‘rtl’ hoặc đổi orientation/logic vị trí.
Mẫu hoàn chỉnh: bộ lọc khoảng giá thương mại điện tử
HTML:
CSS gợi ý:
.filter { max-width: 360px }
.noUi-tooltip { font-size: 12px }
JS:
const slider = document.getElementById(‘price-slider’);
const minText = document.getElementById(‘min-text’);
const maxText = document.getElementById(‘max-text’);
const minInput = document.getElementById(‘min’);
const maxInput = document.getElementById(‘max’);
const vnd = new Intl.NumberFormat(‘vi-VN’, { style: ‘currency’, currency: ‘VND’, maximumFractionDigits: 0 });
noUiSlider.create(slider, {
start: [300000, 2500000],
connect: true,
range: { min: 0, max: 10000000 },
step: 10000,
margin: 50000,
tooltips: {
to: v => vnd.format(Number(v)),
from: str => Number(str.replace(/[^d.-]/g, ”))
},
format: {
to: v => Math.round(Number(v)),
from: v => Number(v)
},
pips: { mode: ‘positions’, values: [0, 25, 50, 75, 100], density: 3 }
});
const api = slider.noUiSlider;
const render = (values) => {
const [min, max] = values.map(Number);
minText.textContent = vnd.format(min);
maxText.textContent = vnd.format(max);
minInput.value = min;
maxInput.value = max;
};
api.on(‘update’, render);
api.on(‘set’, render);
Câu hỏi thường gặp
- Có cần jQuery không? Không, noUiSlider hoạt động thuần JavaScript.
- Dùng được trong React/Vue/Svelte? Có, chỉ cần gắn vào ref/mount, gọi create trong lifecycle tương ứng và dọn dẹp khi unmount.
- Có hỗ trợ nhiều tay cầm hơn 2? Chủ yếu là 1 hoặc 2 handle. Với nhiều mốc, hãy cân nhắc pips hoặc custom UI.
- Có lazy-load được không? Có. Tải JS/CSS khi cần để tối ưu tốc độ trang.
- Tương thích trình duyệt nào? Hoạt động tốt trên trình duyệt hiện đại. Với môi trường cũ, kiểm tra polyfill cho classList, Pointer/Touchevents nếu cần.
Kết luận
noUiSlider cung cấp nền tảng slider mượt, linh hoạt và giàu tùy biến cho mọi dự án web. Bằng cách kết hợp format đúng ngôn ngữ, tối ưu sự kiện và styling gọn nhẹ, bạn có thể tạo trải nghiệm lọc và nhập liệu trực quan, thân thiện người dùng và bền vững cho SEO.