Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. JavaScript

Xây dựng Carousel hoàn hảo, phần 1

by
Difficulty:AdvancedLength:LongLanguages:
This post is part of a series called Create the Perfect Carousel.
Create the Perfect Carousel, Part 2

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Carousel là một yếu tố quan trọng của các website phát trực tuyến và thương mại điện tử. Cả Amazon và Netflix đều sử dụng carousel với vai trò điều hướng nổi bật. Trong hướng dẫn này, chúng tôi sẽ đánh giá thiết kế tương tác của cả hai và sử dụng các phát hiện của chúng tôi để triển khai carousel hoàn hảo.

Trong loạt bài hướng dẫn này, chúng tôi cũng sẽ tìm hiểu một số hàm của Popmotion, một cơ chế chuyển động JavaScript. Cơ chế này cung cấp các công cụ hoạt hình như tweens (hữu ích cho việc phân trang), theo dõi con trỏ (cho thao tác cuộn chuột) và các hiệu ứng vật lý của mùa xuân.

Phần 1 sẽ đánh giá Amazon và Netflix đã triển khai phần cuộn chuột như thế nào. Sau đó, chúng tôi sẽ triển khai carousel có thể cuộn qua thao tác chạm.

Cuối loạt bài này, chúng tôi sẽ triển khai wheel và touchpad cuộn, phân trang, thanh tiến trình, điều hướng bàn phím, và một số điều chỉnh nhỏ bằng cách sử dụng vật lý spring. Chúng tôi cũng sẽ khám phá với một số thành phần hàm cơ bản.

Hoàn hảo?

Điều gì cần thiết cho một carousel để trở thành hoàn hảo? Nó cần phải có thể truy xuất được bằng:

  • Mouse: cho phép dễ dàng thao tác các nút previous (trước) và next (tiếp theo) và không che khuất nội dung.
  • Touch: theo dõi ngón tay và sau đó cuộn chuột với cùng động lượng như khi ngón tay nhấc lên khỏi màn hình.
  • Scroll wheel (di chuyển nút bánh xe chuột): Thao tác này thường bị bỏ qua, Apple Magic Mouse và nhiều trackpad máy tính xách tay hỗ trợ thao tác cuộn ngang khá trơn tru. Chúng ta nên tận dụng các lợi thế này.
  • Keyboard: Nhiều người dùng không thích hoặc không thể sử dụng chuột để điều hướng. Quan trọng là chúng tôi triển khai carousel để những người dùng đó cũng có thể dùng sản phẩm này.

Cuối cùng, chúng ta sẽ tiến thêm một bước nữa và cải tiến thành một UX thú vị bằng cách làm cho carousel phản hồi rõ ràng với vật lý spring khi thanh trượt tiến đến phần cuối cùng.

Cài đặt

Đầu tiên, hãy lấy HTML và CSS cần thiết để xây dựng một carousel sơ khai bằng cách lấy code từ CodePen..

Pen được thiết lập cùng với Sass để xử lý CSS và Babel để chuyển đổi JavaScript ES6. Tôi cũng đã bao gồm Popmotion, có thể được truy xuất qua window.popmotion.

Bạn có thể sao chép code vào một dự án cục bộ nếu bạn thích, nhưng bạn sẽ cần phải đảm bảo môi trường của bạn hỗ trợ Sass và ES6. Bạn cũng sẽ cần cài đặt Popmotion bằng npm install popmotion.

Tạo một carousel mới

Trên trang bất kỳ, chúng ta có thể có nhiều carousel. Vì vậy, chúng ta cần một phương pháp để đóng gói trạng thái và hàm của mỗi carousel.

Tôi sẽ sử dụng một function factory thay vì một class. Các function factory tránh nhu cầu phải sử dụng từ khóa thường gây nhầm lẫn this và sẽ đơn giản hóa code cho các mục đích của hướng dẫn này.

Trong trình soạn thảo JavaScript của bạn, hãy thêm function đơn giản này:

Chúng tôi sẽ thêm code carousel cụ thể của chúng tôi vào bên trong hàm carousel này.

