Deprecated: Creation of dynamic property Simple_Code_Block_Loader::$shortcodes is deprecated in /home/u643716194/domains/votus.com.ua/public_html/wp-content/plugins/simple-code-block/core/class-simple-code-block-loader.php on line 61
Веб-аналітика PDF документів за допомогою PDF.js - Михайло Вотусь - Cайт вебмайстра, аналітика

Веб-аналітика PDF документів за допомогою PDF.js

Передісторія

Стаття виникла сама по собі оскільки була цікава ідея – якось трекати взаємодію юзерів з PDF-документами на сайті. Компанії часто використовують PDF-ки. Вайтпейпери, інструкції, звіти, прайси, шаблони… Як власнику сайту знати, що документ переглядали? Максимум що може знати власник сайту, скільки раз відкривали по кнопці. А це не багато. Може щось там дивився… а що саме? Чи дійшов до кінця документу? А чи скачав він його? Подивився хоч півсторінки? А може він його кинув на друк?

Якщо піти з цим питанням до ChatGPT він скаже, шо можна і напише спосіб, як відстежити відкривання PDF, або запропонує iframe. Спойрел: він також не допоможе :). Читачі мого блогу напевно таке вміють зробити і без ChatGPT.

Будь-яка веб-аналітика цього не вміє, бо не може трекати взаємодію з PDF. Цей файл не є трекабельним.

Тому я пішов шукати глибше і згадав, що ще колись в 201Х, я вчив HTML5 і там був малозрозумілий і малодоступний тег <canvas>. Бібліотек мінімум було, писати шось з нуля під нього – треба бути профіком. А для мене верстка була не чимось профільним.
То ж мої пошуки розпочалися саме з інвестігейту “а шо нового є під canvas” він ж може показувати все що завгодно і все там можна трекати. І тут я натрапив на PDF.JS. Бібліотека від Mozilla, яка може закрити дане питання дуже легко, безкоштовно і головне краще ніж будь-який платний сервіс. Бо івентами можна завішати весь документ. Хіба не скл

А тепер трохи практики

Етап 1: Встановлення PDF.js

Хостимо свій pdf.js + pdf.worker.js та web/viewer.html в одному каталозі. Наприклад:

/pdfjs/
   ├── build/
   │    ├── pdf.js
   │    └── pdf.worker.js
   └── web/
        └── viewer.html

І викликаємо у сторінці (через iframe):

<iframe src="/pdfjs/web/viewer.html?file=/docs/sample.pdf"
        width="100%" height="600"></iframe>

Етап 2: Події з eventBus

З PDF.js v2.x можна підписатись на колбеки з документації:

document.addEventListener('webviewerloaded', () => {
  PDFViewerApplication.initializedPromise.then(() => {
    const bus = PDFViewerApplication.eventBus;
    bus.on("documentloaded", () => console.log('PDF завантажений'));
    bus.on("pagechanging", e => console.log('Зміна сторінки', e.pageNumber));
    bus.on("scalechanging", e => console.log('Масштаб', e.scale));
  });
});

І ще DOM‑івент:

document.addEventListener('pagechange', e =>
  console.log('DOM event pagechange', e.pageNumber, e.previousPageNumber)
);

Етап 3: Прив’язка треків до івентів

function sendPDFEvent(name, data = {}) {
  gtag('event', name, { event_category: 'PDF', ...data });
}

// коли завантажили PDF
bus.on("documentloaded", () =>
  sendPDFEvent('PDF Loaded', { pages: PDFViewerApplication.pdfDocument.numPages })
);

// коли переходять сторінку
bus.on("pagechanging", e =>
  sendPDFEvent('PDF Page View', { page: e.pageNumber })
);

// масштаб змінюють
bus.on("scalechanging", e =>
  sendPDFEvent('PDF Scale Change', { scale: e.scale })
);

Етап 4: Треба більше даних? Ось вам і інші тригери

  • Скачування і друк: в viewer.html кнопки мають id #download, #print:
document.getElementById('download')
  .addEventListener('click', () => sendPDFEvent('PDF Download'));

document.getElementById('print')
  .addEventListener('click', () => sendPDFEvent('PDF Print'));
  • Глибина скролу: PDF.js не дає події, тому юзаємо простий скрол-трекінг:
const vc = document.getElementById('viewerContainer');
let flags = {};
vc.addEventListener('scroll', () => {
  const pct = Math.floor((vc.scrollTop + vc.clientHeight) / vc.scrollHeight * 100);
  [25,50,75,100].forEach(th => {
    if (pct >= th && !flags[th]) {
      flags[th] = true;
      sendPDFEvent('PDF Scroll Depth', { depth: th + '%' });
    }
  });
});
  • Час взаємодії: додаємо setInterval, корисне: якщо ніяких подій немає 5 секунд, вважаємо, що відмова.

Ось що вийшло в мене:

Якщо не підходить iframe

Представимо, що PDF не має відображатися на цій самій сторінці, а має жити окремо за своїм посиланням. Тобто як стандартний файл PDF десь розміщений на сервері і його можна скачати. Ось гіперпосилання на PDF. В такому випадку ось порядок дій:

Крок 1: Додаємо GTM-код у viewer.html

  1. Відкрий файл pdfjs/web/viewer.html
  2. Знайди тег <head> і встав всередину свій GTM Snippet:
<!-- Google Tag Manager -->
<script>
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
  'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX'); // <== сюди встав свій GTM ID
</script>
<!-- End Google Tag Manager -->
  1. Потім відразу після відкриття <body> встав свій noscript iframe:
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

Крок 2: Налаштуй GTM для трекінгу

1. Трекінг кліків по кнопках

Приклад для кнопки «Download»:
  • Створюємо тригер:
    Тип: Click – All Elements
    Фільтр: Click IDequalsdownload
  • Створимо тег GA4 Event:
    • Назва івенту: pdf_download
    • Категорія: PDF
    • Прив’язка: до тригера Click on Download

Аналогічно налаштуємо трекінг для:

ПодіяClick ID
Printprint
Previous Pageprevious
Next Pagenext

2. Трекінг перегляду сторінок через Custom Event

PDF.js кидає DOM подію pagechange. Можна використати:

  • Створимо тригер типу: Custom Event
    • Назва події: pagechange
  • У цьому тригері можна додати змінну Page Number:
    • Створимо нову User-Defined Variable
    • Тип: DOM Element
    • Selection Method: CSS Selector
    • Selector: #pageNumber (або #pageNumberInput)
    • Attribute name: value
  • Прив’язуємо цю змінну до тегу GA4, і в параметри передаємр її як page_number

3. Що ще зробити

  • Встановити таймер (Trigger type: Timer) для відстеження часу перегляду
  • Відслідковувати кількість інших взаємодій
  • Створити Scroll Depth trigger на #viewerContainer (але потрібно буде трохи JavaScript-Variables)

Бонус для лінивих – готовий JSON для імпорту в GTM (хто вміє) =)

{
  "exportFormatVersion": 2,
  "exportTime": "2025-06-13T12:00:00",
  "containerVersion": {
    "container": {
      "publicId": "GTM-XXXXXXX",
      "name": "PDF.js Tracker"
    },
    "tag": [
      {
        "name": "GA4 - PDF Download",
        "type": "ga4Event",
        "parameter": [
          {
            "key": "eventName",
            "value": "pdf_download"
          },
          {
            "key": "eventParameters",
            "type": "list",
            "list": [
              {
                "type": "map",
                "map": [
                  {
                    "key": "name",
                    "value": "event_category"
                  },
                  {
                    "key": "value",
                    "value": "PDF"
                  }
                ]
              }
            ]
          }
        ],
        "triggerId": [
          "1"
        ]
      },
      {
        "name": "GA4 - PDF Print",
        "type": "ga4Event",
        "parameter": [
          {
            "key": "eventName",
            "value": "pdf_print"
          },
          {
            "key": "eventParameters",
            "type": "list",
            "list": [
              {
                "type": "map",
                "map": [
                  {
                    "key": "name",
                    "value": "event_category"
                  },
                  {
                    "key": "value",
                    "value": "PDF"
                  }
                ]
              }
            ]
          }
        ],
        "triggerId": [
          "2"
        ]
      },
      {
        "name": "GA4 - PDF Page View",
        "type": "ga4Event",
        "parameter": [
          {
            "key": "eventName",
            "value": "pdf_page_view"
          },
          {
            "key": "eventParameters",
            "type": "list",
            "list": [
              {
                "type": "map",
                "map": [
                  {
                    "key": "name",
                    "value": "page_number"
                  },
                  {
                    "key": "value",
                    "value": "{{PDF Page Number}}"
                  }
                ]
              }
            ]
          }
        ],
        "triggerId": [
          "3"
        ]
      }
    ],
    "trigger": [
      {
        "name": "Click - PDF Download",
        "type": "CLICK",
        "filter": [
          {
            "type": "EQUALS",
            "parameter": [
              {
                "type": "template",
                "key": "arg0",
                "value": "{{Click ID}}"
              },
              {
                "type": "template",
                "key": "arg1",
                "value": "download"
              }
            ]
          }
        ],
        "uniqueTriggerId": "1"
      },
      {
        "name": "Click - PDF Print",
        "type": "CLICK",
        "filter": [
          {
            "type": "EQUALS",
            "parameter": [
              {
                "type": "template",
                "key": "arg0",
                "value": "{{Click ID}}"
              },
              {
                "type": "template",
                "key": "arg1",
                "value": "print"
              }
            ]
          }
        ],
        "uniqueTriggerId": "2"
      },
      {
        "name": "Event - PDF Page Change",
        "type": "CUSTOM_EVENT",
        "customEventFilter": [
          {
            "type": "EQUALS",
            "parameter": [
              {
                "type": "template",
                "key": "arg0",
                "value": "{{_event}}"
              },
              {
                "type": "template",
                "key": "arg1",
                "value": "pagechange"
              }
            ]
          }
        ],
        "uniqueTriggerId": "3"
      }
    ],
    "variable": [
      {
        "name": "PDF Page Number",
        "type": "DOM_ELEMENT",
        "parameter": [
          {
            "key": "attributeName",
            "value": "value"
          },
          {
            "key": "elementSelector",
            "value": "#pageNumber"
          },
          {
            "key": "selectorMethod",
            "value": "CSS_SELECTOR"
          }
        ]
      }
    ]
  }
}

Залишити коментар