Menu
ESC

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

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

Đang tải...

Bài 47 — Motion Specs cho Dev

UI Design and Design Handoff Bài 47/60

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

Bạn đã bao giờ thiết kế một animation mượt mà trong Figma, tưởng tượng nó trơn tru và tinh tế, rồi khi lập trình viên (dev) build ra sản phẩm thật, nó lại giật cục, quá nhanh, hoặc "văng" một cách kỳ lạ? Đó không phải lỗi của dev. Đó là lỗi của khâu bàn giao (handoff): bạn đã không nói cho họ biết animation nên chạy như thế nào một cách chính xác.

Trong các bài trước, chúng ta đã học về Motion Design Tokens (Bài 21) và Microinteractions (Bài 31) — tức là tư duy thiết kế chuyển động và cách chuyển động tạo phản hồi cho người dùng. Bài 47 này là mảnh ghép còn thiếu ở phía handoff: làm thế nào để đặc tả (spec) chuyển động một cách rõ ràng, đo lường được, để dev có thể tái tạo chính xác những gì bạn hình dung.

Đây là kỹ năng phân biệt một designer "vẽ đẹp" với một designer thực sự làm việc được trong đội sản phẩm. Vì animation là thứ khó bàn giao nhất trong toàn bộ thiết kế. Màu sắc bạn có thể ghi mã hex, khoảng cách bạn ghi pixel, nhưng chuyển động là thứ diễn ra theo thời gian — và nếu bạn chỉ nói "làm cho nó mượt mượt vào nhé", bạn đang đẩy toàn bộ quyết định thẩm mỹ sang cho người không được đào tạo để ra quyết định thẩm mỹ. Kết quả gần như luôn thất vọng cho cả hai bên.

Sau bài này, bạn sẽ biết chính xác cần đặc tả những thông số nào cho mỗi animation, cách diễn đạt chúng bằng ngôn ngữ dev hiểu ngay, và cách trình bày motion spec để không ai phải đoán.

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

Một motion spec (đặc tả chuyển động) hoàn chỉnh cần trả lời được năm câu hỏi, mà tôi hay gọi là "5 trụ cột": cái gì kích hoạt, kéo dài bao lâu, đường cong tốc độ ra sao, thuộc tính nào thay đổi, và có delay/stagger không. Thiếu bất kỳ trụ nào, dev sẽ phải tự bịa — và họ sẽ bịa khác với ý bạn.

1. Trigger — điều gì kích hoạt animation

Trigger là sự kiện làm animation bắt đầu. Nghe hiển nhiên nhưng đây lại là chỗ hay bị bỏ sót nhất. Cùng một hiệu ứng "hiện dropdown" có thể được kích hoạt bởi: click chuột, hover, focus bằng bàn phím, hoặc load trang. Mỗi trigger đòi hỏi cách xử lý code khác nhau.

Các trigger phổ biến cần gọi tên rõ:

  • On click / on tap — người dùng nhấn.
  • On hover (enter/leave) — con trỏ vào và rời khỏi. Nhớ tách riêng animation lúc vào và lúc ra, chúng thường khác nhau.
  • On focus — phần tử nhận focus (quan trọng cho accessibility và điều hướng bàn phím).
  • On mount / on load — phần tử xuất hiện trên màn hình.
  • On scroll / in-view — cuộn đến vị trí.
  • On state change — ví dụ khi dữ liệu tải xong, chuyển từ loading sang loaded.
Quy tắc: mỗi trigger là một dòng riêng trong spec. "Menu trượt xuống khi click nút hamburger; trượt ngược lên khi click ra ngoài (click outside)."

2. Duration — thời lượng

Duration đo bằng mili-giây (ms). Đây là con số dev cần cứng: 200ms, 300ms, 150ms. Đừng ghi "nhanh" hay "chậm".

