Передо мной возникла одна задачка - повысить Google Speed на одном из интернет-ресурсов.
Задачка ещё та, учитывая то, что большинство пунктов выполнено, но при этом просадка капитальная. А всё из-за чего? Куча метрик, яндекс информеры (оцени Я.Маркет, рейтинг Я.Маркет) и... Jivosite.
Просадка -14 в мобильном и -8 на десктоп. Эти показатели могут варьироваться в зависимости от времени блокировки потока.
В сети нашел код отложенной загрузки Jivosite, но при первом взгляде он показался мне не надежным, т.к. во первых, уменьшал просадку не всю, а во вторых, при загрузке отключал и инициализировал заново некоторые JS события Jivosite. А это чревато, при обновлении движка чата.
Я пошел иным путём. Решил ПОЛНОСТЬЮ убрать влияние Jivosite на Google Speed. Каким образом?
Убрать влияние чата на показатели можно лишь в одном случае - не загружая его вовсе. (Ахах, классный выход из ситуации :D). Не-не, слушай дальше.
Инициализируем Jivosite при первом визите на сайт, ТОЛЬКО после того, как пользователь куда-нибудь нажмет, или проскроллит страницу, либо подвигает мышью.
Логично? Ведь Google Speed совершенно бездействует на сайте. Таким образом для него чата не будет вовсе, а для пользователя в первый визит он появится после какого-либо действия. При последующих же визитах всё стандартно.
И так, релиз в пост!
Вначале проверяем в сессии существование ключа, который свидетельствует о том, что ранее отложенная по действию пользователя загрузка Jivosite уже осуществлялась, а значит загружаем в обычном режиме.
Я сделал именно сессией т.к. на больших проектах желательно на куках экономить, дабы не вывалило кому-то ошибку 500 из-за большого количества кук.
Но, если Вы владелец сайта не на фреймворке с единой точкой входа, то в этом случае целесообразнее использовать куки.
<?php if ( !isset($_SESSION['jivoLazy']) ) { ?>
<?php $_SESSION['jivoLazy'] = 'ready'; ?>
<!-- Здесь будет новый способ загрузки, отложенный -->
<?php } else { ?>
<!-- Сюда впишем обычный способ загрузки Jivosite -->
<?php } ?>
Уже прилично давно Jivosite упростила код инициализации своего чата. Обычный инициализатор выглядит вот так:
<script src="//code.jivosite.com/widget.js" jv-id="#widgetID#" async></script>
Я не приверженец какой-либо кастомизации стороннего кода, но именно один раз нам придется его загрузить старым способом, который раньше предлагал Jivo. Это связано с тем, что как я не крутил с новой версией инициализатора, при отложенной загрузке он не видит ID виджета.
По этому вот старый инициализатор Jivosite
(function(){ var widget_id = '#widgetId#';var d=document;var w=window;function l(){
var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '//code.jivosite.com/script/widget/'+widget_id; var ss = document.getElementsByTagName('script')[0]; ss.parentNode.insertBefore(s, ss);}if(d.readyState=='complete'){l();}else{if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
Теперь напишем просто JS функцию, которая будет инициализировать чат при отложенной загрузке.
function jivoAsync()
{
if ( typeof window.jivoLazyReady === 'undefined' )
{
window.jivoLazyReady = true;
window.jivoLazyTimeout = setTimeout(function()
{
// Сюда вставляем старый способ инициализации Jivo
// Я его привёл выше
console.log('jivosite load from lazy');
clearTimeout(window.jivoLazyTimeout);
},
1000);
}
}
В данной функции мы проверяем был ли уже ранее загружен чат, если нет, то загружаем.
Далее инициализируем функцию jivoAsync()
В данном инициализаторе мы слушаем события Движение мыши, Клик, Скроллинг страницы. И инициализируем написанную нами функцию jivoAsync.
['mouseover','click','scroll'].forEach(function(event)
{
var elm = event == 'click' ? document.getElementsByTagName('body')[0] : window;
if ( typeof window.addEventListener === 'undefined' ) {
elm.addEvent(event, jivoAsync);
} else {
elm.addEventListener(event, jivoAsync, false);
}
});
Данный скрипт инициализирует чат только 1 раз. Ведь кликов, движений мыши, скроллинга может быть много, но функция контролирует этот момент.
В мобильном срабатывают события Клик куда угодно и скроллинг страницы.
Таким образом Jivosite загрузится через секунду после того, как пользователь первый раз вошел на сайт и что-либо сделал на нём. Согласитесь, чат не очень-то и нужен прямо сразу. Ну а при сёрфинге Jivosite загрузится уже стандартно.
Полный код примера
<?php if ( !isset($_SESSION['jivoLazy']) ) { ?>
<?php $_SESSION['jivoLazy'] = 'ready'; ?>
<script type="text/javascript">
function jivoAsync()
{
if ( typeof window.jivoLazyReady === 'undefined' )
{
window.jivoLazyReady = true;
window.jivoLazyTimeout = setTimeout(function()
{
(function(){ var widget_id = '#widgetID#';var d=document;var w=window;function l(){
var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '//code.jivosite.com/script/widget/'+widget_id; var ss = document.getElementsByTagName('script')[0]; ss.parentNode.insertBefore(s, ss);}if(d.readyState=='complete'){l();}else{if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
console.log('jivosite load from lazy');
clearTimeout(window.jivoLazyTimeout);
},
1000);
}
}
['mouseover','click','scroll'].forEach(function(event)
{
var elm = event == 'click' ? document.getElementsByTagName('body')[0] : window;
if ( typeof window.addEventListener === 'undefined' ) {
elm.addEvent(event, jivoAsync);
} else {
elm.addEventListener(event, jivoAsync, false);
}
});
</script>
<?php } else { ?>
<script src="//code.jivosite.com/widget.js" jv-id="#widjetID#" async></script>
<?php } ?>
Поздравляю, Google Speed добавил +10-15 пунктов на Вашем проекте!
Не забудьте в двух местах #widgetID# изменить на ID виджета Jivosite.
Всем пока, всем мира.
Комментарии (39)
AlexeyCaTHaR
23.11.2021 12:55+1Сейчас бы для каждой строчки писать
<?php .... ?>
И мои супер любимые конструкции вида
<?php } ?>
Что же вы так не любите всех, кто после вас будет работать с кодом?
webSasha1990 Автор
23.11.2021 15:02Просто внутри будет Javascript) мне показалось так читабельнее написать.
mobi
23.11.2021 13:01+2- А зачем делать
clearTimeout
в обработчикеsetTimeout
? - Зачем вообще ради какой-то пузомерки заставлять пользователей страдать еще дольше, так как теперь мало дождаться загрузки страницы, она ведь еще будет "лагать" какое-то время (те самые 1,46 секунды из отчета PSI/Lighthouse) после первого скролла (причем не сразу, а через 1 секунду, см.
setTimeout
).
webSasha1990 Автор
23.11.2021 14:57clearTimeout - очищаю инициализатор. А так можно и не чистить конечно.
А по поводу страданий пользователя - тут конечно каждому своё). На моём проекте приоритет отдается скорости Google Speed. Ну а так кому как конечно. Если важно прогружать чат сазу - то так не пойдет.
setTimeout можно попробовать убрать, я его сделал чтоб 100500 скролла не было, возможно лишнее.
mobi
23.11.2021 19:58+2Вы, наверное, путаете
setTimeout
иsetInterval
, т.к. первый никогда не будет вызван дважды (поэтому результатsetTimeout
нужно сохранять только в одном случае: если нужно будет отменить таймер до того, как он будет вызван). Если же цель в том, чтобы удалить переменную, которую непонятно зачем создали в объекте window, то для этого есть операторdelete
. Как-то так.
- А зачем делать
Druj
23.11.2021 15:04Автор изобрёл клоакинг, поздравляю.
webSasha1990 Автор
23.11.2021 15:13Он ведь не вредит никому, при том один раз в сессию. Это мизер неудобств.
OlegIva
26.11.2021 11:19Он ведь не вредит никому
Мне, например, такая подлянка с чатом вредит, вплоть до отвращения к сайту и ухода с него. Следовательно, ваше утверждение ложно. Но 10
долларовпопугаев Google speed - это 10 попугаев...webSasha1990 Автор
26.11.2021 18:17Вы же бездействуете на сайте пару секунд. Если не знать о lazy чата, то разница на мой взгляд несущественна. Наоборот, вы зашли и ничего не выскакивает, поскролили - высочило. При том такая прогрузка лишь при первом визите.
Но кому как. Я не настаиваю на абсолютной целесообразности способа. Просто когда мобила всего 20, 10 попугаев уже играет роль, когда заказчику это важно.
OlegIva
26.11.2021 18:36зашли и ничего не выскакивает, поскролили - высочило
Вот это и убивает напрочь.
Acsac
23.11.2021 17:07Pagespeed это просто тест. Данные о скорости сайта есть синтетические и реальные полевые. Поисковик собирает данные по реальным. Об этом написано в описании Pagespeed например. Если есть субъективные факторы понижения баллов именно в тесте, стоит ли так гнаться за синтетикой? Всё равно скорость будет фиксироваться по Network браузера каждого посетителя, а у каждого своё железо и свой канал. Может стоит таки читать описание теста?)
webSasha1990 Автор
23.11.2021 18:01+1Не всегда можно не делать то, что просят сделать). У меня была конкретная задача, подтянуть Google Speed.
Мы вертелись как могли XD)
Acsac
23.11.2021 20:04И продолжаете писать тут что Pagespeed так важен. Надо объяснять же тем кто не понимает, что Pagespeed просто образец синтетик проверки, которая не зависит зачастую от скорости сайта как её измеряют поисковики. Люди понятно что им проще глянуть 1 тест цифры, чем хотя бы прочесть описание теста, но надо объяснять, а не культивировать дальше мифы.
webSasha1990 Автор
23.11.2021 20:32Я в первую очередь написал это для тех, кому это нужно. Даже на сайте Jivosite описана данная проблема. У меня она также возникла, нашел решение. А в СЕО я слаб, не могу проанализировать с этой стороны. Была задача. Вот решение. Плохое или хорошее решать вам. На проекте всех устроило. Поэтом решил поделиться здесь.
Acsac
23.11.2021 20:41Это не SEO - это описание теста Pagespeed. Полевое и синтетическое исследование. Бьётесь за то, за что бороться не стоит. Ибо вместо цифр синтетики в тесте смотреть надо Network - чтобы понимать тормозит Jivosite сайт или нет.
webSasha1990 Автор
23.11.2021 20:48Вы предлагаете в этом случае не делать задачу?) я и не говорил про тормоза сайта, я говорил про проседания г. спид. Может еще кому-то надо за счет jivo её поднять. Если не надо, то и не надо. Чат для пользователя не влияет на скорость погрузки информации.
Acsac
23.11.2021 21:07Вот стоило бы замерить скорость именно, а не баллы за синтетик оценку, показать заказчику, дать почитать описание Pagespeed - документация. То что вы сделали хорошо, но бесполезно в плане скорости.
YChebotaev
23.11.2021 20:18+4clearTimeout(window.jivoLazyTimeout);
Лишняя конструкция
document.getElementsByTagName('body')[0]
Можно просто `document.body`
if ( typeof window.addEventListener === 'undefined' ) { elm.addEvent(event, jivoAsync); } else { elm.addEventListener(event, jivoAsync, false); }
IE8 поддерживаете?
В этом же фрагменте не увидел как вы снимаете листенер. Получается, он у вас на каждый скролл, клик и движение мышкой отрабатывает, но потом выходит из функции
jivoAsync
на строчке `if ( typeof window.jivoLazyReady === 'undefined' )`. Не идеально.Лучше так (с учетом предыдущего замечания):
var eventTypes = ['mouseover', 'click', 'scroll'] function listener() { var target = this jivoAsync() eventTypes.forEach(function(eventType) { target.removeEventListener(eventType, listener) }) } events.forEach(function(eventType) { var elm = event === 'click' ? document.body : window elm.addEventListener(eventType, listener) })
Теперь, благодаря тому что `jivoAsync` запускается только один раз, удастся избавиться от глобальных переменных и проверок внутри.
Также, поскольку код виджета был предназначен для использования внутри `<script>`, оборачивать код инициализации виджета в функцию нет смысла.
function jivoAsync() { setTimeout(function() { var widget_id = '#widgetID#'; var d = document; var w = window; function l() { var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '//code.jivosite.com/script/widget/' + widget_id; var ss = document.getElementsByTagName('script')[0]; ss.parentNode.insertBefore(s, ss); } if (d.readyState == 'complete') { l(); } else { if (w.attachEvent) { w.attachEvent('onload', l); } else { w.addEventListener('load', l, false); } } }, 1000) }
Поскольку у нас на момент таймаута страница уже загрузилась, то код инициализации виджета можно сильно сократить. Также, нет никакого смысла выискивать первый скрипт на странице, можно просто вставить в конец
body
:function jivoAsync() { setTimeout(function() { var widget_id = '#widgetID#'; var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '//code.jivosite.com/script/widget/' + widget_id; document.body.appendChild(s) }, 1000) }
Это то что в глаза бросилось
webSasha1990 Автор
23.11.2021 20:55Но, позвольте, разве removeEventListener не удалит чужие экшны? Это здесь код чистый. А на реальном проекте 100500 скриптов. Т.к. window, body общие для всех элементы, разве это безопасно?
zkrvndm
24.11.2021 10:01+1Пффф, с таким же успехом я могу вообще не грузить ничего кроме ключей и текста, а затем уже как будет клик или прокрутка подгружать реальную страницу со всеми картинками и структурой.
kvadrokot
24.11.2021 12:34+1а раскажите поподробнее про "Я сделал именно сессией т.к. на больших проектах желательно на куках экономить, дабы не вывалило кому-то ошибку 500 из-за большого количества кук."
как 500ая ошибка может появиться из-за большого кол-ва кук?
webSasha1990 Автор
24.11.2021 12:35-1Если на сайте много кук, и их общий вес слишком большой, то да, в этом случае выскакивает у пользователя 500.
kvadrokot
24.11.2021 17:02+1куки не хранятся "на сайте", куки хранятся только в браузере пользователя
webSasha1990 Автор
24.11.2021 20:26-1Куки отправляет из браузера на сервер. Если их вес слишком велик, сервер выдает 500
kvadrokot
25.11.2021 11:31+1а насколько "большие" должны быть куки, чтобы сервер выдал 500?
в вашем случае достаточно было добавить 1 куку -
jivoLazy=ready
webSasha1990 Автор
25.11.2021 12:28-1Кука да куки, 2 куки) дело не в том, что она маленькая, дело в экономии. На боевом сайте с метриками и прочим, очень много кук. И экономия, как по мне, не лишнее.
Чтобы выдало 500 зависит от настроек сервера. По-моему размер буфера для клиента. Возможно ошибаюсь, но чаще всего он 8кб-16кб, можно ручками расширить, если позволяет хостинг.
kvadrokot
25.11.2021 13:48+1ни разу не встречал такого размера кук
и как-то странно "экономит"ь 16 байт из 8кбайт, храня что-то дополнительно в сессии (а она как раз на сервере хранится)
wadowad
24.11.2021 17:29Я бы стал грузить код не сразу при скролле, а по завершении первого скролла при остановке. Тогда, для пользователя по ощущениям "тормозов" должно быть меньше.
P.S. И сюда бы ещё завернуть всякие всякие метрики и аналитиксы. Только наверно поведенческие факторы у сайта понизятся.
webSasha1990 Автор
24.11.2021 17:31По сути setTimeout в 1с и говорит о завершении скролла.
По метрикам. аналитике - да, точность упадет.
namikiri
Google Speed это, конечно, мило и здорово, но грошь цена вашим разработкам, если этот треклятый живосайт специально дожидается клика пользователя по странице, чтобы не просто ВНЕЗАПНО выскочить поверх всего, но ещё и издать звук.
webSasha1990 Автор
Ну он же и так выскакивает). Тут вопрос в приоритетах на конкретном проекте. У нас поставлен вот такой приоритет. Что лучше пользователю 1 раз в сессии пострадать, чем просадка -10)
namikiri
Лучше убрать или спрятать его до востребования самим пользователем.