Swipe Direction Lock
Hãy thử nhớ lại lần đầu bạn dùng Tinder. Bạn muốn vuốt sang trái — và card trượt sang trái. Không có gì đặc biệt. Nhưng nếu bạn dừng lại và nghĩ, bạn sẽ thấy điều đó không đơn giản chút nào.
Ngón tay không bao giờ đi thẳng. Khi bạn vuốt sang trái, ngón tay cũng lệch lên vài pixel, xuống vài pixel. Nếu màn hình phản ứng với mọi chuyển động nhỏ đó, card sẽ vừa trượt ngang vừa giật lên giật xuống theo từng milimetre ngón tay bạn lệch. Nhưng bạn không thấy điều đó. Vì Tinder — và hầu hết app làm gesture tốt — đã âm thầm giải quyết vấn đề này.
Kéo card ngang hoặc lên — thử kéo chéo và quan sát hệ thống chọn direction
Nỗi đau
Hãy tưởng tượng một màn hình có hai loại gesture: vuốt ngang để chuyển tab, vuốt dọc để scroll nội dung. Đây là pattern cực kỳ phổ biến — Instagram, TikTok, Apple Music đều dùng. Và khi làm đúng, nó cảm giác mượt không tưởng.
Nhưng khi làm sai, nó là một trong những trải nghiệm tệ nhất trên mobile. Bạn muốn scroll xuống đọc tiếp, nhưng màn hình lại chuyển tab. Hoặc bạn muốn chuyển tab, nhưng nội dung lại scroll. Bạn không kiểm soát được gì, dù ngón tay rõ ràng đang làm đúng điều bạn muốn.
Cảm giác đó — khi interface không hiểu ý bạn — là một trong những friction lớn nhất trong mobile UX. Người dùng không nói ra. Họ chỉ thấy app “khó dùng” mà không biết tại sao.
Insight của designer
Khi team UX của Apple thiết kế scroll cho iPhone đầu tiên, họ nhận ra một điều: ngón tay người không phải là chuột. Chuột di chuyển chính xác theo một trục. Ngón tay thì không — mỗi cú vuốt đều có noise, đều có độ lệch.
Giải pháp không phải là yêu cầu người dùng vuốt chính xác hơn. Giải pháp là để hệ thống tự đọc ý định.
Insight đó dẫn đến direction lock: một khoảng thời gian ngắn sau khi ngón tay chạm màn hình và bắt đầu di chuyển, hệ thống quan sát — ngang nhiều hơn hay dọc nhiều hơn? Khi đã đủ rõ, hệ thống commit vào một trục và block trục còn lại hoàn toàn. Từ đó, mọi chuyển động của ngón tay chỉ tạo ra một loại action duy nhất, không có cross-contamination.
Điều này giải thích tại sao scroll trên iOS không bao giờ vô tình chuyển tab, và chuyển tab không bao giờ vô tình scroll. Dù ngón tay bạn lệch đến đâu trong 8px đầu tiên — hệ thống đã quyết định xong, và nó sẽ giữ quyết định đó đến cuối gesture.
Uncommitted zone — khoảnh khắc hệ thống lắng nghe
8px đầu tiên là vùng im lặng. Element không di chuyển. Hệ thống không làm gì cả — chỉ lắng nghe.
Điều thú vị là người dùng không nhận ra khoảng thời gian này tồn tại. 8px ở tốc độ ngón tay bình thường kéo dài khoảng 40–60ms — ngắn hơn ngưỡng nhận thức có ý thức của não người. Nhưng nếu tăng lên 20px hoặc hơn, người dùng bắt đầu cảm thấy lag. Interface “chậm phản ứng”.
0px → 8px : im lặng, quan sát
8px → 9px : commit direction, bắt đầu di chuyển
9px → ... : theo trục đã chọn, block trục còn lại
Con số 8px không phải quy tắc cứng — iOS dùng khoảng 10px, Android từ 8–12px tùy OEM — nhưng tất cả đều nằm trong một vùng hẹp. Nhỏ hơn thì commit sai khi ngón tay chưa kịp định hướng. Lớn hơn thì người dùng cảm thấy bị ignore.
Cái khó thực sự: khoảnh khắc commit
Direction lock không khó. Cái khó là làm cho khoảnh khắc commit trông tự nhiên.
Khi hệ thống vừa xác định được direction, nó bắt đầu cho element di chuyển. Nhưng nếu đo displacement từ điểm touchstart ban đầu, element sẽ “bắt kịp” ngón tay từ vị trí 0 về vị trí hiện tại của ngón tay — trong một frame. Cú nhảy đó có thể cảm nhận được, đặc biệt khi ngón tay di chuyển nhanh.
Giải pháp mà phần lớn platform implementation dùng: reset điểm đo về đúng vị trí ngón tay tại thời điểm commit. Element bắt đầu di chuyển từ 0, không từ vị trí trước đó. Kết quả: không có jump, không có bắt kịp. Chỉ là movement tự nhiên từ điểm người dùng đang đứng.
Đây là thứ khó nhìn thấy khi nó đúng — nhưng cực kỳ khó chịu khi nó sai.
Spring-back và cảm giác vật lý
Khi người dùng kéo card rồi thả ra mà không đủ xa để trigger action, điều gì xảy ra?
Phần lớn designer mới tay sẽ để element snap về vị trí cũ ngay lập tức — hoặc dùng ease-out. Cả hai đều trông mechanical. Không có gì trong thế giới vật lý di chuyển theo kiểu đó.
Các platform làm gesture tốt dùng spring với một chút overshoot — element bay về quá vị trí gốc một chút rồi bounce lại. Bounce đó không lớn, không thấy rõ. Nhưng nó đủ để não người nhận ra “đây là vật thể có khối lượng, không phải pixel”. Cảm giác đó tạo ra sự tin tưởng vào interface.
Đây là lý do iOS springboard, Android sheets, và mọi bottom drawer tốt đều có bounce nhẹ khi snap back — dù không ai nói ra.
Dismiss theo ý định, không theo đo lường
Một vấn đề nhỏ hơn nhưng đủ để làm hỏng trải nghiệm: điều kiện để dismiss gesture là gì?
Câu trả lời trực giác là distance — kéo đủ xa thì dismiss. Nhưng người dùng đôi khi flick nhanh và ngắn — ngón tay đi chưa đủ xa nhưng ý định rõ ràng là muốn dismiss. Nếu hệ thống chỉ đo distance, flick đó sẽ bị reject. Interface trông như bị lag, dù ngón tay đã làm đúng.
Giải pháp: dismiss khi đủ xa OR đủ nhanh. Hai điều kiện đó đại diện cho hai cách người dùng biểu đạt cùng một ý định — kéo chậm xa, hoặc flick nhanh ngắn. Hệ thống nên hiểu cả hai.
Đây là loại quyết định mà designer không thể tìm ra từ code hay documentation. Phải dùng app, phải test tay, phải nhận ra cái gì trông “tự nhiên” và cái gì trông “cứng” — rồi mới biết tại sao.
Tham khảo: Gesture Feedback Motion, Spring Parameters, Modal & Sheet Animation.