Nguyên tắc thời lượng đã được kiểm chứng qua Material Design và các hệ thống lớn:

  • 100–150ms: micro-feedback tức thời (đổi màu nút khi hover, ripple bắt đầu).
  • 200–300ms: phần lớn chuyển động UI thông thường (dropdown, tooltip, card mở rộng).
  • 300–400ms: chuyển động lớn, di chuyển quãng đường dài (drawer trượt toàn màn hình, chuyển trang).
  • Trên 500ms: hiếm dùng, chỉ cho chuyển cảnh có chủ đích. Quá dài sẽ khiến người dùng thấy ì ạch.
Nguyên tắc vàng: quãng đường di chuyển càng dài, duration càng lớn; phần tử càng nhỏ, animation càng nhanh. Một tooltip nhỏ xuất hiện trong 150ms là hợp lý; một bottom sheet cao nửa màn hình cần 300ms mới không bị "giật".

Và một điểm cực kỳ quan trọng cho handoff: enter thường chậm hơn exit. Khi phần tử xuất hiện, ta muốn người dùng kịp nhận biết (250ms). Khi nó biến mất, ta muốn dọn nhanh để không cản trở (150–200ms). Hãy ghi rõ hai con số này.

3. Easing — đường cong tốc độ

Easing (hay timing function) mô tả tốc độ animation thay đổi theo thời gian. Đây là linh hồn của chuyển động — cùng duration nhưng easing khác nhau cho cảm giác hoàn toàn khác.

Bốn nhóm easing dev hiểu ngay vì chúng là chuẩn CSS:

  • ease-out cubic-bezier(0, 0, 0.2, 1): bắt đầu nhanh, kết thúc chậm. Đây là easing dùng cho phần tử xuất hiện/đi vào. Cảm giác tự nhiên, "đáp xuống" nhẹ nhàng. Nếu chỉ được chọn một, hãy chọn cái này.
  • ease-in cubic-bezier(0.4, 0, 1, 1): bắt đầu chậm, kết thúc nhanh. Dùng cho phần tử biến mất/đi ra, vì nó "tăng tốc rời đi".
  • ease-in-out cubic-bezier(0.4, 0, 0.2, 1): chậm hai đầu, nhanh ở giữa. Dùng cho phần tử di chuyển từ điểm A sang B khi cả hai trạng thái đều nhìn thấy.
  • linear: tốc độ đều. Chỉ dùng cho chuyển động cơ khí như spinner loading xoay đều, hoặc progress bar. Với UI thường thì linear cho cảm giác "máy móc", vô hồn.
Cách đặc tả chuẩn nhất là ghi cả tên giá trị cubic-bezier. Dev có thể copy-paste giá trị cubic-bezier(...) thẳng vào CSS. Nếu bạn dùng easing tùy biến (ví dụ một đường cong hơi "overshoot" nảy nhẹ), giá trị bezier là bắt buộc — không có tên gọi chuẩn nào cho nó cả.

Với animation dạng lò xo (spring) — phổ biến trên iOS và trong React (Framer Motion, react-spring) — easing không đủ. Bạn cần đặc tả bằng các tham số vật lý: stiffness (độ cứng), damping (độ giảm chấn), mass (khối lượng). Hãy ghi rõ nếu đội dev dùng thư viện spring, vì bezier không chuyển đổi 1-1 sang spring được.

4. Property — thuộc tính nào thay đổi

Đây là chi tiết mà designer hay quên nhưng dev sống chết vì nó. Hãy chỉ rõ thuộc tính nào biến đổi và từ giá trị nào đến giá trị nào:

  • opacity: 0 → 1 (fade).
  • transform: translateY: 8px → 0 (trượt lên vào vị trí).
  • transform: scale: 0.95 → 1 (phóng nhẹ).
  • height / width: thường tránh vì gây reflow, ưu tiên scale hoặc max-height.
Có một lý do kỹ thuật để bạn ưu tiên transform và opacity: đây là hai thuộc tính được GPU tăng tốc, chạy mượt 60fps ngay cả trên máy yếu. Nếu bạn spec một animation đổi left, top, width, margin, dev sẽ phải đánh đổi hiệu năng. Là designer hiểu handoff, bạn nên diễn đạt ý đồ theo hướng "trượt vào" (translate) thay vì "đổi vị trí left" — vừa đúng ý đồ, vừa thân thiện với hiệu năng.

