Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 21 — Motion Design Tokens

UI Design and Design Handoff Bài 21/60

Mở đầu — vì sao bài này quan trọng

Bạn có để ý rằng khi bấm nút "Đặt hàng" trên Shopee, nút không nhảy ngay lập tức mà "nhún" xuống một chút rồi bật lên? Hay khi kéo menu ngăn kéo (drawer) trong app MoMo, nó trượt vào với một nhịp mượt mà, chậm dần ở cuối chứ không dừng khựng lại? Đó chính là motion — chuyển động trong giao diện. Và điều mà đa số Junior Designer không nhận ra: những chuyển động đó không phải do developer tự nghĩ ra một cách ngẫu nhiên. Chúng được quy định bởi một hệ thống — Motion Design Tokens.

Ở những bài trước, bạn đã học cách hệ thống hóa màu sắc (color tokens), khoảng cách (spacing tokens), bo góc (border radius tokens). Motion cũng vậy. Nếu mỗi màn hình, mỗi component lại có một tốc độ chuyển động khác nhau, một kiểu tăng tốc/giảm tốc khác nhau, thì sản phẩm của bạn sẽ cho cảm giác lộn xộn, thiếu chuyên nghiệp — dù từng màn hình riêng lẻ trông vẫn đẹp. Người dùng không nói ra được vì sao họ thấy app "rẻ tiền", nhưng cảm giác đó đến từ motion không nhất quán.

Bài này sẽ dạy bạn cách biến chuyển động — thứ tưởng chừng mơ hồ và cảm tính — thành một hệ thống token rõ ràng, có thể tái sử dụng, và quan trọng nhất là bàn giao cho developer một cách chính xác. Đây là nền tảng để sau này bạn viết được motion spec chuẩn (bài 47) và làm việc hiệu quả với kỹ sư (bài 50).

Khái niệm cốt lõi

Motion trong UI, dù trông phức tạp đến đâu, đều được cấu thành từ hai trục chính: Duration (thời lượng) và Easing (đường cong tăng/giảm tốc). Hiểu và hệ thống hóa hai trục này là bạn đã nắm 80% của motion design.

Trục 1 — Duration (Thời lượng)

Duration trả lời câu hỏi: "Chuyển động này kéo dài bao lâu?" Đơn vị đo là millisecond (ms) — một phần nghìn giây. Nghe thì nhỏ, nhưng chênh lệch 100ms là đủ để người dùng cảm nhận được sự khác biệt giữa "nhanh gọn" và "chậm chạp".

Nguyên tắc vàng: quãng đường di chuyển càng ngắn, khoảng cách trên màn hình càng nhỏ thì duration càng ngắn. Một checkbox đổi trạng thái chỉ cần 100ms. Một drawer trượt từ mép màn hình vào giữa cần 300ms. Một trang chuyển cảnh toàn màn hình có thể cần 400–500ms.

Thay vì để developer tự đoán, ta định nghĩa một duration scale — một thang thời lượng cố định:

motion.duration.instant = 100ms   (state change tức thì: hover, checkbox, toggle)
motion.duration.fast    = 200ms   (element nhỏ: tooltip, dropdown, badge)
motion.duration.normal  = 300ms   (element trung bình: drawer, modal, card expand)
motion.duration.slow    = 400ms   (element lớn: page transition, bottom sheet)
motion.duration.slower  = 500ms   (chuyển cảnh phức tạp, hero animation)

Lưu ý: đừng để duration vượt quá 500ms cho các tương tác thông thường. Nghiên cứu của Google Material Design chỉ ra rằng chuyển động trên 500ms bắt đầu gây cảm giác "app bị lag" thay vì "app mượt". Chỉ những animation mang tính trình diễn (onboarding, splash screen) mới nên vượt ngưỡng này.

Trục 2 — Easing (Đường cong tốc độ)

Easing trả lời câu hỏi: "Chuyển động này tăng tốc và giảm tốc như thế nào?" Trong thế giới thực, không vật thể nào chuyển động với tốc độ đều tuyệt đối rồi dừng phựt. Một chiếc xe khởi động từ từ, tăng tốc, rồi phanh chậm dần. Easing mô phỏng chính cảm giác vật lý tự nhiên đó vào giao diện.

