Popup Ad Corner (Responsive) cho Blogspot: khung nổi gọn nhẹ, tôn trọng người dùng

Anh Trai Nắng
18 tháng 9
Tutorial
Mục lục

Đây là một khung popup nổi ở góc màn hình, nhẹ – responsive – có nút Thu gọn/Đóng – tôn trọng người dùng. Khuyến nghị dùng cho house-ads/thông báo nội bộ. Không chèn mã AdSense trực tiếp vào popup để tránh vi phạm chính sách.

Popup Ad Corner Responsive cho Blogspot
Popup Ad Corner (Responsive) cho Blogspot

Giới thiệu

Nếu bạn muốn hiển thị một đề xuất hay thông báo gọn gàng ở góc màn hình mà không làm phiền người đọc, thì "Popup Ad Corner (Responsive)" là lựa chọn phù hợp. Thành phần này:

  • Xuất hiện sau một khoảng trễ ngắn (tránh gây giật mình).
  • Có nút Thu gọn để tiết kiệm không gian và Đóng để ẩn trong một thời gian.
  • Tối ưu responsive cho mọi thiết bị và tôn trọng prefers-reduced-motion.
  • Dễ tích hợp vào Blogspot/Blogger chỉ bằng một widget HTML/JS.

⚠️ Quan trọng về AdSense: Google không cho phép đặt quảng cáo AdSense trong popup/phần tử nổi do bạn tự tạo (trừ các định dạng overlay do Google quản lý như Anchor/Vignette trong Auto ads). Thành phần này được thiết kế cho house-ads (tự quảng bá), thông báo, hoặc nội dung tuỳ chỉnh. Nếu bạn muốn kiểu "neo" chuẩn của AdSense, hãy để Auto ads xử lý.

🚀 XEM DEMO

Tính năng nổi bật

  • Responsive toàn diện: tự co giãn theo chiều rộng thiết bị; sử dụng aspect-ratio để tránh layout shift.
  • Nhẹ & độc lập: chỉ dùng HTML/CSS/JS thuần; không phụ thuộc thư viện.
  • Tôn trọng người dùng: có Thu gọn (collapse) và Đóng (dismiss), nhớ trạng thái bằng localStorage.
  • Chống chồng chéo UI: logic tránh đè lên chat widget/cookie banner ở góc.
  • Dễ tuỳ biến: đổi tiêu đề, thời gian ẩn sau khi Đóng, vị trí, hiệu ứng, v.v.

Cách thức hoạt động (dưới nắp capo)

Trạng thái hiển thị:

  • data-state="hidden|visible" điều khiển hiệu ứng mờ/di chuyển (tôn trọng prefers-reduced-motion).
  • data-collapsed="true|false" ẩn/hiện phần thân & chân (chế độ thu gọn).

Nhớ trạng thái:

  • localStorage["adCorner:dismissedUntil"]: mốc thời gian đến khi nào mới cho hiện lại sau khi người dùng bấm Đóng.
  • localStorage["adCorner:collapsed"]: trạng thái đang thu gọn hay không.

Trì hoãn xuất hiện: setTimeout(open, 1200) để tránh che nội dung ngay khi tải trang.

Tránh chồng chéo: hàm avoidOverlap() dò các widget thường gặp (chat/cookie ở góc) rồi tinh chỉnh right.

Sơ đồ luồng tối giản:

Page load → delay 1200ms → shouldShow()? → Hiện popup ├─ (Đóng) → set dismissedUntil = now + DURATION → ẩn └─ (Thu gọn) → toggle collapsed ↔ lưu vào localStorage

Hướng dẫn tích hợp vào Blogspot/Blogger

Bạn có 2 cách chính:

Cách A (khuyến nghị) – Thêm Widget HTML/JavaScript

  1. Vào Blogger → Bố cục (Layout).
  2. Chọn vị trí phù hợp (ví dụ Sidebar hoặc Footer), bấm Thêm tiện ích (Add a gadget) → chọn HTML/JavaScript.
  3. Dán code đầy đủ bên dưới vào ô nội dung → Lưu → Lưu bố cục.