5. Delay & Stagger — độ trễ và hiệu ứng nối tiếp

Delay là khoảng chờ trước khi animation bắt đầu (ví dụ tooltip chờ 400ms mới hiện để tránh nhấp nháy khi lướt chuột qua). Stagger là độ lệch giữa các phần tử trong một danh sách: item 1 xuất hiện, 40ms sau item 2, 40ms sau nữa item 3 — tạo hiệu ứng "rơi cascade" rất sang. Nếu thiết kế của bạn có stagger, phải ghi rõ giá trị lệch (delay increment), nếu không dev sẽ cho tất cả xuất hiện cùng lúc.

Reduced motion — trụ cột thứ sáu không được quên

Một motion spec chuyên nghiệp phải có phần trả lời: khi người dùng bật "Reduce Motion" (giảm chuyển động) trong cài đặt hệ thống thì sao? Nhiều người bị rối loạn tiền đình sẽ chóng mặt vì animation trượt/scale mạnh. Chuẩn là: giữ lại fade opacity đơn giản, bỏ hết translate/scale, hoặc rút duration về gần 0. Ghi câu này vào spec để dev không quên implement prefers-reduced-motion.

Tình huống thực tế

Ví dụ 1: Bottom sheet của app đặt đồ ăn (bối cảnh Việt Nam)

Một đội thiết kế tại một startup giao đồ ăn ở TP.HCM — giả định tên là FoodNow — bàn giao màn hình chọn món. Khi người dùng bấm vào một nhà hàng, một bottom sheet (tấm trượt từ dưới lên) hiển thị menu. Spec ban đầu của designer chỉ ghi: "bottom sheet trượt lên mượt".

Kết quả build ra: sheet trượt lên trong 500ms với easing ease-in-out, cảm giác nặng nề như kéo một tấm gỗ. Người dùng bấm liên tục vì tưởng app lag. Chỉ số bounce (thoát) ở màn hình này cao bất thường.

Đội thiết kế viết lại spec đúng chuẩn: Trigger: on tap vào card nhà hàng. Property: translateY từ 100% → 0, kèm opacity của lớp overlay nền 0 → 0.4. Duration: 280ms enter, 200ms exit. Easing: ease-out cubic-bezier(0, 0, 0.2, 1) cho enter, ease-in cho exit. Reduced motion: chỉ fade overlay, sheet hiện tức thời. Sau khi build lại đúng spec, cảm giác app "nhẹ" hẳn, và thời gian người dùng thao tác đến bước đặt hàng giảm rõ. Bài học: "mượt" không phải một con số. Con số mới là thứ dev build được.

Ví dụ 2: Danh sách thông báo với stagger (bối cảnh fintech Đông Nam Á)

Một ví (e-wallet) — giả định GrabPay-style — muốn danh sách giao dịch xuất hiện có hiệu ứng cao cấp khi mở tab lịch sử. Designer hình dung từng dòng "rơi" xuống nối tiếp nhau. Nhưng spec đầu tiên chỉ ghi "list fade in".

Dev làm đúng như ghi: cả 20 dòng fade cùng lúc — đúng chữ nhưng sai hồn. Trông rẻ tiền, chẳng có nhịp điệu.

Designer bổ sung: mỗi item: opacity 0 → 1 kèm translateY 12px → 0, duration 220ms, ease-out. Stagger 35ms giữa các item, tối đa áp dụng cho 8 item đầu (các item sau xuất hiện cùng lúc để không kéo dài quá 1 giây). Chi tiết "tối đa 8 item" cực kỳ quan trọng — nếu không giới hạn, một danh sách 50 giao dịch sẽ mất gần 2 giây mới hiện hết, gây khó chịu. Bài học: stagger phải có giá trị lệch cụ thể VÀ có giới hạn số phần tử, nếu không hiệu ứng đẹp sẽ biến thành hiệu ứng chậm.