Có bốn loại easing cơ bản bạn cần nắm:

  • Linear (tuyến tính): tốc độ đều từ đầu đến cuối. Cảm giác máy móc, cứng nhắc. Chỉ dùng cho chuyển động lặp vô hạn như spinner loading.
  • Ease-in (tăng tốc): bắt đầu chậm, kết thúc nhanh. Dùng khi element rời khỏi màn hình (thoát ra, biến mất).
  • Ease-out (giảm tốc): bắt đầu nhanh, kết thúc chậm. Đây là loại được dùng nhiều nhất, cho cảm giác "đáp xuống nhẹ nhàng". Dùng khi element xuất hiện vào màn hình.
  • Ease-in-out: chậm ở hai đầu, nhanh ở giữa. Dùng cho element di chuyển trong màn hình (đổi vị trí, sắp xếp lại).
Về mặt kỹ thuật, easing được biểu diễn bằng cubic-bezier — một hàm toán học với bốn tham số. Ví dụ cubic-bezier(0, 0, 0.2, 1) là ease-out chuẩn của Material Design. Bạn không cần nhớ con số, nhưng cần biết chúng tồn tại để đưa vào token:

motion.easing.standard   = cubic-bezier(0.2, 0, 0, 1)     (mặc định — ease-out)
motion.easing.decelerate = cubic-bezier(0, 0, 0, 1)       (element xuất hiện)
motion.easing.accelerate = cubic-bezier(0.3, 0, 1, 1)     (element biến mất)
motion.easing.emphasized = cubic-bezier(0.2, 0, 0, 1)     (chuyển động quan trọng, có "sức nặng")

Kết hợp thành Motion Token hoàn chỉnh

Một motion token thực thụ thường gom cả duration lẫn easing thành một cặp có nghĩa, gọi là semantic motion token. Thay vì bắt developer nhớ "modal dùng 300ms + ease-out", ta đặt tên theo mục đích:

motion.enter    = { duration: 300ms, easing: decelerate }
motion.exit     = { duration: 200ms, easing: accelerate }
motion.emphasis = { duration: 400ms, easing: emphasized }

Đây chính là điểm phân biệt giữa một designer nghiệp dư và một designer có tư duy hệ thống: bạn không nghĩ theo "con số", bạn nghĩ theo "ý định" (intent).

Tình huống thực tế

Ví dụ 1 — Tiki và bài toán 40 màn hình không đồng nhất

Hãy tưởng tượng một team thiết kế tại một sàn thương mại điện tử lớn như Tiki, với khoảng 40 màn hình chính. Trước khi có hệ thống motion token, mỗi designer trong team tự set animation cho phần của mình: bạn A cho modal chạy 250ms ease-in-out, bạn B cho drawer chạy 350ms linear, bạn C cho toast notification chạy 500ms.

Khi build ra app thật, tester phản hồi rằng "app cảm giác giật cục, không mượt". Vấn đề không nằm ở bất kỳ animation riêng lẻ nào — chúng đều "ổn" khi xem riêng. Vấn đề là sự thiếu nhất quán: người dùng lướt từ màn hình này sang màn hình khác và não bộ họ liên tục phải "hiệu chỉnh lại" kỳ vọng về tốc độ.

Giải pháp của team là ngồi lại, định nghĩa đúng 5 mức duration và 4 mức easing như trên, đưa vào một file token dùng chung. Kết quả: toàn bộ 40 màn hình chỉ còn tham chiếu đến motion.duration.normalmotion.easing.standard. Không ai được phép gõ số ms trực tiếp nữa. Điểm hài lòng về "độ mượt" trong khảo sát nội bộ tăng rõ rệt, và quan trọng hơn: khi cần chỉnh toàn bộ app chạy nhanh hơn 10%, họ chỉ sửa một dòng token thay vì lục 40 màn hình.

Bài học: Motion token không làm animation đẹp hơn — nó làm cả sản phẩm nhất quán hơn, và nhất quán chính là thứ tạo cảm giác cao cấp.

Ví dụ 2 — Startup fintech ở Singapore và chi phí ẩn của "gõ số trực tiếp"

Một startup fintech tại Singapore có app chuyển tiền. Developer của họ khi nhận design đã hardcode duration khắp nơi trong code: chỗ này 300, chỗ kia 280, chỗ nọ 320. Con số gần giống nhau nhưng không bằng nhau — vì designer đưa file Figma khác nhau ở từng sprint.

Sáu tháng sau, họ muốn hỗ trợ chế độ "Giảm chuyển động" (Reduce Motion) cho người dùng bị chóng mặt tiền đình — một yêu cầu về accessibility. Đáng lẽ chỉ cần một công tắc: nếu người dùng bật Reduce Motion, mọi duration về 0ms. Nhưng vì không có token tập trung, developer phải đi tìm và sửa hơn 200 chỗ hardcode rải rác trong codebase. Việc lẽ ra mất nửa ngày kéo dài thành gần hai tuần, và vẫn sót vài chỗ gây bug.