Cách B – Chèn trực tiếp vào theme

  1. Vào Chủ đề (Theme) → Chỉnh sửa HTML (Edit HTML).
  2. Dán phần <style> trước </head>, và khối HTML + <script> trước </body>.

Blogger đôi khi hạn chế script trong bài viết. Do đó, cách A (gadget) thường ổn định hơn.

Code đầy đủ (dán vào widget HTML/JS của Blogger)

Đã cấu hình mặc định: bấm Đóng sẽ ẩn khoảng 30 giây rồi mới hiện lại.

  <style>
    :root {
      --popup-max-width: 360px;   /* tối đa trên desktop */
      --popup-min-width: 280px;   /* tối thiểu trên mobile lớn */
      --popup-padding: 8px;
      --popup-radius: 14px;
      --popup-shadow: 0 10px 30px rgba(0,0,0,.18);
      --popup-z: 2147483000; /* trên hầu hết UI */
    }
    .ad-corner {
      position: fixed;
      right: max(12px, env(safe-area-inset-right));
      bottom: max(12px, env(safe-area-inset-bottom));
      width: min(100vw - 24px, var(--popup-max-width));
      min-width: var(--popup-min-width);
      box-sizing: border-box;
      z-index: var(--popup-z);
      display: none; /* sẽ bật bằng JS khi sẵn sàng */
    }
    .ad-card {
      background: #fff;
      border-radius: var(--popup-radius);
      box-shadow: var(--popup-shadow);
      overflow: hidden;
      border: 1px solid rgba(0,0,0,.06);
      display: flex;
      flex-direction: column;
      max-height: 70vh;
    }
    .ad-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 6px 8px;
      gap: 4px;
      background: #f7f7f8;
      border-bottom: 1px solid rgba(0,0,0,.06);
      font: 500 13px/1.2 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
      color: #111;
    }
    .ad-head .title {
      display: flex; align-items: center; gap: 8px;
    }
    .ad-head .dot {
      width: 8px; height: 8px; border-radius: 50%; background: #10b981; /* teal */
    }
    .ad-actions { display: flex; gap: 4px; }
    .ad-btn {
      border: none; background: transparent; cursor: pointer;
      width: 32px; height: 32px; border-radius: 8px; display: grid; place-items: center;
    }
    .ad-btn:hover { background: rgba(0,0,0,.06); }
    .ad-btn:active { transform: translateY(1px); }
    .ad-btn svg { width: 16px; height: 16px; }

    .ad-body {
      padding: var(--popup-padding);
      background: #fff;
    }
    /* khối "ads" responsive: có tỉ lệ, tránh layout shift */
    .ad-slot-wrap {
      position: relative;
      width: 100%;
      /* tỉ lệ 300x250 mặc định; sẽ auto-fit theo chiều rộng */
      aspect-ratio: 6 / 5; /* ~1.2 */
      overflow: hidden;
      border-radius: 10px;
      background: #f2f2f3; /* màu nền chờ */
    }
    .ad-slot-wrap > * { position: absolute; inset: 0; width: 100%; height: 100%; }
    .ad-slot-wrap img {margin: 0 auto;}
    /* huy hiệu nhỏ / tuỳ chọn */
    .ad-foot {
      padding: 6px 8px; font: 12px/1.3 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
      color: #555; background: #fafafb; border-top: 1px solid rgba(0,0,0,.05);
      display: flex; align-items: center; gap: 6px; justify-content: space-between;
    }
    .ad-foot a { color: #2563eb; text-decoration: none; }
    /* Mobile tối ưu: dưới 400px thì kéo full width trừ lề */
    @media (max-width: 400px) {
      .ad-corner {
        left: max(12px, env(safe-area-inset-left));
        right: max(12px, env(safe-area-inset-right));
        width: auto; min-width: 0;
      }
    }
    /* tôn trọng prefers-reduced-motion */
    @media (prefers-reduced-motion: no-preference) {
      .ad-card { transition: transform .25s ease, opacity .25s ease; }
      .ad-corner[data-state="hidden"] .ad-card { opacity: 0; transform: translateY(10px) scale(.98); }
      .ad-corner[data-state="visible"] .ad-card { opacity: 1; transform: translateY(0) scale(1); }
    }
    /* chế độ thu gọn */
    .ad-corner[data-collapsed="true"] .ad-body,
    .ad-corner[data-collapsed="true"] .ad-foot { display: none; }
  </style>
  <!--
    ⚠️ LƯU Ý CHÍNH SÁCH ADSENSE
    Google không cho phép quảng cáo ở pop-up hoặc phần tử nổi che nội dung,
    trừ các định dạng được Google cung cấp (Auto ads: Anchor, Vignette...).
    Nếu bạn chạy AdSense, KHÔNG đặt mã vào popup này. Hãy dùng khối này cho house ads,
    thông báo, hoặc nội dung tự quảng bá. Nếu vẫn muốn dùng AdSense, nên dùng Auto ads hợp lệ.
  -->
  <!-- Popup góc màn hình -->
  <div class="ad-corner" id="adCorner" role="dialog" aria-label="Khối nội dung góc màn hình" aria-modal="false" data-state="hidden" data-collapsed="false">
    <div class="ad-card">
      <div class="ad-head">
        <div class="title"><span class="dot" aria-hidden="true"></span><span>Đề xuất dành cho bạn</span></div>
        <div class="ad-actions">
          <button class="ad-btn" id="btnCollapse" aria-label="Thu gọn" title="Thu gọn" type="button">
            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M6 15l6-6 6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
          </button>
          <button class="ad-btn" id="btnClose" aria-label="Đóng" title="Đóng" type="button">
            <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M6 6l12 12M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
          </button>
        </div>
      </div>
      <div class="ad-body">
        <div class="ad-slot-wrap">
          <!--
            ✅ HOUSE-AD (khuyến nghị): đặt nội dung tuỳ ý ở đây (ảnh/banner/iframe nội bộ).
            Ví dụ demo: hình ảnh + link.
          -->
          <a href="#" style="display:block; width:100%; height:100%;">
            <img alt="House Ad" src="https://picsum.photos/600/500" style="width:100%; height:100%; object-fit: cover;" />
          </a>
          <!--
            ❌ KHÔNG KHUYẾN NGHỊ CHO ADSENSE (có thể vi phạm chính sách):
            Nếu bạn tự chịu trách nhiệm, thay khối trên bằng mã <ins class="adsbygoogle"> chuẩn.
            Nhắc lại: hãy ưu tiên Auto ads (Anchor/Vignette) để tuân thủ.

            <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXX" crossorigin="anonymous"></script>
            <ins class="adsbygoogle"
                 style="display:block"
                 data-ad-client="ca-pub-XXXX"
                 data-ad-slot="1234567890"
                 data-ad-format="auto"
                 data-full-width-responsive="true"></ins>
            <script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
          -->
        </div>
      </div>
      <div class="ad-foot">
        <span>Hiển thị góc màn hình • Tôn trọng người dùng</span>
        <a href="#" id="whyLink">Vì sao thấy mục này?</a>
      </div>
    </div>
  </div>
  <script>
    (function() {
      const EL = {
        root: document.getElementById('adCorner'),
        btnClose: document.getElementById('btnClose'),
        btnCollapse: document.getElementById('btnCollapse'),
        why: document.getElementById('whyLink')
      };

      const STORAGE_KEY = 'adCorner:dismissedUntil';
      const COLLAPSE_KEY = 'adCorner:collapsed';

      // Bạn có thể đổi điều kiện hiển thị tuỳ ý
      const shouldShow = () => {
        try {
          const until = Number(localStorage.getItem(STORAGE_KEY) || 0);
          return Date.now() > until;
        } catch (_) { return true; }
      };

      const setDismissForDays = (days = 1) => {
        const ms = days * 24 * 60 * 60 * 1000;
        try { localStorage.setItem(STORAGE_KEY, String(Date.now() + ms)); } catch(_) {}
      };

      const restoreCollapsed = () => {
        try { EL.root.dataset.collapsed = localStorage.getItem(COLLAPSE_KEY) === '1' ? 'true' : 'false'; } catch(_) {}
      };

      const toggleCollapsed = () => {
        const next = EL.root.dataset.collapsed !== 'true';
        EL.root.dataset.collapsed = next ? 'true' : 'false';
        try { localStorage.setItem(COLLAPSE_KEY, next ? '1' : '0'); } catch(_) {}
      };

      const open = () => {
        EL.root.style.display = 'block';
        requestAnimationFrame(() => EL.root.dataset.state = 'visible');
      };
      const close = () => {
        EL.root.dataset.state = 'hidden';
        setTimeout(() => { EL.root.style.display = 'none'; }, 250);
      };

      // Sự kiện
      EL.btnClose.addEventListener('click', () => {
        setDismissForDays(30/(24*60*60)); // ~30 giây // ẩn 1 ngày sau khi đóng
        close();
      });
      EL.btnCollapse.addEventListener('click', toggleCollapsed);
      EL.why.addEventListener('click', (e) => {
        e.preventDefault();
        alert('Bạn thấy mục này vì chúng tôi muốn giới thiệu nội dung hữu ích. Bạn có thể đóng hoặc thu gọn bất kỳ lúc nào.');
      });

      // Khởi tạo
      restoreCollapsed();
      if (shouldShow()) {
        // trì hoãn 1 chút để tránh che nội dung ngay lập tức
        setTimeout(open, 1200);
      }

      // Đảm bảo không đè UI quan trọng (tự động đổi vị trí nếu có thanh chat ở góc phải)
      // Bạn có thể tuỳ biến theo app thực tế
      const avoidOverlap = () => {
        const overlapping = document.querySelector('[data-affix="bottom-right"], .chat-widget, .cookie-banner');
        if (!overlapping) return;
        const rect = overlapping.getBoundingClientRect();
        const space = window.innerWidth - rect.right + 12;
        EL.root.style.right = space < 220 ? `calc(${space}px + env(safe-area-inset-right))` : 'max(12px, env(safe-area-inset-right))';
      };
      window.addEventListener('resize', avoidOverlap, { passive: true });
      setTimeout(avoidOverlap, 1800);
    })();
  </script>
  

Gợi ý nội dung: Thay ảnh demo bằng banner nội bộ, hoặc chèn iframe/HTML tuỳ chỉnh cho house-ads. Tránh dùng ảnh quá nặng để đảm bảo hiệu năng.

Lợi ích mang lại

  • Tăng tỷ lệ nhìn thấy (viewability) cho nội dung bạn muốn nhấn mạnh, nhưng vẫn tôn trọng trải nghiệm đọc.
  • Giảm bounce do khó chịu: người dùng có quyền thu gọn/đóng, thời gian ẩn có thể tinh chỉnh.
  • Không phụ thuộc hệ thống quảng cáo: dùng tốt cho chiến dịch nội bộ, CTA, giới thiệu sản phẩm mới.
  • Dễ bảo trì: mã gọn, không phụ thuộc framework, di chuyển giữa theme/blog dễ dàng.

Câu hỏi thường gặp (FAQ)

1) Có thể chèn AdSense vào đây không?