Ví dụ 3: Nút submit với micro-feedback (bối cảnh SaaS)

Một công ty SaaS bàn giao nút "Lưu thay đổi". Designer muốn khi bấm, nút hơi thu nhỏ lại (press effect) rồi bật về, tạo cảm giác chạm vật lý. Họ chỉ nói miệng với dev: "cho nó nhún nhún khi bấm nhé".

Dev diễn giải "nhún" thành scale 0.8 trong 400ms — quá mạnh và quá chậm, trông như nút bị hỏng. Sau vài vòng qua lại tốn thời gian, họ chốt spec: on press (mousedown): scale 1 → 0.96, duration 100ms, ease-out. On release: scale trở về 1, duration 150ms, ease-out. Chênh lệch giữa "0.96 trong 100ms" và "0.8 trong 400ms" chính là ranh giới giữa tinh tế và lỗi. Bài học: micro-interaction càng nhỏ càng cần con số chính xác, vì sai số nhỏ trong spec bị khuếch đại thành cảm giác rất khác. Đừng bao giờ spec animation bằng lời nói suông.

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

Đây là quy trình tạo motion spec cho một animation bất kỳ trong Figma và bàn giao cho dev:

Bước 1 — Liệt kê tất cả animation trong màn hình. Đi qua từng thành phần: cái gì di chuyển, xuất hiện, biến mất, đổi trạng thái. Đặt tên rõ ràng cho mỗi cái, ví dụ "Dropdown menu — open/close".

Bước 2 — Với mỗi animation, điền vào bảng 5 trụ cột. Tạo một bảng đơn giản: Tên | Trigger | Property (from → to) | Duration | Easing | Delay/Stagger | Reduced motion. Bảng này chính là "hợp đồng" giữa bạn và dev.

Bước 3 — Prototype trong Figma để tự nghiệm. Dùng Smart Animate với các easing preset của Figma (Ease Out, Ease In, v.v.) và nhập duration bằng ms. Figma cho phép bạn xem trước — nhưng lưu ý: Figma prototype không phản ánh 100% hành vi web/native. Nó dùng để bạn cảm nhận và chốt con số, không phải nguồn chân lý cuối cùng.

Bước 4 — Tham chiếu về design tokens. Đừng đặt số lẻ. Nếu hệ thống của bạn (từ Bài 21) đã có token như duration-fast: 150ms, duration-base: 250ms, easing-standard, hãy gọi tên token thay vì viết số thô. Điều này giúp dev dùng đúng biến trong code và cả hệ thống nhất quán.

Bước 5 — Ghi spec vào nơi dev nhìn thấy. Ba lựa chọn: (a) chú thích trực tiếp trên frame Figma bằng annotation trong Dev Mode (Bài 42–43); (b) một trang "Motion" riêng trong file design system doc; (c) mô tả trong Storybook nếu đội có (Bài 45). Đừng để spec chỉ nằm trong đầu bạn hoặc trong tin nhắn chat trôi mất.

Bước 6 — Review cùng dev khi build xong. Ngồi cạnh dev, xem animation chạy thật, so với spec. Điều chỉnh con số nếu cần. Motion luôn cần một vòng tinh chỉnh trên thiết bị thật.

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

Lỗi 1: Spec bằng tính từ. "Mượt", "nhanh", "nảy nảy", "sang" — đây không phải spec, đây là mong ước. Luôn dịch sang con số: ms, cubic-bezier, giá trị from → to.

Lỗi 2: Quên animation exit. Designer thường chỉ spec lúc phần tử xuất hiện mà quên lúc nó biến mất. Dev sẽ tự bịa hoặc cho biến mất tức thời, làm hỏng cảm giác. Luôn spec cả enter và exit, và nhớ exit thường nhanh hơn.

