Cách Grammarly giảm độ trễ nhập liệu ~91% bằng 3 kỹ thuật đơn giản. Mỗi phần bên dưới giải thích một kỹ thuật kèm demo tương tác trực tiếp.
Đọc bài blog gốc →Extension trình duyệt của Grammarly thêm underline, nút nổi và xử lý gợi ý lên mọi text editor. Với mỗi lần gõ phím, extension thực hiện tất cả điều này đồng bộ:
Tổng cộng: ~517ms bị chặn trước khi ký tự xuất hiện. Với 1000 underline trong tài liệu dài, người dùng gặp độ trễ nửa giây với mỗi lần gõ phím.
Vấn đề: Với mỗi lần gõ, Grammarly gọi caretRangeFromPoint() — một DOM API tốn kém — để kiểm tra xem nút nổi có chồng lên con trỏ văn bản không. Chi phí ~2ms mỗi lần gõ.
Giải pháp: Không kiểm tra overlap khi đang gõ. Đơn giản ẩn nút khi người dùng bắt đầu gõ, và chỉ hiện lại (kèm kiểm tra overlap) sau khi người dùng dừng gõ 500ms.
Thử nghiệm: Gõ vào ô bên dưới. Chú ý nút G màu xanh — nó ẩn ngay khi bạn gõ và hiện lại sau 500ms khi bạn dừng.
Kết quả: Tiết kiệm ~2ms mỗi lần gõ. Quan trọng hơn, 9% người dùng ít hủy kích hoạt extension Grammarly hơn — họ thích trải nghiệm ít phân tâm hơn của nút ẩn khi gõ.
Vấn đề: Với mỗi lần gõ, Grammarly tính lại vị trí của mọi underline trong tài liệu. Với 1000 underline, mỗi cái cần một DOM API call (~0.5ms), tổng cộng 500ms công việc chặn mỗi lần gõ.
Giải pháp: Đặt time budget mỗi frame (ví dụ 4ms). Xử lý càng nhiều underline càng tốt trong budget đó. Khi hết thời gian, dùng requestAnimationFrame() để lên lịch phần còn lại cho frame tiếp theo. Dùng Set để tránh xử lý trùng.
Thử nghiệm: Click các nút để thấy sự khác biệt. Mỗi block màu = 1 underline. Quan sát cách phiên bản chunked xử lý thành từng lô nhỏ trên nhiều frame thay vì tất cả cùng lúc.
Kết quả: Frame đầu chỉ chặn ~4ms thay vì ~500ms. Người dùng thấy ký tự xuất hiện ngay lập tức. 1000 underline còn lại trải đều qua ~125 frame (~2 giây) — UI vẫn hoàn toàn responsive. Cải thiện latency nhập liệu lên đến 50%.
Vấn đề: Cập nhật gợi ý là yếu tố gây độ trễ lớn nhất. Mỗi lần gõ kích hoạt xử lý lại toàn bộ gợi ý (~15ms), cũng gây ra cập nhật highlight và DOM.
Insight chính: Người dùng không tương tác với gợi ý khi đang gõ. Họ gõ một đoạn, rồi dừng và xem gợi ý. Vì vậy có thể an toàn trì hoãn toàn bộ xử lý gợi ý cho đến khi dừng gõ.
Giải pháp: Buffer các thay đổi văn bản khi gõ. Chỉ xử lý gợi ý sau khi dừng 300ms. Điều này biến 15ms công việc chặn mỗi lần gõ thành 0ms mỗi lần gõ + một lô 15ms ở cuối.
Thử nghiệm: Gõ vào ô bên dưới. Quan sát log — thay đổi được buffer khi gõ, rồi xử lý cùng lúc khi bạn dừng.
Kết quả: Loại bỏ hoàn toàn yếu tố gây độ trễ lớn nhất khỏi keystroke handler. Kết hợp với kỹ thuật 1 và 2, tổng độ trễ nhập liệu giảm ~91%.
Đây là keystroke handler đã tối ưu với cả ba kỹ thuật được áp dụng:
Sau khi dừng gõ (300-500ms sau):
Thử kết quả cuối: Gõ nhanh trong editor. Đồng hồ đo latency cho thấy mỗi lần gõ chặn main thread bao lâu.
requestAnimationFrame để trải công việc qua nhiều frame. Trình duyệt giữ responsive vì bạn nhường giữa các chunk.performance.now() tại keydown và trong requestAnimationFrame tiếp theo để đo latency thực từ nhập liệu đến paint.