Hiệu ứng gõ chữ tự nhiên (Typewriter Effect) không dùng thư viện

Typewriter effect nhìn thì đơn giản, nhưng làm không khéo là lộ ngay: chữ nhảy, gõ không đều, thậm chí hiện nguyên câu rồi mới bắt đầu gõ lại. Lý do thường không nằm ở CSS, mà ở cách điều khiển nhịp render bằng JavaScript. Bài này viết lại hoàn toàn, tập trung vào một bản script mượt, ổn định và đủ tốt để dùng trong sản phẩm thật.

Hiệu ứng gõ chữ tự nhiên (Typewriter Effect) không dùng thư viện

Không canvas, không thư viện, không mẹo vặt rối rắm.

Cách tiếp cận đúng cho hiệu ứng gõ chữ

Trình duyệt vẽ lại giao diện theo từng frame. Nếu logic gõ chữ chạy lệch nhịp đó, animation sẽ giật ngay. Vì vậy, thay vì dùng nhiều setTimeout lồng nhau, script dưới đây bám sát vòng lặp render của trình duyệt để kiểm soát tốc độ gõ, xóa và khoảng dừng một cách rõ ràng.

HTML gọn nhẹ

<span id="typewriter"></span>

Một phần tử duy nhất để hiển thị nội dung, tránh dư thừa DOM.

CSS cho con trỏ gõ chữ

#typewriter::after {
  content: '|';
  margin-left: 4px;
  animation: cursor-blink 1s steps(2) infinite;
}

@keyframes cursor-blink {
  0% { opacity: 1; }
  50% { opacity: 0; }
}

Con trỏ chỉ nên nhấp nháy nhẹ, không cần hiệu ứng phức tạp.

JavaScript: Script Typewriter phiên bản mượt

const messages = [
  'Thiết kế web hiện đại',
  'Hiệu ứng mượt, không thư viện',
  'Tập trung trải nghiệm người dùng'
];

const el = document.getElementById('typewriter');

let msgIndex = 0;
let charIndex = 0;
let deleting = false;
let lastFrame = 0;
let waitUntil = 0;

const TYPE_SPEED = 90;
const DELETE_SPEED = 50;
const PAUSE_AFTER_TYPE = 1200;
const PAUSE_AFTER_DELETE = 400;

function loop(time) {
  if (time < waitUntil) {
    requestAnimationFrame(loop);
    return;
  }

  const speed = deleting ? DELETE_SPEED : TYPE_SPEED;

  if (time - lastFrame < speed) {
    requestAnimationFrame(loop);
    return;
  }

  lastFrame = time;
  const text = messages[msgIndex];

  if (!deleting) {
    charIndex++;
    el.textContent = text.slice(0, charIndex);

    if (charIndex === text.length) {
      deleting = true;
      waitUntil = time + PAUSE_AFTER_TYPE;
    }
  } else {
    charIndex--;
    el.textContent = text.slice(0, charIndex);

    if (charIndex === 0) {
      deleting = false;
      msgIndex = (msgIndex + 1) % messages.length;
      waitUntil = time + PAUSE_AFTER_DELETE;
    }
  }

  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);

Script này chỉ dùng một vòng lặp duy nhất, không tạo timer chồng chéo và không bao giờ render sẵn toàn bộ chuỗi.

Vì sao script này không bị giật

  • Đồng bộ trực tiếp với render loop của trình duyệt
  • Không dùng setTimeout lặp liên tục
  • Kiểm soát rõ ràng trạng thái gõ, xóa và tạm dừng
  • Chỉ cập nhật DOM khi cần thiết

Khi trình duyệt quyết định vẽ frame mới, script mới cho phép thay đổi nội dung. Đây là điểm khác biệt lớn nhất.

Điều chỉnh cho từng trường hợp sử dụng

  • Hero section: tăng PAUSE_AFTER_TYPE
  • Banner quảng cáo: giảm TYPE_SPEED
  • Trang giới thiệu: có thể bỏ bước xóa chữ

Không có cấu hình chung cho mọi giao diện.

Lưu ý khi đưa vào dự án thật

  • Không nên để chạy vô hạn trên mọi trang
  • Nên dừng khi element ra khỏi viewport
  • Mobile yếu nên giảm tốc độ hoặc tắt hẳn

Kết luận

Typewriter effect trông tự nhiên không đến từ hiệu ứng màu mè, mà đến từ việc tôn trọng nhịp render của trình duyệt. Với cách tiếp cận này, hiệu ứng gõ chữ chạy mượt, không nhảy, không lộ kỹ thuật và đủ ổn để dùng lâu dài.

Viết ít nhưng đúng, đó mới là thứ giữ được trải nghiệm.

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...