Cập nhật: Lê Phúc 12 phút đọc

Cách dùng thư viện Fancybox chuẩn tối ưu hiệu suất

Trong suốt mấy năm làm nghề, mình nhận thấy một điều khá thú vị: hầu như ai khi làm web cũng muốn có những hiệu ứng hình ảnh thật lung linh để thu hút người xem. Tuy nhiên, ranh giới giữa một website “đẹp” và một website “nặng” đôi khi rất mong manh. Bản thân mình thời gian đầu cũng từng mắc sai lầm khi cài đặt quá nhiều thư viện mà không để ý rằng chúng đang “ngốn” tài nguyên của người dùng như thế nào.

Hôm nay, mình muốn chia sẻ với bạn về cách mình đang dùng thư viện Fancybox. Đây là một công cụ mình thấy rất ổn định để hiển thị ảnh và video. Thay vì chỉ hướng dẫn cài đặt thông thường, mình sẽ chia sẻ cách mình tối ưu nó để trang web vẫn chạy thanh thoát, đạt điểm số tốt trên các công cụ đo lường và quan trọng nhất là khách hàng của bạn cảm thấy thoải mái khi lướt web.

Minh họa thực tế lightbox Fancybox hiện đại, thumbnails classic, lazy load giúp website load siêu nhanh mà vẫn đẹp chuyên nghiệp.
Demo gallery Fancybox với thumbnails tối ưu

Fancybox là gì và tại sao mình lại chọn nó?

Nếu bạn chưa biết thì Fancybox là một thư viện JavaScript giúp tạo ra các hộp thoại (lightbox) để phóng to hình ảnh, xem video hoặc các nội dung khác ngay trên trang hiện tại.

Trước đây, Fancybox phụ thuộc khá nhiều vào jQuery, nhưng ở các phiên bản mới (như v5), nó đã chuyển sang dùng thuần JavaScript (Vanilla JS). Điều này đối với mình là một điểm cộng lớn vì nó giúp giảm bớt sự phụ thuộc vào các thư viện cũ, giúp web gọn nhẹ hơn.

Những ứng dụng mình hay áp dụng thực tế:

  • Trang Portfolio: Khi mình thực hiện các dự án thiết kế website WordPress, mình thường dùng Fancybox để khách hàng có thể xem chi tiết các mẫu thiết kế mà không cần chuyển trang.
  • Trang sản phẩm: Giúp người mua hàng phóng to ảnh sản phẩm để xem rõ chất liệu hoặc chi tiết nhỏ.
  • Video Testimonial: Thay vì nhúng trực tiếp video YouTube làm nặng trang, mình dùng một tấm ảnh đại diện, khi khách click vào thì Fancybox mới gọi video ra.

Vài ưu và nhược điểm nhỏ mình rút ra:

  • Ưu điểm: Giao diện rất hiện đại, hỗ trợ tốt các cử chỉ vuốt chạm trên điện thoại. Đặc biệt là khả năng tự động tạo thumbnails và hỗ trợ phím điều hướng khá thông minh.
  • Nhược điểm: Dù đã cải thiện nhưng nếu cứ nạp file script một cách mặc định, nó vẫn sẽ cộng thêm khoảng 100KB vào tổng dung lượng tải trang. Nếu bạn không khéo léo xử lý, nó sẽ làm chậm thời gian phản hồi đầu tiên của website.

Tại sao chúng ta cần tối ưu Fancybox?

Mình luôn tâm niệm rằng, một website tốt trước hết phải là một website nhanh. Khách hàng bây giờ khá thiếu kiên nhẫn; chỉ cần trang xoay vòng quá 3 giây là họ có thể rời đi ngay.

Khi mình kiểm tra nhiều website, mình thấy lỗi phổ biến là mọi người nạp tất cả các thư viện ngay từ khi trang bắt đầu tải (trong thẻ <head>). Điều này khiến trình duyệt bị “ngợp”. Thực tế, người dùng chưa chắc đã click vào xem ảnh ngay khi vừa vào trang, vậy tại sao chúng ta phải bắt họ tải đống code đó ngay lập tức?

Bằng cách áp dụng kỹ thuật load “lười” (lazy load) và chỉ nạp khi cần, mình đã giúp nhiều dự án cải thiện đáng kể chỉ số Core Web Vitals. Bạn có thể xem thêm về cách mình tối ưu và tăng tốc độ website WordPress để hiểu thêm về tầm quan trọng của việc tiết kiệm từng mili giây tải trang.

Cách mình triển khai từ dễ đến khó

Mình sẽ hướng dẫn bạn cách làm sao để tích hợp Fancybox vào website một cách hiệu quả, tối ưu nhất.

Bước 1: Cấu trúc HTML đơn giản và chuẩn SEO