Cách thực hiện và lý do triển khai thao tác cuộn

Nhiệm vụ đầu tiên là làm cho carousel cuộn. Có hai cách chúng ta có thể thực hiện điều này:

Thao tác cuộn nguyên thuỷ từ trình duyệt

Giải pháp hiển nhiên sẽ là đặt overflow-x: scroll trên thanh trượt. Điều này sẽ cho phép việc cuộn diễn ra tự nhiên trên tất cả các trình duyệt, bao gồm cả việc chạm cản ứng và các thiết bị hỗ trợ mouse wheel ngang.

Tuy nhiên, có những hạn chế đối với phương pháp này:

  • Nội dung bên ngoài vùng chứa sẽ không hiển thị, có thể bị hạn chế đối với thiết kế của chúng tôi.
  • Điều này cũng giới hạn những cách chúng ta có thể sử dụng diễn hoạt để cho biết chúng ta đã tiến đến đoạn cuối.
  • Các trình duyệt trên máy tính thông thường sẽ có thanh cuộn ngang khá xấu (dù có thể sử dụng được).

Một cách khác:

Diễn hoạ translateX

Chúng tôi cũng có thể diễn hoạt cho thuộc tính translateX của carousel. Điều này sẽ linh hoạt vì chúng tôi có thể triển khai thiết kế chính xác mà chúng tôi thích. translateX rất hiệu quả, không giống như thuộc tính CSS left, nó có thể được xử lý bởi GPU của thiết bị.

Mặt khác, chúng tôi sẽ phải triển khai lại hàm cuộn bằng JavaScript. Công việc nhiều hơn, nhiều code hơn.

Làm thế nào để Amazon và Netflix tiếp cận thao tác cuộn?

Cả carousel của Amazon và Netflix đều tạo ra sự cân bằng khác biết khi tiếp cận vấn đề này.

Amazon diễn hoạt thuộc tính left trên chế độ "desktop". Việc diễn hoạt left là một chọn lựa cực kỳ tồi tệ, khi thay đổi sẽ khởi động việc tính toán lại bố cục. Việc này gây áp lực cho CPU, và những chiếc máy cũ phải vật vã để chạm đến 60fps.

Bất cứ ai đưa ra quyết định diễn hoạt left thay vì translateX phải là một tên ngốc thực sự (tiết lộ: đó là tôi, năm 2012. Chúng tôi đã không được sáng suốt trong những ngày đó.)

Khi phát hiện thiết bị cảm ứng, carousel sử dụng thao tác cuộn nguyên thuỷ của trình duyệt. Vấn đề khi sử dụng tính năng này trong chế độ "di động" là người dùng máy tính thông thường với mouse wheel theo phương ngang không thể sử dụng được. Điều đó cũng có nghĩa là bất kỳ nội dung nào bên ngoài carousel sẽ phải bị che khuất:

Screenshot of Amazon illustrating lack of design bleed

Netflix diễn hoạt chính xác thuộc tính translateX của carousel và triển khai trên tất cả các thiết bị. Điều này cho phép họ có một thiết kế tràn ra khỏi bên ngoài carousel:

Screenshot of Netflix carousel illustrating design bleed

Điều này lần lượt cho phép họ tạo ra một thiết kế lạ mắt, nơi các thành phần được mở rộng ra ngoài các cạnh x và y của carousel và các thành phần xung quanh di chuyển ra khỏi đường đi của chúng:

Screenshot of Netflix carousel illustrating enlarged item

Thật không may, việc triển khai carousel của Netflix trên các thiết bị cảm ứng không đạt yêu cầu: nó sử dụng một hệ thống phân trang dựa trên hành vi, kết quả cho thấy chậm và rườm rà. Cũng không có cài đặt cho mouse wheel theo chiều ngang.

Chúng ta có thể làm tốt hơn. Hãy cùng code nào!

Thực hiện thao tác cuộn như một chuyên gia

Bước đầu tiên của chúng ta là lấy node .slider. Hãy lấy các phần tử đang nằm bên trong để chúng ta có thể tìm ra kích thước của thanh trượt.