Lỗi 3: Dùng duration quá dài. Người mới rất hay để 400–600ms vì trong Figma nhìn "đẹp". Trên sản phẩm thật, người dùng thao tác hàng chục lần mỗi phút — animation dài thành rào cản. Khi nghi ngờ, hãy rút ngắn.

Lỗi 4: Bỏ qua reduced motion. Đây vừa là vấn đề accessibility (Bài 39) vừa là dấu hiệu chuyên nghiệp. Luôn có một dòng cho prefers-reduced-motion.

Lỗi 5: Spec animate các thuộc tính đắt về hiệu năng. Yêu cầu animate height, top, box-shadow liên tục có thể gây giật trên máy yếu. Mẹo: diễn đạt ý đồ qua transform và opacity. Muốn "cao lên"? Nghĩ tới scaleY hoặc reveal bằng mask thay vì animate height.

Mẹo vàng: Xây một motion token sheet dùng chung cho cả team — chỉ 3–4 duration và 3–4 easing được phép dùng. Ràng buộc này khiến toàn bộ sản phẩm có nhịp chuyển động nhất quán, và việc handoff trở thành "dùng token duration-base + easing-out" thay vì tranh cãi từng con số. Đây là cách các design system lớn (Material, Carbon của IBM, Polaris của Shopify) vận hành.

Mẹo giao tiếp: Khi bàn giao, hãy quay một video màn hình ngắn (screen recording) từ prototype Figma đính kèm bên cạnh bảng số. Video cho dev "cảm giác đích đến", bảng số cho họ "công thức". Hai thứ bổ sung nhau chứ không thay thế nhau.

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

  • Viết spec đầy đủ cho một dropdown menu. Chọn một dropdown bất kỳ (ví dụ menu tài khoản góc phải). Điền đủ bảng 5 trụ cột cho cả trạng thái open và close, kèm dòng reduced motion. Gợi ý: enter nên ease-out ~200ms với translateY + opacity; exit ease-in ~150ms.
  • Tái spec một animation quá đà. Tìm trong một app bạn dùng hằng ngày (Shopee, MoMo, Grab...) một chuyển động mà bạn thấy quá chậm hoặc quá mạnh. Viết lại spec bạn cho là đúng, giải thích vì sao đổi con số đó.
  • Thiết kế và spec một hiệu ứng stagger. Lấy một danh sách 10 item, prototype hiệu ứng xuất hiện có stagger trong Figma. Ghi spec với giá trị lệch cụ thể và giới hạn số item được áp dụng stagger. Tự đo tổng thời gian để đảm bảo không vượt 1 giây.
  • Xây motion token sheet mini. Định nghĩa 3 duration (fast/base/slow) và 3 easing (out/in/in-out) cho một sản phẩm giả định, kèm hướng dẫn ngắn "khi nào dùng cái nào". Đây sẽ là nền tảng handoff cho mọi animation về sau.

Tóm tắt

Motion spec là cách bạn biến một chuyển động trong đầu thành thứ dev tái tạo được chính xác. Mỗi animation cần được đặc tả qua các trụ cột: Trigger (điều gì kích hoạt), Duration (bao nhiêu ms), Easing (cubic-bezier cụ thể, ease-out cho enter, ease-in cho exit), Property (thuộc tính nào từ giá trị nào đến giá trị nào — ưu tiên transform và opacity), Delay/Stagger (độ trễ và độ lệch nối tiếp), và Reduced motion (hành vi khi người dùng bật giảm chuyển động).

Nguyên tắc cốt lõi cần nhớ: đừng bao giờ spec bằng tính từ, hãy spec bằng con số; enter chậm hơn exit; quãng đường dài thì duration lớn; và luôn dùng token dùng chung để cả sản phẩm có một nhịp chuyển động thống nhất. Khi bạn bàn giao motion với sự rõ ràng này, dev sẽ không phải đoán, và animation trên sản phẩm thật sẽ đúng như bạn hình dung — đó chính là dấu hiệu của một UI designer làm việc chuyên nghiệp trong đội sản phẩm.