Thay vì chỉ dùng thẻ ảnh thông thường, mình bao bọc nó trong một thẻ link với các thuộc tính data. Việc này giúp script nhận diện được ảnh nào thuộc nhóm nào.

HTML
<a data-fancybox="gallery-portfolio" 
   data-src="https://domain.com/anh-full-1.jpg" 
   data-thumb="https://domain.com/anh-thumb-1.jpg" 
   href="#">
   <img src="https://domain.com/anh-thumb-1.jpg" 
        alt="Mô tả SEO ảnh 1" 
        width="600" height="400" loading="lazy">
</a>

<button data-open-gallery="gallery-portfolio" 
        data-start-index="2">
   Xem toàn bộ 12 ảnh dự án
</button>

Lưu ý từ Phúc: Luôn nhớ thêm thuộc tính width, heightloading=”lazy” cho ảnh thumbnail để tránh lỗi CLS (thay đổi bố cục đột ngột).

Bước 2: JavaScript tối ưu (Kỹ thuật Preload Lazy)

Đây là phần không thể thiếu trong bài viết này của mình. Thay vì load Fancybox ngay khi tải trang, mình sẽ chỉ load khi người dùng di chuột gần ảnh hoặc click vào ảnh.

JavaScript
/* ===============================
   FANCYBOX GALLERY WITH THUMBS - TỐI ƯU BỞI LÊ PHÚC
================================ */
let fancyboxPromise = null;
let isOpening = false;

/* ---------- LOAD LAZY: Chỉ tải file khi cần ---------- */
function preloadFancybox() {
   if (window.Fancybox) return Promise.resolve();
   if (fancyboxPromise) return fancyboxPromise;

   fancyboxPromise = new Promise(resolve => {
      const css = document.createElement("link");
      css.rel = "stylesheet";
      css.href = "https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css";

      const js = document.createElement("script");
      js.src = "https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.js";
      js.defer = true;
      js.onload = () => requestAnimationFrame(resolve);

      document.head.append(css, js);
   });
   return fancyboxPromise;
}

/* ---------- BUILD ITEMS: Quét ảnh trong group ---------- */
function buildGalleryItems(group) {
   return Array.from(
      document.querySelectorAll(`[data-fancybox="${group}"]`)
   ).map(el => ({
      src: el.dataset.src,
      type: "image",
      thumb: el.dataset.thumb || el.dataset.src
   }));
}

/* ---------- OPEN GALLERY: Mở với hiệu ứng mượt ---------- */
function openGallery(group, startIndex = 0) {
   if (!window.Fancybox || isOpening) return;
   if (Fancybox.getInstance()) return;

   const items = buildGalleryItems(group);
   if (!items.length) return;

   isOpening = true;
   requestAnimationFrame(() => {
      Fancybox.show(items, {
         group,
         startIndex,
         animationEffect: "fade",
         animated: true,
         dragToClose: true,
         trapFocus: true,
         autoFocus: true,
         Hash: true, // Hỗ trợ link URL chia sẻ
         preload: 1, // Chỉ load trước 1 ảnh kế tiếp để tiết kiệm băng thông
         lazyLoad: true,
         Thumbs: {
            autoStart: items.length > 1,
            type: "classic"
         },
         Image: {
            zoom: true,
            click: "toggleZoom",
            wheel: "zoom"
         },
         on: {
            ready: (fancyboxRef) => {
               const newIndex = startIndex || fancyboxRef.getSlide().index;
               history.replaceState(null, "", `#${group}-${newIndex + 1}`);
            },
            "Carousel.change": (fancyboxRef) => {
               const newIndex = fancyboxRef.getSlide().index + 1;
               history.replaceState(null, "", `#${group}-${newIndex}`);
            },
            destroy: () => { isOpening = false; }
         }
      });
   });
}

/* ---------- HASH SUPPORT: Tự động mở từ link chia sẻ ---------- */
function parseHash() {
   const hash = window.location.hash.replace("#", "");
   if (!hash) return null;
   const match = hash.match(/^(.+)-(\d+)$/);
   if (!match) return null;
   return {
      group: match[1],
      index: parseInt(match[2], 10) - 1
   };
}

async function openFromHash() {
   const data = parseHash();
   if (!data) return;
   const exists = document.querySelector(`[data-fancybox="${data.group}"]`);
   if (!exists) return;

   await preloadFancybox();
   openGallery(data.group, data.index);
}

window.addEventListener("load", openFromHash);

/* ---------- CLICK EVENT: Lắng nghe click ---------- */
document.addEventListener("click", async e => {
   const trigger = e.target.closest("[data-open-gallery], [data-fancybox]");
   if (!trigger) return;

   e.preventDefault();
   await preloadFancybox();

   let group = trigger.dataset.openGallery || trigger.dataset.fancybox;
   const startIndex = trigger.dataset.openGallery 
                      ? parseInt(trigger.dataset.startIndex || 0) 
                      : 0;
   openGallery(group, startIndex);
});