Không khuyến nghị. Đây là popup/floating do bạn tự tạo; chèn AdSense có thể vi phạm chính sách. Nếu muốn định dạng overlay hợp lệ, hãy bật Auto ads → Anchor/Vignette của AdSense.

2) Có thể đặt thời gian ẩn theo phút/giờ/ngày?

Có. Dùng DISMISS_SECONDS để thiết lập bất kỳ giá trị nào (giây). Ví dụ: 10 phút = 600, 2 giờ = 7200, 3 ngày = 259200.

3) Có làm nặng trang không?

Không đáng kể. Mã thuần, không tải thêm thư viện. Vẫn nên tối ưu ảnh/iframe bên trong để giữ hiệu năng.

4) Có hiện trên mobile không?

Có, phần CSS đã tối ưu lề an toàn (env(safe-area-inset-*)) và giới hạn chiều rộng hợp lý. Bạn có thể điều chỉnh thêm qua media query.

Giấy phép & Ghi công

Mã nguồn trong bài viết này phát hành theo MIT License. Bạn có thể dùng miễn phí cho mục đích cá nhân và thương mại, nhớ giữ chú thích license khi cần.

Nếu thấy hữu ích, vui lòng dẫn nguồn về bài viết này khi chia sẻ lại.

Chia sẻ:
Đã sao chép link!

Bài viết liên quan

Nhận xét (2)

Hiển thị

Đăng nhận xét