Страницы

Поиск по вопросам

среда, 5 февраля 2020 г.

Глубина просмотра одностраничных сайтов

#javascript #jquery


Пытаюсь написать скрипт, который будет измерять глубину просмотра одностраничных сайтов.

Идея следующая: одностраничный сайт разделен на секции. При просмотре любой из секций
больше 15 секунд срабатывает цель Яндекс.Метрики.

Пример секций.

Определение конкретной секции. // Получаем нужный элемент var element = document.querySelector('target'); var Visible = function (target) { // Все позиции элемента var targetPosition = { top: window.pageYOffset + target.getBoundingClientRect().top, bottom: window.pageYOffset + target.getBoundingClientRect().bottom }, // Получаем позиции окна windowPosition = { top: window.pageYOffset, bottom: window.pageYOffset + document.documentElement.clientHeight }; if (targetPosition.bottom > windowPosition.top && // Если позиция нижней части элемента больше позиции верхней чайти окна, то элемент виден сверху targetPosition.top < windowPosition.bottom) { // Если позиция верхней части элемента меньше позиции нижней чайти окна, то элемент виден снизу // Если элемент полностью видно, то запускаем следующий код console.clear(); console.log('Вы видите элемент!'); } else { // Если элемент не видно, то запускаем этот код console.clear(); }; }; // Запускаем функцию при прокрутке страницы window.addEventListener('scroll', function() { Visible (element); }); У меня не получается решить две задачи, без которых скрип нельзя считать рабочим: 1.) Секции больше, чем одна. Не пойму как реализовать удобный способ перечисления требуемых секций и механизм активации таймера, если пользователь остановился на определенной секции. 2.) Самая большая проблема в том, что в поле зрения одного пользователя может попасть 2 или 3 секции одновременно. Пример: пользователю видна секция 1 на 100% и секция 2 на 20%. Или, пользователю видна секция 2 на 20%, секция 3 на 100% и секция 4 на 20%. Не пойму как активировать таймер для секции, которая больше всего в поле зрения пользователя, а при прокрутки на другую секцию перезапустить таймер. Пример: в поле зрения пользователя секция 1 на 100% и секция 2 на 20%. Для секции 1 таймер посчитал 14 секунд, но тут пользователь пролистал страницу и теперь получилось что секция 2 видна на 20%, секция 3 на 100% и секция 4 на 20%, а значит нужно обновить таймер и начать новый отчет для секции 3. Две проблемы связаны между собой и если первую проблему я могу решить самостоятельно с помощью определённых костылей, то связать их вместе у меня не получается на протяжении нескольких дней. Есть идеи по поводу решения данной задачи?


Ответы

Ответ 1



Вариант с использованием IntersectionObserver. Схема реализации следующая: выбираем список элементов по css селектору; создаём виртуальную таблицу секций, куда пишем id и видимую высоту; при изменении пересечения секции с областью видимости, переписываем видимую высоту элемента в таблице; по максимальному значению высот из таблицы определяем лидирующую секцию; если новая лидирующая секция отличается от предыдущей, то запускаем таймер; по окончанию отсчета таймера в демки выводится сообщение с id лидирующей секции. // Количество секунд, когда будет определен лидер. const REMAINING_TIME = 8; // Параметры для наблюдателя. const OPTIONS = { // Корневой элемент для пересечения видимости. // По умолчанию `viewport` документа. root: null, // Порог срабатывания наблюдателя за пересечением видимости. threshold: [0, .2, .4, .6, .8, 1] } // Выбираем секции, за которыми будем устанавливать наблюдения. const SECTIONS = document.querySelectorAll('.intersection'); // Секции, как участники детской игры "Царь горы". let conquerors = []; // Текущий победитель. let winner; // Идентификатор таймера. let timerId; // Счетчик для таймера. let remaining; function decrement() { // Очищаем предыдущий таймер, чтобы не было зацикливаний. clearTimeout(timerId); if (remaining > 0) { timerId = setTimeout(decrement, 1000); } else { alert(`Прошло ${REMAINING_TIME} сек просмотра. Победитель: + ${winner.id}`); } // Распечатка результатов для демки. printResult(); // Минусуем счетчик для таймера. remaining--; } // Обновление данных о секции в таблице секций. function update(id, ratio, height) { // Текущая секция из таблицы секций. let conqueror = conquerors.find(i => id === i.id); conqueror.ratio = ratio.toFixed(4); conqueror.height = height.toFixed(4); // Обновим данные о победителе. // Победитель определяется по // максимально видимой высоте элемента. winner = conquerors.reduce(function(prev, curr) { return parseFloat(curr.height) > parseFloat(prev.height) ? curr : prev; }); } // Функция обратного вызова для наблюдателя. Будет вызываться, когда // каждый элемент из набора `entries` // появляется и исчезает из области видимости. function computedTimeOut(entries, observer) { entries.forEach(function(entry, index) { // Элемент, видимость которого изменилась. let element = entry.target; // Коэффициент пересечения текущего элемента с `viewport`. let ratio = entry.intersectionRatio; // Видимая высота элемента. let height = entry.intersectionRect.height; // Сохраним `id` текущего победителя до того, как обновим таблицу. let winner_id = winner ? winner.id : winner; // Обновим данные о секции в таблице секций. update(element.id, ratio, height); // Если секция попала в поле видимости. if (entry.isIntersecting) { // Визуализируем стиль секции. visualize(element, ratio); // Если победитель еще не был определен либо он сменился. if (!winner_id || winner.id !== winner_id) { // Сбрасываем счетчик на значение, // заданное в качестве шаблонного. remaining = REMAINING_TIME; // Запускаем таймер. decrement(); } } }); } // Создаем экземпляр наблюдателя за пересечением видимости элементов. let visibleDetails = new IntersectionObserver(computedTimeOut, OPTIONS); // Перебираем все секции. SECTIONS.forEach(function(section, index) { // Устанавливаем наблюдателя за секцией. visibleDetails.observe(section); // Заполняем таблицу секций. // Используем атрибут `id` для идентификации элемента. conquerors.push({ id: section.id, ratio: 0, height: 0 }); // Для демки добавим разную высоту секциям: < 1440px. section.style.height = Math.random() * 1440 + 'px'; }) /** * Дальше идут константы и функции, которые используются для демки. */ // Спаны для вывода коэффициентов пересечения. const RATIO_SPANS = document.querySelectorAll('.intersection__ratio'); // Спан для вывода времени пересечения. const TIMING_SPAN = document.querySelector('#intersection__timing'); // Спан для вывода таблицы секций. const CONQUERORS_SPAN = document.querySelector('#intersection__conquerors'); // Спан для вывода победителя. const WINNER_SPAN = document.querySelector('#intersection__winner'); // Визуализация наблюдений. // Вызывается при изменении пересечений // областей видимости секции с `vieport`. function visualize(element, ratio) { let span = element.querySelector(".intersection__ratio"); span.textContent = element.id + ' ' + (Math.floor(ratio * 100)) + "%"; element.style.color = ratio > 0.5 ? '#fff' : '#bbb'; element.style.backgroundColor = `rgba(195, 195, 195, ${ratio})`; } // Распечатка результатов для демки. // Вызывается при изменении счетчика и таймера. // Все переменные глобальные. function printResult() { TIMING_SPAN.textContent = remaining > 0 ? remaining : 'Таймер остановлен.'; CONQUERORS_SPAN.textContent = JSON.stringify(conquerors, null, 2); WINNER_SPAN.textContent = JSON.stringify(winner, null, 2); } body { margin: 0; } #app { max-width: 280px; margin: 0 auto; } #intersection__timing { position: fixed; top: 8px; left: 16px; } #intersection__winner { position: fixed; bottom: 8px; left: 16px; color: red; font-size: 18px; } #intersection__conquerors { position: fixed; top: 8px; right: 16px; } .intersection { height: 220px; border: 1px solid #ccc; text-align: center; font-family: cursive; } .intersection__ratio { position: sticky; top: 0; }

  