Ngoài ra nếu bạn muốn tìm hiểu sâu hơn về các config và thuộc tính của Fancybox có thể tham khảo thêm tại https://fancyapps.com/fancybox/

Tại sao bạn cần phải dùng cách này?

Nếu bạn đã đọc bài viết về cách trì hoãn GTM của mình, bạn sẽ thấy mình rất ưa chuộng phương pháp “Lazy Loading Assets”.

  1. Hàm preloadFancybox: Mình dùng một Promise để đảm bảo file JS và CSS chỉ được chèn vào DOM đúng một lần duy nhất. Khi người dùng click vào ảnh, hệ thống sẽ kiểm tra xem Fancybox đã sẵn sàng chưa. Nếu chưa, nó sẽ load “chớp nhoáng” trong khoảng 200ms.
  2. requestAnimationFrame: Đây là kỹ thuật giúp trình duyệt thực hiện các thay đổi giao diện vào thời điểm tối ưu nhất, giúp hiệu ứng mở modal không bị giật (stuttering).
  3. Hỗ trợ Hash URL: Đây là tính năng rất ít người dùng nhưng lại cực kỳ hiệu quả cho SEO social. Khi bạn gửi link website.com/#portfolio-3, trình duyệt sẽ tự động mở trang và bật ngay ảnh thứ 3 cho khách xem.
  4. Tối ưu CSS: Mình thường kết hợp kỹ thuật này với việc tối ưu CSS để loại bỏ các đoạn style thừa, giúp tổng thể trang web cực kỳ thanh thoát.

Mẹo nâng cao từ kinh nghiệm thực tế của Phúc

Qua hàng chục dự án, mình rút ra được vài mẹo nhỏ bạn hãy thử tham khảo xem nha :>

  • Sử dụng WebP cho Thumbnails: Đừng bao giờ dùng ảnh gốc (full size) để làm ảnh đại diện hiển thị trên trang. Hãy dùng ảnh đã resize và chuyển sang định dạng .webp.
  • Preload Hint: Nếu bạn biết chắc chắn người dùng sẽ click vào gallery (ví dụ trang Portfolio), bạn có thể thêm <link rel=”preload”> cho file CSS của Fancybox trong thẻ <head> để nó sẵn sàng nhanh hơn nữa.
  • Video Performance: Khi dùng Fancybox để mở video, hãy set data-type=”video”. Fancybox v5 xử lý iframe video rất thông minh, nó sẽ hủy iframe ngay khi đóng modal để giải phóng tài nguyên CPU.
  • Group lớn: Nếu một gallery có trên 50 ảnh, đừng hiển thị hết 50 cái thumbnails ra ngoài. Hãy chỉ hiển thị 5-6 ảnh tiêu biểu và dùng nút “Xem thêm” để gọi hàm openGallery. Điều này giúp giảm số lượng DOM node, tốt cho điểm PageSpeed.

Kết quả thực tế mình đã đạt được

Sau khi áp dụng bộ code chuẩn này cho các dự án gần đây, mình ghi nhận được những con số rất tích cực:

  • Điểm Mobile PageSpeed: Tăng trung bình từ 70 lên 94 điểm.
  • Chỉ số TBT (Total Blocking Time): Giảm từ ~400ms xuống còn dưới 100ms.
  • Thời gian On-site: Khách hàng ở lại xem ảnh lâu hơn vì trải nghiệm chuyển ảnh cực kỳ mượt, không có độ trễ.
Biểu đồ minh họa sự ổn định của các chỉ số tốc độ khi áp dụng kỹ thuật load thư viện theo yêu cầu thay vì load mặc định.
Cải thiện Core Web Vitals sau khi tối ưu Fancybox

Lời kết

Việc sử dụng thư viện Fancybox không khó, nhưng dùng sao cho chuẩn SEOtối ưu hiệu suất thì đòi hỏi một chút sự tận tâm trong cách xử lý code. Hy vọng với những chia sẻ thực tế và bộ code mình đã tinh chỉnh, bạn có thể tự tin triển khai cho website của mình hoặc khách hàng.

Nếu bạn gặp khó khăn trong quá trình cài đặt hoặc muốn tối ưu sâu hơn cho WordPress, đừng ngần ngại để lại comment hoặc liên hệ với mình. Chúc bạn có một website vừa đẹp, vừa nhanh!

Lê Văn Phúc

LÊ PHÚC

Xin chào các bạn! mình là Lê Văn Phúc (Phuc Lee), chuyên tư vấn & triển khai giải pháp website tối ưu cho các cá nhân và doanh nghiệp. Nếu bạn đang gặp khó khăn hay có bất kì thắc mắc, đừng ngần ngại hãy kết nối với Phúc. Rất vui được đồng hành cùng bạn!