Cơ chế tạm dừng hoạt ảnh trong Nativefier: Tối ưu hóa tab nền

Khi chạy nhiều ứng dụng web dưới dạng ứng dụng desktop qua Nativefier, bạn có thể nhận thấy các hoạt ảnh, video hoặc hiệu ứng CSS vẫn tiếp tục chạy ngay cả khi tab bị đưa vào nền — gây tiêu tốn CPU không cần thiết, làm nóng máy và giảm tuổi thọ pin. Nativefier, được xây dựng trên nền tảng Electron, cung cấp cơ sở hạ tầng để kiểm soát vòng đời tab một cách chủ động, từ đó mở ra khả năng tạm dừng nội dung động khi tab không ở trạng thái hiển thị.

Thách thức chính trong quản lý tab nền

Trong môi trường trình duyệt thông thường, sự kiện visibilitychange hoặc trạng thái document.hidden thường bị bỏ qua bởi các trang web, dẫn đến việc script và CSS animation vẫn hoạt động liên tục. Nativefier tận dụng các hook của Electron như BrowserWindow.webContents.on('did-navigate'), webContents.on('visibility-change') và API điều khiển trực tiếp DOM để can thiệp sâu hơn vào luồng thực thi.

Cách tiếp cận dựa trên sự kiện hiển thị

Một giải pháp hiệu quả và không xâm lấn là gắn xử lý vào sự kiện visibilitychange ngay tại thời điểm khởi tạo cửa sổ. Thay vì chờ đợi logic phía client tự xử lý, Nativefier có thể tiêm một đoạn script khởi tạo (preload script) để giám sát trạng thái hiển thị:

function setupVisibilityHandler() {
  const pauseMedia = () => {
    document.querySelectorAll('video, audio, canvas').forEach(el => {
      if ('pause' in el && typeof el.pause === 'function') {
        el.pause();
      }
      if (el instanceof HTMLCanvasElement) {
        (el as any).isPaused = true;
      }
    });
  };

  const resumeMedia = () => {
    document.querySelectorAll('video, audio, canvas').forEach(el => {
      if ('play' in el && typeof el.play === 'function') {
        el.play().catch(() => {});
      }
      if (el instanceof HTMLCanvasElement) {
        (el as any).isPaused = false;
      }
    });
  };

  document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
      pauseMedia();
      // Tạm dừng tất cả requestAnimationFrame
      (window as any).__rafId = requestAnimationFrame(() => {});
      cancelAnimationFrame((window as any).__rafId);
    } else {
      resumeMedia();
      // Khôi phục chu kỳ render nếu cần
      if ((window as any).__rafCallback) {
        (window as any).__rafId = requestAnimationFrame((window as any).__rafCallback);
      }
    }
  });
}

// Gọi khi preload script được nạp
setupVisibilityHandler();

Tích hợp vào quy trình khởi tạo cửa sổ

Trong file app/src/helpers/windowHelpers.ts, hàm createNewWindow có thể được mở rộng để tự động đính kèm script quản lý hiển thị vào mỗi BrowserWindow mới:

function attachVisibilityScript(window: BrowserWindow): void {
  const preloadPath = path.join(__dirname, '..', 'preload', 'visibility.js');
  window.webContents.session.setPreloads([
    ...window.webContents.session.getPreloads(),
    preloadPath
  ]);
}

export function createNewWindow(
  options: WindowOptions,
  setupWindow: (opts: WindowOptions, win: BrowserWindow) => void,
  url: string
): BrowserWindow {
  const win = new BrowserWindow({
    ...options,
    webPreferences: {
      ...options.webPreferences,
      preload: path.join(__dirname, '..', 'preload', 'base.js'),
      nodeIntegration: false,
      contextIsolation: true,
    }
  });

  attachVisibilityScript(win);

  win.loadURL(url);
  return win;
}

Tối ưu hóa CSS động không cần JavaScript

Ngoài việc kiểm soát qua script, Nativefier cũng hỗ trợ tiêm CSS toàn cục nhằm vô hiệu hóa hoạt ảnh bằng thuộc tính animation-play-state. Đoạn mã sau có thể được áp dụng thông qua webContents.insertCSS:

:root {
  --animation-pause-state: paused;
}

* {
  animation-play-state: var(--animation-pause-state) !important;
  transition-property: none !important;
  will-change: auto !important;
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

Kết hợp với trạng thái document.hidden, giá trị CSS custom property có thể được cập nhật động bằng webContents.executeJavaScript, giúp đạt được mức độ kiểm soát linh hoạt mà không yêu cầu sửa đổi mã nguồn ứng dụng gốc.

Khả năng mở rộng cho hệ sinh thái Electron

Cơ chế này không chỉ áp dụng riêng cho Nativefier — nó có thể được đóng gói thành một module độc lập (ví dụ: electron-tab-suspender) và tích hợp vào bất kỳ ứng dụng Electron nào sử dụng BrowserView hoặc BrowserWindow đa tab. Với Electron 24+, việc sử dụng webContents.visibilityState còn cho phép xác định chính xác hơn trạng thái "ẩn", bao gồm cả khi cửa sổ bị thu nhỏ hoặc nằm dưới các cửa sổ khác.

Thẻ: electron nativefier css-animation visibilitychange preload-script

Đăng vào ngày 20 tháng 5 lúc 01:40