Ответ 2



А что если считать сколько % поверхности какого элемента пользователь видел в сумме? например за 1 секунду просмотра 100% площади элемента для этого элемента счетчик увеличивается на 1, а если было видно только 20% элемента - то 0.2 let handle = s => { let b = s.getBoundingClientRect(); s.square = b.bottom > 0 ? (Math.min(b.bottom, window.innerHeight)-Math.max(0, b.top))/b.height : 0; s.style.background = `linear-gradient(to right, #d77 ${s.square*100}%, wheat ${s.square*100}%)` } let sections = [...document.querySelectorAll('section')] window.onscroll = e => sections.forEach(handle) window.onscroll() setInterval(i => { sections.forEach(s => s.textContent = +s.textContent + Math.max(0, s.square)*0.1) document.querySelector('span').textContent = sections.map(s => (+s.textContent).toFixed(1)) },100) section { width: calc(100vw - 35px); margin-bottom: 8px } span { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); border:solid; font-size:30px; padding:3px; }
Блок красится в зависимости от того на сколько % он виден, это для наглядности и отладки.. Можно еще для верности добавить порог, например процентов в 20... PS: решения с кучей тайм-аутов поддаются отладке чуть лучше чем никак, рекомендую до последнего избегать такого подхода..

Ответ 3



Можно реализовать через таймауты, задав переменную viewTimeouts, которая будет хранить таймаут для каждого элемента. Ну и задаем граничные условия. var elements = document.querySelectorAll('section'), viewTimeouts = {}, secondsLimit = 4, // сколько секунд ждем, чтобы среагировать на просмотр viewPercentLimit = 80; // Минимальный процент видимости элемента Также сделал метод VisibleElements, который будет бежать по всем элементам и сравнивать позицию. Его еще стоит запустить при старте, чтобы анализ был сразу после загрузки страницы а не только после скролла. var VisibleElements = function(elements){ for(var i=0; i windowPosition.top && // Если позиция нижней части элемента больше позиции верхней чайти окна, то элемент виден сверху targetPosition.top < windowPosition.bottom) { // Если позиция верхней части элемента меньше позиции нижней чайти окна, то элемент виден снизу // Если элемент полностью видно, то запускаем следующий код var targetSize = targetPosition.bottom - targetPosition.top, targetVisibleTop = targetPosition.top > windowPosition.top ? targetPosition.top : windowPosition.top, targetVisibleBottom = targetPosition.bottom < windowPosition.bottom ? targetPosition.bottom : windowPosition.bottom, viewSize = targetVisibleBottom - targetVisibleTop, percent = 100 * viewSize / targetSize; return percent > viewPercentLimit; } else { // Если элемент не видно, то запускаем этот код }; }; https://jsfiddle.net/skywave/6vacfyL0/75/

Комментариев нет:

Отправить комментарий