Передісторія
Стаття виникла сама по собі оскільки була цікава ідея – якось трекати взаємодію юзерів з 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
- Відкрий файл
pdfjs/web/viewer.html
- Знайди тег
<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 -->
- Потім відразу після відкриття
<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 ID
→equals
→download
- Створимо тег GA4 Event:
- Назва івенту:
pdf_download
- Категорія:
PDF
- Прив’язка: до тригера
Click on Download
- Назва івенту:
Аналогічно налаштуємо трекінг для:
Подія | Click ID |
---|---|
print | |
Previous Page | previous |
Next Page | next |
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" } ] } ] } }