Bài học: Motion token không chỉ phục vụ tính thẩm mỹ. Nó là điểm điều khiển tập trung (single source of truth) cho các tính năng như dark mode motion, reduce motion, hay tùy chỉnh tốc độ theo hiệu năng thiết bị. Thiếu nó, "nợ kỹ thuật" tích tụ âm thầm.

Ví dụ 3 — VNG và việc chuẩn hóa cảm giác thương hiệu qua motion

Một studio game/app lớn như VNG khi xây design system nội bộ nhận ra rằng motion cũng mang tính cách thương hiệu. Các app hướng đến giới trẻ, năng động thì họ dùng easing "bouncy" (có độ nảy nhẹ, như lò xo) với cubic-bezier có phần vượt quá 1. Ngược lại, app tài chính nghiêm túc thì dùng easing mượt, phẳng, không nảy.

Họ định nghĩa một token đặc trưng: motion.easing.brand = cubic-bezier(0.34, 1.56, 0.64, 1) — một đường cong có độ nảy đặc trưng. Mọi micro-interaction quan trọng của thương hiệu (nút like, thả tim, mở quà) đều dùng token này. Nhờ vậy, ngay cả khi hai app khác nhau về màu sắc, người dùng vẫn "cảm" được đó là sản phẩm cùng một nhà — qua nhịp điệu chuyển động.

Bài học: Ở cấp độ nâng cao, easing token là một công cụ xây dựng bản sắc thương hiệu, không chỉ là kỹ thuật.

Hướng dẫn từng bước

Dưới đây là quy trình xây dựng hệ thống motion token cho một sản phẩm từ con số không:

Bước 1 — Kiểm kê chuyển động hiện có (Audit). Đi qua toàn bộ sản phẩm và liệt kê mọi nơi có chuyển động: hover nút, mở modal, trượt drawer, hiện toast, chuyển trang. Ghi lại duration và easing hiện tại của từng chỗ (nếu đã có). Bạn sẽ ngạc nhiên vì số lượng biến thể lộn xộn.

Bước 2 — Nhóm theo quy mô và mục đích. Phân loại chuyển động thành các nhóm: state change tức thì (checkbox, toggle), element nhỏ (tooltip, dropdown), element trung bình (modal, drawer), element lớn (page transition). Mỗi nhóm sẽ ứng với một mức duration.

Bước 3 — Định nghĩa duration scale. Chốt 5 mức như đã nêu: instant (100ms), fast (200ms), normal (300ms), slow (400ms), slower (500ms). Đừng tạo quá nhiều mức — 5 đến 6 là đủ. Nhiều hơn sẽ khiến người dùng token bối rối không biết chọn cái nào.

Bước 4 — Định nghĩa easing set. Chốt 3–4 easing: standard (mặc định), decelerate (xuất hiện), accelerate (biến mất), và tùy chọn emphasized/brand nếu cần dấu ấn riêng.

Bước 5 — Tạo semantic token. Gom duration + easing thành các cặp có nghĩa: motion.enter, motion.exit, motion.emphasis. Đây là token mà designer và developer thực sự sử dụng hằng ngày.

Bước 6 — Đưa vào Figma Variables. Từ bài 15 bạn đã biết Figma hỗ trợ Variables. Tạo một collection tên "Motion", thêm các variable kiểu number cho duration (100, 200, 300...) và variable kiểu string cho easing (lưu chuỗi cubic-bezier). Dù Figma chưa hỗ trợ áp motion token trực tiếp vào prototype một cách hoàn hảo, việc lưu chúng dưới dạng variable giúp bàn giao rõ ràng và đồng bộ được qua Tokens Studio (bài 35).

Bước 7 — Ghi tài liệu và bàn giao. Tạo một trang tài liệu ghi rõ: token nào dùng cho tình huống nào, kèm ví dụ trực quan. Đây là phần developer đọc để implement chính xác.

Lỗi thường gặp & mẹo

Lỗi 1 — Tạo quá nhiều mức duration. Junior thường muốn "linh hoạt" nên tạo tới 10–12 mức duration (100, 120, 150, 180, 200...). Kết quả là không ai biết khi nào dùng 150 và khi nào dùng 180. Mẹo: giữ tối đa 6 mức. Ràng buộc chính là sức mạnh của token.