Đo đạc carousel

Chúng ta có thể tìm ra vùng hiển thị của thanh trượt bằng cách đo chiều rộng của nó:

Chúng tôi cũng sẽ muốn tổng chiều rộng của tất cả các phần tử chứa bên trong. Để hàm carousel của chúng ta tương đối gọn gàng, hãy đặt phép tính này vào một hàm riêng biệt ở đầu file.

Bằng cách sử dụng getBoundingClientRect để đo offset (độ lệch) left của phần tử đầu tiên và offset right của thành phần cuối cùng của chúng tôi, chúng tôi có thể sử dụng sự khác biệt giữa chúng để tìm tổng chiều rộng của tất cả các thành phần.

Sau phép đo sliderVisibleWidth của chúng tôi, hãy viết:

Bây giờ chúng ta có thể tìm ra khoảng cách tối đa carousel của chúng ta được phép cuộn. Đó là tổng chiều rộng của tất cả các thành phần, trừ đi chiều rộng đầy đủ của thanh trượt hiển thị của chúng tôi. Phép toán cho ra một con số, giá trị này cho phép thành phần ngoài cùng bên phải phù hợp với bên phải thanh trượt của chúng tôi:

Với những số liệu tính toán này, chúng ta sẵn sàng để bắt đầu cuộn carousel của mình.

Thiết lập translateX

Popmotion đi kèm với trình kết xuất CSS để cài đặt các thuộc tính CSS đơn giản và hiệu quả. Nó cũng đi kèm với một function giá trị có thể được sử dụng để theo dõi các con số và, quan trọng (như chúng ta sẽ sớm thấy), để truy xuất vận tốc của chúng.

Ở đầu file JavaScript của bạn, hãy nhập như sau:

Sau đó, trên dòng sau khi chúng ta xét minXOffset, tạo trình kết xuất CSS cho thanh trượt của chúng tôi:

Và tạo một value để theo dõi độ lệch x của thanh trượt của chúng tôi và cập nhật thuộc tính translateX của thanh trượt khi nó thay đổi:

Bây giờ, di chuyển thanh trượt theo chiều ngang cũng đơn giản như viết:

Hãy thử đi!

Touch Scroll

Chúng tôi muốn carousel bắt đầu cuộn khi người dùng kéo thanh trượt theo chiều ngang và dừng di chuyển khi người dùng ngừng chạm vào màn hình. Việc xử lý sự kiện của chúng tôi sẽ trông như thế này:

Trong function startTouchScroll của chúng tôi, chúng tôi muốn:

  • Ngăn chặn hành động bất kỳ khác hỗ trợ slliderX.
  • Tìm điểm chạm nguyên gốc.
  • Lắng nghe sự kiện touchmove tiếp theo để xem liệu người dùng có đang thao tác drag theo chiều dọc hoặc chiều ngang hay không.

Sau document.addEventListener, hãy thêm vào:

Điều này sẽ ngăn chặn bất kỳ thao tác nào khác khi duyển thanh trượt (như thao tác cuộn theo động lực vật lý mà chúng tôi sẽ triển khai trong stopTouchScroll). Điều này sẽ cho phép người dùng ngay lập tức "bắt kịp" thanh trượt nếu nó cuộn qua một thành phần hoặc tiêu đề mà họ muốn nhấp vào.

Tiếp theo, chúng ta cần lưu lại điểm tiếp xúc nguyên thuỷ. Điều đó sẽ cho phép chúng ta biết vị trí tiếp theo của người dùng khi họ di chuyển ngón tay. Nếu đó là chuyển động thẳng đứng, chúng tôi sẽ cho phép cuộn trang như bình thường. Nếu đó là chuyển động ngang, thì chúng tôi sẽ cho cuộn thanh trượt.

Chúng tôi muốn chia sẻ hàm touchOrigin giữa các trình xử lý sự kiện. Vì vậy, sau let action, hãy thêm vào:

Quay lại trình xử lý startTouchScroll, thêm vào:

Bây giờ chúng ta có thể thêm một trình nghe sự kiện touchmove vào document để xác định hướng drag dựa trên hàm touchOrigin:

Hàm defineDragDirection của chúng ta sẽ tính vị trí tiếp theo của thao tác chạm, kiểm tra xem nó có thực sự di chuyển hay không, và nếu có, hãy đo góc độ để xác định xem nó nằm dọc hoặc nằm ngang:

Popmotion bao gồm một số phép tính hữu ích để tính toán những thứ như khoảng cách giữa hai tọa độ x/y. Chúng ta có thể import những thứ sau đây:

Sau đó đo khoảng cách giữa hai điểm là vấn đề của phép toán distance:

Bây giờ nếu thao tác chạm đã xảy ra, chúng tôi loại bỏ trình lắng nghe sự kiện này.

Đo góc giữa hai điểm bằng phép tính angle:

Chúng ta có thể sử dụng phép tính này để xác định xem góc này là một góc phương ngang hay dọc, bằng cách chuyển nó đến hàm sau đây. Thêm hàm này vào đầu file của chúng tôi:

Hàm này trả về true nếu góc được cung cấp nằm trong khoảng -90 +/- 45 độ (thẳng đứng) hoặc 90 +/- 45 độ (thẳng xuống.) Vì vậy, chúng ta có thể bổ sung return nếu function này trả về true.

Theo dõi con trỏ chuột

Bây giờ chúng tôi biết người dùng đang thao tác cuộn carousel, chúng tôi có thể bắt đầu theo dõi ngón tay của họ. Popmotion cung cấp một thao tác pointer (con trỏ) để tìm ra tọa độ x/y của một con trỏ chuột hoặc con trỏ thao tác chạm.

Đầu tiên, nhập pointer:

Để theo dõi phần input của thao tác chạm, hãy cung cấp sự kiện khởi nguồn cho pointer:

Chúng tôi muốn đo vị trí x ban đầu của con trỏ và áp dụng chuyển động bất kỳ cho thanh trượt. Để làm việc này, chúng ta có thể sử dụng một transformer được gọi là applyOffset.

Transformer là những hàm đơn giản sẽ lấy một giá trị và trả về it—yes—transformed. Ví dụ: const double = (v) => v * 2.

applyOffset là một hàm "curry". Nghĩa là khi chúng ta gọi hàm này, nó sẽ tạo ra một hàm mới, sau đó hàm này có thể được truyền đi như một giá trị. Đầu tiên chúng tôi gọi hàm này với một số mà chúng tôi muốn tính toán giá trị offset, trong trường hợp là này giá trị hiện tại của action.x, và một con số để áp dụng phần offset đó. Trong trường hợp này, đó là sliderX.

Vậy hàm applyOffset của chúng ta sẽ như sau:

Bây giờ chúng ta có thể sử dụng hàm này trong hàm callback output của con trỏ để áp dụng chuyển động con trỏ tới thanh trượt.

Dừng lại, với style

Carousel hiện hỗ trợ thao tác drag bằng cảm ứng! Bạn có thể kiểm tra bằng cách sử dụng trình mô phỏng thiết bị trong Developer Tools của Chrome.

Chất lượng có vẻ hơi kém một chút, phải không? Bạn có thể đã gặp phải sự cố cuộn như đã từng cảm thấy lúc trước: bạn rời ngón tay đi, và quá trình cuộn ngừng phản hồi. Hoặc di chuyển cuộn ngừng phản hồi và sau đó một diễn hoạt nhỏ sẽ chiếm quyền để giả việc tiếp tục cuộn.

Chúng ta sẽ không làm như thế. Chúng ta có thể sử dụng hoạt động vật lý trong Popmotion để lấy vận tốc thực của sliderX và áp dụng lực ma sát cho nó qua một khoảng thời gian.

Đầu tiên, thêm nó vào danh sách import (đang tăng dần):

Sau đó, ở cuối hàm stopTouchScroll, hãy thêm:

Ở đây, fromvelocity đang được thiết lập với giá trị hiện tại và vận tốc của sliderX. Điều này đảm bảo mô phỏng vật lý của chúng tôi có các điều kiện bắt đầu ban đầu giống như chuyển động drag (kéo) của người dùng.

friction đang được thiết lập là 0,2. Ma sát được đặt làm giá trị từ 0 đến 1, với 0 không có ma sát nào cả và 1 là ma sát tuyệt đối. Hãy thử nghiệm với giá trị này để xem thay đổi tạo ra cho "cảm giác" của carousel khi một người dùng dừng thao tác drag.

Con số nhỏ hơn sẽ làm cho cảm giác này giảm bớt, và con số lớn hơn sẽ làm cho chuyển động nặng nề hơn. Đối với một chuyển động cuộn, tôi cảm thấy 0,2 tạo sự cân bằng thích hợp giữa sự gián đoạn và chậm chạp.

Những ranh giới

Nhưng có một vấn đề! Nếu bạn đã từng thử nghiệm nhiều với phần carousel cảm ứng mới này, điều đó rõ ràng. Chúng tôi lập giới hạn cho chuyển động để bỏ qua carousel của bạn!

Có một transformer khác cho việc này, clamp. Đây cũng là một hàm curried, có nghĩa là nếu chúng ta gọi nó với một giá trị min và max, 0 1, nó sẽ trả về một hàm mới. Trong ví dụ này, function mới sẽ tạo giới hạn từ 0 đến 1 cho con số bất kỳ được truyền vào hàm này:

Đầu tiên, import clamp:

Chúng tôi muốn sử dụng hàm này trên carousel, vì vậy hãy bổ sung dòng này sau khi định nghĩa minXOffset:

Chúng tôi sẽ sửa đổi hai output chúng tôi đã đặt trên hành động của chúng tôi bằng cách sử dụng một số thành phần chức năng gọn nhẹ với transformer pipe.

Pipe

Khi gọi một hàm, chúng ta viết hàm đó như sau:

Nếu chúng ta muốn đưa output của hàm đó cho một function khác, chúng ta có thể viết như sau:

Điều này trở nên hơi khó đọc chút, và khi chúng ta bổ sung thêm nhiều hàm hơn, nó sẽ tệ hơn nữa.

Sử dụng pipe chúng ta có thể tạo một hàm ngoài foobar, chúng ta có thể tái sử dụng hàm này:

Nó cũng được viết trong theo thứ tự tự nhiên start -> finish, và dễ dàng hơn để làm theo. Chúng ta có thể sử dụng điều này để soạn applyOffsetclamp thành một hàm duy nhất. Import pipe:

Thay thế hàm callback output của pointer bằng:

Và thay thế hàm callback output của physics bằng:

Đây là loại thành phần hàm khá gọn gàng có thể tạo ra mô tả, từng bước quy trình ra khỏi nhỏ hơn, hàm tái sử dụng.

Bây giờ, khi bạn drag và throw carousel, nó sẽ không rục rịch ra bên ngoài ranh giới của nó.

Việc đột ngột dừng không thực sự thoã mãn. Nhưng là vấn đề cho bài viết sau.

Tổng kết

Đó là tất cả phần 1. Cho đến nay, chúng tôi đã xem xét carousel hiện có để xem điểm mạnh và điểm yếu của các cách tiếp cận khác nhau về thao tác cuộn. Chúng tôi đã sử dụng tính năng theo dõi đầu vào của Popmotion và vật lý để tạo hiệu ứng hoạt động cho translateX của chúng tôi bằng thao tác cuộn cảm ứng. Chúng tôi cũng đã được giới thiệu hàm thành phần và hàm curried.

Bạn có thể xem bản có giải thích "story so far" ở CodePen.

Trong các phần sắp tới, chúng ta sẽ xem xét:

  • cuộn bằng một bánh xe chuột
  • đo kích thước carousel lại khi cửa sổ thay đổi kích thước
  • phân trang, với khả năng truy xuất từ bàn phím và chuột
  • những điều thú vị, với sự giúp đỡ của vât lý spring

Mong được thấy bạn ở đó!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.