2 điểm bởi GN⁺ 2024-12-06 | 1 bình luận | Chia sẻ qua WhatsApp
  • @stanko/dual-range-input kết hợp hai HTML range input native để tạo slider phạm vi kép, tận dụng nguyên vẹn tương tác mặc định và khả năng truy cập của trình duyệt
  • Phần triển khai chủ yếu gồm CSS và khoảng 50 dòng JavaScript để đồng bộ giá trị input, nên đơn giản hơn các cách làm truyền thống phải tự xây lại thao tác kéo và khả năng truy cập
  • Hai input được cập nhật min, max và chiều rộng theo điểm giữa của các giá trị đã chọn; khi nhấp vào track, thumb gần hơn sẽ di chuyển tới giá trị đó
  • Vì track của range input ngắn hơn chiều rộng thực tế của input, phần triển khai cộng thêm chiều rộng thumb vào phép tính và dùng padding của wrapper để hấp thụ phần chiều rộng vượt ra
  • Theme có thể được điều chỉnh bằng biến CSS và gradient, cho phép override màu vùng được chọn, kích thước thumb, màu track, v.v.

Slider kép được tạo bằng range input native

  • @stanko/dual-range-input triển khai input phạm vi kép bằng cách đặt hai HTML range input cạnh nhau
  • Ở đây, “native” không có nghĩa là một control trình duyệt đơn lẻ, mà là dùng hai range input để giữ lại tương tác mặc định và tính năng accessibility của trình duyệt
  • Phần triển khai gồm khoảng 50 dòng JavaScript để đồng bộ các input, cùng CSS để hoàn thiện phần hiển thị
  • Điểm khởi đầu là nhu cầu có một UI để điều chỉnh giá trị tối thiểu và tối đa của tham số trong một công cụ vẽ tạo sinh
  • Yêu cầu có hai điểm
    • Sử dụng HTML range input native
    • Khi nhấp vào track, thumb nào trong hai thumb gần vị trí nhấp hơn sẽ di chuyển tới giá trị đó

Đồng bộ giá trị và xử lý nhấp chuột

  • Khi một trong hai input thay đổi, thư viện tính điểm giữa giữa hai giá trị được chọn
    • Đặt thuộc tính minmax của từng input theo điểm giữa
    • Chiều rộng của hai input cũng được cập nhật dựa trên điểm giữa
  • Vì kích thước các input được điều chỉnh để gặp nhau tại điểm giữa, khi nhấp vào khoảng giữa hai thumb, thumb gần vị trí nhấp hơn sẽ di chuyển
    • Nếu số step giữa hai thumb là số lẻ, input được dùng gần nhất sẽ được ưu tiên

Tính toán CSS và styling

  • Track của range input ngắn hơn toàn bộ chiều rộng input, và trình duyệt chừa khoảng trống hai bên để thumb không lòi ra ngoài
    • Nếu chỉ chia chiều rộng input đơn giản như 25% và 75% thì tỷ lệ track thực tế sẽ không khớp
    • Phần triển khai cộng --dri-thumb-width vào chiều rộng của từng input, đồng thời thêm padding cho wrapper để chứa phần chiều rộng vượt ra
  • Styling sử dụng khả năng CSS styling của range input trên trình duyệt
    • Trang trí track và thumb, đồng thời xử lý để phần giữa nơi hai track gặp nhau không xuất hiện border radius
  • Theme có thể được tạo bằng cách override biến CSS
    • Các biến chính gồm --dri-height, --dri-thumb-width, --dri-thumb-color, --dri-track-color, --dri-track-filled-color, --dri-track-border-radius, v.v.
  • Phạm vi được chọn được tô bằng CSS gradient
    • Vị trí gradient được chỉ định bằng biến --dri-gradient-position
    • Khi cập nhật chiều rộng input trong mã, biến này cũng được cập nhật cùng lúc
  • Cách triển khai này là hướng tiếp cận ưu tiên tận dụng phần tử native nhiều nhất có thể, thay vì tự xây lại thao tác kéo và khả năng truy cập bằng thư viện tùy chỉnh