Lỗi 2 — Dùng cùng một duration cho mọi thứ. Ngược lại với lỗi trên: gán tất cả animation là 300ms cho "gọn". Điều này khiến checkbox (đáng lẽ 100ms) trở nên chậm chạp, và page transition (đáng lẽ 400ms) trở nên vội vã. Mẹo: nhớ nguyên tắc "quãng đường ngắn — thời gian ngắn".

Lỗi 3 — Lạm dụng linear easing. Nhiều bạn để mặc định linear vì đó là giá trị mặc định của công cụ. Linear cho cảm giác robot. Mẹo: mặc định của bạn nên là ease-out (decelerate), không phải linear. Linear chỉ dành cho loading spinner.

Lỗi 4 — Quên accessibility. Không tính đến người dùng bị nhạy cảm với chuyển động. Mẹo: luôn thiết kế token sao cho có thể "tắt" toàn bộ về 0ms khi hệ điều hành báo prefers-reduced-motion. Ghi rõ điều này trong tài liệu bàn giao cho dev.

Lỗi 5 — Bàn giao mơ hồ. Chỉ nói "cho nó mượt mượt tí" hay "chậm lại xíu". Developer không đọc được suy nghĩ của bạn. Mẹo: luôn bàn giao bằng con số và tên token cụ thể: "modal enter dùng motion.enter = 300ms decelerate".

Mẹo nâng cao: Với các chuyển động phức tạp có nhiều element (như một danh sách item lần lượt xuất hiện), hãy thêm token stagger — độ trễ giữa các item, thường 20–50ms. Ví dụ motion.stagger = 40ms. Đây là chi tiết nhỏ nhưng tạo cảm giác cực kỳ tinh tế, chuyên nghiệp.

Bài tập thực hành

  • Audit một app quen thuộc. Mở app MoMo hoặc Shopee trên điện thoại. Quan sát và ghi lại (ước lượng) ít nhất 5 chuyển động khác nhau: mở modal, trượt tab, hiện toast, bấm nút, chuyển trang. Đoán xem mỗi cái kéo dài khoảng bao nhiêu ms và dùng easing loại nào. Không cần chính xác tuyệt đối — mục tiêu là rèn con mắt "nhìn ra" motion.
  • Xây duration scale của riêng bạn. Tạo một bảng gồm 5 mức duration với tên semantic (instant, fast, normal, slow, slower), kèm ghi chú mỗi mức dùng cho tình huống nào. Viết ra dưới dạng token như trong bài.
  • Thiết kế cặp enter/exit. Cho một modal giả định, hãy định nghĩa hai semantic token motion.entermotion.exit. Giải thích vì sao exit thường ngắn hơn enter (gợi ý: người dùng đã "quyết định đóng" nên không cần thời gian để tiếp nhận).
  • Đưa vào Figma Variables. Mở Figma, tạo một collection tên "Motion", thêm 5 variable duration và 3 variable easing. Chụp màn hình lại như một artifact cho portfolio (bài 55 sẽ dùng đến).
  • Thử nghiệm reduce motion. Viết một đoạn ghi chú bàn giao ngắn (3–4 dòng) cho developer, mô tả cách toàn bộ motion token nên hành xử khi người dùng bật "Giảm chuyển động".

Tóm tắt

Motion trong UI không phải là thứ ngẫu hứng — nó là một hệ thống có thể chuẩn hóa được, giống như màu sắc hay khoảng cách. Toàn bộ chuyển động đều xoay quanh hai trục: Duration (thời lượng, đo bằng ms) và Easing (đường cong tăng/giảm tốc). Bằng cách định nghĩa một duration scale gọn gàng (khoảng 5 mức từ 100ms đến 500ms) và một easing set rõ ràng (standard, decelerate, accelerate, emphasized), rồi gom chúng thành các semantic token như motion.enter, motion.exit, bạn biến chuyển động từ thứ cảm tính thành thứ có thể tái sử dụng và bàn giao chính xác.

Ba bài học lớn từ các tình huống thực tế: motion token tạo sự nhất quán (Tiki), là điểm điều khiển tập trung cho accessibility và bảo trì (fintech Singapore), và là công cụ xây dựng bản sắc thương hiệu (VNG). Hãy nhớ nguyên tắc cốt lõi: quãng đường càng ngắn thì duration càng ngắn, mặc định easing là ease-out chứ không phải linear, và luôn bàn giao bằng con số cùng tên token cụ thể — đừng bao giờ nói "mượt mượt tí". Nắm vững motion token, bạn đã đặt nền móng vững chắc cho việc viết motion spec cho dev ở những bài sau.