Ускорение работы сайта. Боремся с Google PageSpeed
Последние записи
Данный материал является агрегативным и написан более для себя. По мере работы и накопления опыта обновляю его.
Используйте описанные методы на свой страх и риск.
Прелоад файлов и классов на php 7.4
В php 7.4 появилась возможность использовать предзагрузку скриптов и файлов проекта для постоянного хранения их в памяти. Воспользуемся этим, подробно здесь
.htaccess для Битрикс
Настраиваем сжатие и устанавливаем время кеширования для ресурсов. Подробно здесь
Использование preload
для ускорения
Указание спецификации preload
позволяет браузеру установить правильный приоритет и способ загрузки в соответствии с типом загружаемого контента. Плюс, это позволяет пройти один тест Google Page Speed
<link rel="preload" href="/js/script.js" as="script">
<link rel="preload" href="/fonts/font.woff2" as="font" type="font/woff2" crossorigin>
Асинхронная загрузка скриптов и стилей
<link rel="preload" as="style" href="async.css" onload="this.rel='stylesheet'"> //асинхронно загружаем css
<script async src="async.js"></script> //асинхронно загружаем js
Отложенная загрузка скриптов
Выполняем загрузку скрипта после инициализации страницы
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false; //Можем поставить async = true
document.body.append(script);
}
loadScript("lazy.js");
Предварительная загрузка скрипта с отсроченным выполнением
<link href="async.js" rel="preload" as="script" onload="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">
Более подробно
Отложенная загрузка GTM
Помещаем скрипт GTM
в данную конструкцию
function goodbyeGTM() {
(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','YOUR_WIDJET_ID');
}
setTimeout(goodbyeGTM, 1000);
Еще один вариант откладывания загрузки - подписка на событие скролла и вызов скрипта, только после его движения. За способ спасибо Игорю Воротнёву
var fired = false;
window.addEventListener('scroll', () => {
if (fired === false) {
fired = true;
setTimeout(() => {
// Здесь все эти тормознутые трекеры, чаты и прочая ересь,
// без которой жить не может отдел маркетинга, и которые
// дико бесят разработчиков, когда тот же маркетинг приходит
// с вопросом "почему сайт медленно грузится, нам гугл сказал"
}, 1000)
}
});
Отложенная загрузка jivosite
(function(){ document.jivositeloaded=0;var widget_id = 'ВАШ_ВИДЖЕТ_ID';var d=document;var w=window;function l(){var s = d.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);}//эта строка обычная для кода JivoSite
function zy(){
//удаляем EventListeners
if(w.detachEvent){//поддержка IE8
w.detachEvent('onscroll',zy);
w.detachEvent('onmousemove',zy);
w.detachEvent('ontouchmove',zy);
w.detachEvent('onresize',zy);
}else {
w.removeEventListener("scroll", zy, false);
w.removeEventListener("mousemove", zy, false);
w.removeEventListener("touchmove", zy, false);
w.removeEventListener("resize", zy, false);
}
//запускаем функцию загрузки JivoSite
if(d.readyState=='complete'){l();}else{if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}
//Устанавливаем куку по которой отличаем первый и второй хит
var cookie_date = new Date ( );
cookie_date.setTime ( cookie_date.getTime()+60*60*28*1000); //24 часа для Москвы
d.cookie = "JivoSiteLoaded=1;path=/;expires=" + cookie_date.toGMTString();
}
if (d.cookie.search ( 'JivoSiteLoaded' )<0){//проверяем, первый ли это визит на наш сайт, если да, то назначаем EventListeners на события прокрутки, изменения размера окна браузера и скроллинга на ПК и мобильных устройствах, для отложенной загрузке JivoSite.
if(w.attachEvent){// поддержка IE8
w.attachEvent('onscroll',zy);
w.attachEvent('onmousemove',zy);
w.attachEvent('ontouchmove',zy);
w.attachEvent('onresize',zy);
}else {
w.addEventListener("scroll", zy, {capture: false, passive: true});
w.addEventListener("mousemove", zy, {capture: false, passive: true});
w.addEventListener("touchmove", zy, {capture: false, passive: true});
w.addEventListener("resize", zy, {capture: false, passive: true});
}
}else {zy();}
})();
Отложенная загрузка vk.com
Достаточно добавить атрибут async
<script type="text/javascript" async>!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://vk.com/js/api/openapi.js?162",t.onload=function(){VK.Retargeting.Init("ВАШ_ВИДЖЕТ_ID"),VK.Retargeting.Hit()},document.head.appendChild(t)}();</script><noscript><img src="https://vk.com/rtrg?p=ВАШ_ВИДЖЕТ_ID" style="position:fixed; left:-999px;" alt=""/></noscript>
Отложенная загрузка изображений
document.addEventListener('DOMContentLoaded', function(){
//Блок замены src изображений для товаров в галерее
if(document.querySelector( 'js-zoom__pic' )){
let zoomPic = document.querySelectorAll('js-zoom__pic');
for (let i = 0; i < zoomPic.length; i++){
if(zoomPic[i].getAttribute('data-src')){
zoomPic[i].src = zoomPic[i].getAttribute('data-src');
}
}
}
}, false);
Отдельный более сложный вариант javascript lazy-load
Отложенная загрузка iframe
document.addEventListener('DOMContentLoaded', function(){
if(document.querySelector( 'js-lazy__iframe' )){
let lazyIframe = document.querySelectorAll('js-lazy__iframe');
for (let i = 0; i < lazyIframe.length; i++){
if(lazyIframe[i].getAttribute('data-src')){
lazyIframe[i].src = lazyIframe[i].getAttribute('js-lazy__iframe');
}
}
}
}, false);
Отложенная загрузка Яндекс карт
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false; //Можем поставить async = true
document.body.append(script);
}
function mapLoad() {
if (ymaps) {
let spbMap;
let moscowMap;
function init() {
spbMap = new ymaps.Map('map_spb', {
center: [59, 30],
zoom: 15,
}, {
searchControlProvider: 'yandex#search',
});
moscowMap = new ymaps.Map('map_moscow', {
center: [55, 37],
zoom: 15,
}, {
searchControlProvider: 'yandex#search',
});
spbMap.geoObjects.add(new ymaps.Placemark([59.924812, 30.303085]));
moscowMap.geoObjects.add(new ymaps.Placemark([55.675352, 37.470662]));
}
window.addEventListener('resizeYandexMap', function (e) {
moscowMap.container.fitToViewport();
spbMap.container.fitToViewport();
});
ymaps.ready(init);
}
}
document.addEventListener("DOMContentLoaded", () => {
loadScript('https://api-maps.yandex.ru/2.1/?lang=ru_RU&amp;apikey=API_KEY');
setTimeout(mapLoad, 2000);
});
Отложенная загрузка youtube
Вместо iframe
используем свой div
<div class="youtube" id="8XLWGW109876" style="width:100%;height:100%;"></div>
js
document.addEventListener('DOMContentLoaded', function(){
/* Блок замены youtube video */
var videos = document.getElementsByClassName("youtube");
var nb_videos = videos.length;
for (var i=0; i<nb_videos; i++) {
// Based on the YouTube ID, we can easily find the thumbnail image
videos[i].style.backgroundImage = 'url(http://i.ytimg.com/vi/' + videos[i].id + '/maxresdefault.jpg)';
// Overlay the Play icon to make it look like a video player
var play = document.createElement("div");
play.setAttribute("class","play");
videos[i].appendChild(play);
videos[i].onclick = function() {
// Create an iFrame with autoplay set to true
var iframe = document.createElement("iframe");
var iframe_url = "https://www.youtube.com/embed/" + this.id + "?autoplay=1&autohide=1&enablejsapi=1";
if (this.getAttribute("data-params")) iframe_url+='&'+this.getAttribute("data-params");
iframe.setAttribute("src",iframe_url);
iframe.setAttribute("frameborder",'0');
// The height and width of the iFrame should be the same as parent
iframe.style.width = this.style.width;
iframe.style.height = this.style.height;
// Replace the YouTube thumbnail with YouTube Player
this.parentNode.replaceChild(iframe, this);
}
}
}, false);
css
.youtube {background-position: center;background-repeat: no-repeat;position: relative;display: block;overflow: hidden;transition: all 200ms ease-out;cursor: pointer;}
.youtube .play {background-image: url('/upload/youtube.svg');background-position: center center;background-repeat: no-repeat;background-size: 64px 64px;position: absolute;height: 100%;width: 100%;opacity: .8;filter: alpha(opacity=80);-webkit-transition: all 0.2s ease-out;-moz-transition: all 0.2s ease-out;-o-transition: all 0.2s ease-out;transition: all 0.2s ease-out;}
.youtube .play:hover {opacity: 1;filter: alpha(opacity=100);}
Оптимизируем изображения
Режем излишне большие изображения с помощью CFile::ResizeImageGet
. Подробно здесь
Переводим все изображения на сайте в формат webP
. Подробно здесь
Исправляем Настройте показ всего текста во время загрузки веб-шрифтов
Во все вызовы font-face
добавьте свойство font-display
, например так:
@font-face {
font-family: Roboto;
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmWUlfCRc9.ttf) format('truetype');
font-display: swap;
}
Более быстрый способ получения SECTION_PAGE_URL
// этой функцией будем стоить урл
function makeUrl($parentId = 0){
global $sections;
$sefFolder = '/katalog/';
$codes = array();
while (isset($parentId)) {
$codes[] = $sections[$parentId]['CODE'];
if(isset($sections[$parentId])){
$parentId = $sections[$parentId]['IBLOCK_SECTION_ID'];
}else{
$parentId = null;
}
}
if (sizeof($codes)) {
return $sefFolder . implode('/', array_reverse($codes));
} else {
return null;
}
}
$sections = array();
$resCatalog = CIBlockSection::GetList(
array(
'LEFT_MARGIN' => 'ASC'
),
array(
'IBLOCK_ID' => $arParams['IBLOCK_ID'],
'ACTIVE' => 'Y',
'GLOBAL_ACTIVE' => 'Y',
),
false,
array(
'IBLOCK_ID',
'IBLOCK_SECTION_ID',
'NAME',
'SECTION_PAGE_URL',
'UF_*'
)
);
while($arCatalog = $resCatalog->Fetch()){
$sections[$arCatalog['ID']] = $arCatalog;
}
foreach($sections as &$section){
$section['SECTION_PAGE_URL'] = makeUrl($section['ID']);
}
За подсказку спасибо Olegpro
Оптимизация запросов
Во всех запросах следует отказаться от GetNext()
и GetNextElement()
в пользу Fetch()
. Использование GetNext()
оправдано только если нужно получить DETAIL_PAGE_URL
или SECTION_PAGE_URL
При фильтрации по свойству типа список следует отказаться от конструкции PROPERTY_VALUE
в пользу фильтрации по ID
свойства
При фильтрации HL
блоков по точному соответствию - следует явно указывать =
в свойстве
Быстрое получение значения свойства элемента инфоблока по id свойства
$el = \ElementPropertyTable::getList([
'filter'=>['IBLOCK_ELEMENT_ID'=>$arParams['ELEMENT_ID'], 'IBLOCK_PROPERTY_ID'=>PROPERTY_ID], //где PROPERTY_ID это id свойства инфоблока
'select'=>['VALUE']
])->fetch();
Выгрузка больших данных
$tokenID = 'XXXXXXXXXXXXXXXXXXXXX';
$host = 'XXXX.bitrix24.ru';
$user = 1;
/**
* Начинаем с нуля или с какого то предыдущего шага
*/
$leadID = 0;
$finish = false;
while (!$finish)
{
/**
* Выполняем пока не заберем все данные, в этом случае не стоит забывать и про задержку между хитами.
* Либо каждый раз выбираем только 50, начиная с того элемента, на котором остановилась прошлая итерация
*/
$http = new \Bitrix\Main\Web\HttpClient();
$http->setTimeout(5);
$http->setStreamTimeout(50);
$json = $http->post(
'https://'.$host.'/rest/'.$user.'/'.$tokenID.'/crm.lead.list/',
[
'order' => ['ID' => 'ASC'],
'filter' => ['>ID' => $leadID],
'select' => ['ID', 'TITLE', 'DATE_CREATE'],
'start' => -1
]
);
$result = \Bitrix\Main\Web\Json::decode($json);
if (count($result['result']) > 0)
{
foreach ($result['result'] as $lead)
{
$leadID = $lead['ID'];
}
}
else
{
$finish = true;
}
}
Комментарии