1 bình luận

 
GN⁺ 2024-12-06
Các ý kiến trên Hacker News
  • Tôi nghĩ loại slider này phần ở giữa cũng phải luôn kéo được. Tức là khi nắm ở giữa thì hai tay nắm ở hai bên sẽ di chuyển cùng lúc
    Tham khảo: Unity xử lý như vậy: https://docs.unity3d.com/StaticFiles/ScriptRefImages/EditorG...

    • Ngược lại, tôi chắc hẳn sẽ chẳng hề nghĩ tới việc nhấp vào vùng giữa. Trong đầu tôi, nó trông không phải một widget hoàn toàn mới mà là một slider biến thể
      “Luôn luôn” có lẽ hơi quá lời
  • Tôi là tác giả. Rất vui khi thấy nó được đưa lên HN
    Tôi thấy mọi người đã bắt đầu để lại issue và báo lỗi ở đây; chỗ tôi đã muộn nên mai tôi sẽ xem
    Tôi làm cái này vì cần cho công việc tạo ảnh bằng AI, và tôi thích kéo slider để xem hình ảnh thay đổi ra sao hơn là nhập số
    Tôi đã cố giữ nó tối giản nhất có thể

    • Bài này thật sự rất hay. Tôi vừa uống cà phê sáng vừa đọc phần quy trình tính toán và giải thích về chế độ gỡ lỗi, thấy rất thú vị, và cảm giác đây là một cách tận dụng component native rất khéo
      Hiện tôi đang dùng SwiftUI, nơi việc tự tạo component gần như quá dễ. Nhưng như tác giả giải thích, đó không phải lúc nào cũng là lựa chọn tốt nhất, và component native thường có khá đầy đủ chức năng
      Tôi cũng thử xem bài trên một trình duyệt hơi đặc biệt là Firefox trên iOS, và nó hoạt động tốt. Làm rất tốt
  • Tôi vừa thử dùng đã gặp lỗi ngay. Sau khi kéo slider về 100 - 100, nếu muốn đưa về 99 - 99 thì không được
    Từ 99 - 100 cũng không đi được, từ 98 - 99 cũng không đi được. Cách duy nhất để tới 99 - 99 là kéo cận dưới xuống thấp hơn 98
    Ở trạng thái 98 - 99, nếu hạ xuống 97 - 99 thì lúc đó mới có thể tới 99 - 99
    Lỗi này có thể xảy ra ở bất cứ đâu; 99 - 99 chỉ là dễ tái hiện nhất vì gần cuối slider. Nói chung, ở một trạng thái X - X nào đó, nếu di chuyển một đầu đi 1 bước, bạn không thể kéo đầu còn lại vào khoảng trống đó. Muốn đóng khoảng cách đó, trước tiên phải kéo đầu kia ra xa hơn

    • Bắt lỗi hay đấy! Thật ra tôi đã nghĩ tới trường hợp này và nó hoạt động trong Chrome và Firefox, nhưng Safari có vẻ không phát sinh sự kiện focus khi mouse down, mà chỉ phát sinh khi bắt đầu di chuyển slider
      Cảm ơn báo cáo, tôi nhất định sẽ sửa
    • Trên Chromium ở desktop Linux, 100-100 → 99-100 → 99-99 hoạt động ổn
      Nhưng có một lỗi hiển thị khác. Nếu bắt đầu ở 23-25 rồi kéo thành 24-25, tay nắm bên phải ở 25 sẽ dịch chuyển vài pixel. Khi đưa tay nắm bên trái về lại 23, tay nắm bên phải cũng quay lại
    • Đó là lý do tôi thích làm công cụ cho lập trình viên
  • Hay đấy! Tôi luôn nghĩ HTML input cần có cách triển khai nhập khoảng với hai giá trị, vì nó khá hợp lý
    Ví dụ có use case như “tôi muốn lọc các sản phẩm có số widget ít nhất là X và nhiều nhất là Y”
    Tuy nhiên tôi tìm thấy một lỗi muốn nhấn mạnh trên Firefox. Mỗi lần nhấp vào tay nắm min hoặc max, giá trị lại đổi lên hoặc xuống một bước
    Nếu cố nhấp đúng vào giữa tay nắm thì giá trị không đổi, nhưng nhấp hơi lệch sang trái thì giá trị được chọn giảm một bước, còn hơi lệch sang phải thì tăng
    Hành vi mong đợi là giá trị chỉ đổi khi có thao tác thay đổi rõ ràng: giữ nút và thực sự di chuyển con trỏ chuột

    • Thành thật mà nói, tôi không nghĩ đó là lỗi. Tất cả ví dụ đều khá dày đặc, và tay nắm che phủ nhiều bước trên track
      Firefox diễn giải những cú nhấp đó như nhấp trực tiếp vào track. Thư viện này hoàn toàn không can thiệp vào đó; đó chỉ là cách Firefox hoạt động
  • Việc nó hoạt động bằng bàn phím là rất tuyệt. Nó hoạt động đúng, nên nếu giữ Shift-Tab thì cũng có thể di chuyển lùi
    Làm thật sự tốt, và dù chỉ là chi tiết rất nhỏ, tôi vẫn biết ơn vì phần tương tác đó được triển khai chính xác

  • Trong design system của công ty chúng tôi, một thực tập sinh cũng từng làm proof-of-concept đặt cạnh nhau kiểu này, nhưng luôn có hiện tượng pixel của một input bị nhảy lặt vặt và cuối cùng không làm cho đúng được
    Vì vậy một thực tập sinh thứ hai đã đưa ra giải pháp dùng 2 input range chồng lên nhau

  • Mỗi khi cần nhập khoảng, tôi đều dùng slider của jQuery UI, và phải thêm jQuery cùng jQuery UI vào trang chỉ vì một slider. Cái này thật sự tốt
    Sửa: Có khả năng dựng sẵn một file JS thường dùng trực tiếp thay vì module ESM không?

  • Không biết báo lỗi ở đây có được không. Tay nắm trong hai ví dụ đầu chặn tương tác chạm và ngăn cuộn. Ba ví dụ sau thì không
    Hệ quả là không thể kéo lên để nhìn nhãn nằm dưới ngón tay, và trang bị cuộn khiến nhãn vẫn cứ nằm dưới ngón tay

    • Nếu tái hiện được thì bạn mở GitHub issue giúp nhé [1]
      Tôi muốn biết bạn đang dùng trình duyệt và hệ điều hành nào. Thư viện này dùng input native nên không làm gì liên quan tới việc chặn cuộn
      Trên điện thoại iOS của tôi, việc cuộn bị chặn trên mọi phần tử input[type=range], bất kể có áp dụng style hay không
      [1] https://github.com/Stanko/dual-range-input/issues
  • Phần gọi là “native” còn có thể tranh luận. Ý là thư viện dùng hai HTML range input native, nên giữ được tương tác native và các tính năng accessibility, nhưng tiêu chí của tôi thì rõ ràng
    Nếu để hoạt động như dự định mà cần JavaScript, thì nó không còn là native nữa

    • Đồng ý. Tôi nghĩ thông thường người ta cũng dùng từ như vậy
      Dù vậy tôi hiểu ý tác giả. Vì họ ghép các slider thông thường thay vì tự triển khai một đống div chạy bằng JS, nên đây gần với giải pháp native hơn nhiều. Nếu không thể thay JavaScript bằng CSS khéo léo, có lẽ đây là cách gần nhất có thể
      Bất kể có native hay không, loại widget này nếu dùng được thì rất hữu ích, và trong mảng đó còn thiếu implementation tốt
    • Thường là vậy, nhưng ranh giới không hoàn toàn rõ. Phần tử có thể là native, nhưng có trường hợp vẫn phải dùng JavaScript để mở ở trạng thái modal
      Trước khi thuộc tính popover xuất hiện, không có JavaScript thì hoàn toàn không mở được
      Ngoài ra cũng có những phương thức JavaScript vanilla native đáng được phân biệt với các thư viện hoàn toàn không dùng công cụ xác thực nội bộ của trình duyệt, như form validation
  • Tôi đã tìm một thứ như thế này khi làm https://spodj.intermer.net/
    Tôi đã phải dùng React khá vụng về để có được chức năng cần thiết, vì tôi không giỏi phát triển front-end