Speed up the site. Fighting Google PageSpeed
The last notes
All English-language materials have been translated fully automatically using the Google service
This material is aggregate and written more for yourself. As I work and gain experience, I update it.
Use the described methods at your own risk.
Preload files and classes for php 7.4
In php 7.4, it became possible to use preloading of scripts and project files to permanently store them in memory. Let's take advantage of this in detail here
.htaccess for Bitrix
Set up compression and set caching time for resources. Detail here
Using preload
to speed up
Specifying the preload
specification allows the browser to set the correct priority and loading method according to the type of content being loaded. Plus, it allows you to pass one test 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>
Load scripts and styles asynchronously
<link rel="preload" as="style" href="async.css" onload="this.rel='stylesheet'"> //асинхронно загружаем css
<script async src="async.js"></script> //асинхронно загружаем js
Lazy loading scripts
Loading the script after initializing the page
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false; //Можем поставить async = true
document.body.append(script);
}
loadScript("lazy.js");
Preloading a deferred script
<link href="async.js" rel="preload" as="script" onload="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">
More detail
Lazy Loading GTM
Place the GTM
script in this construction
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);
Another option to postpone the download is to subscribe to the scroll event and call the script, only after its movement. Thanks for the method Igor Vorotnev
var fired = false;
window.addEventListener('scroll', () => {
if (fired === false) {
fired = true;
setTimeout(() => {
// Здесь все эти тормознутые трекеры, чаты и прочая ересь,
// без которой жить не может отдел маркетинга, и которые
// дико бесят разработчиков, когда тот же маркетинг приходит
// с вопросом "почему сайт медленно грузится, нам гугл сказал"
}, 1000)
}
});
Lazy Loading 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();}
})();
Lazy Loading vk.com
It is enough to add the 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>
Lazy loading images
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);
A separate more complex version javascript lazy-load
Lazy Loading 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);
Delayed loading Yandex maps
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);
});
Lazy Loading youtube
Instead of iframe
, we use our own 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);}
Optimizing images
Cutting unnecessarily large images with CFile :: ResizeImageGet
. In detail here p>
We translate all images on the site into the webP
format. Details here
Fixing Set to show all text when loading web fonts
In all font-face
calls add the font-display
property, like this:
@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;
}
A faster way to get 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']);
}
Thanks for the hint Olegpro
Query Optimization
All requests should ditch GetNext ()
and GetNextElement ()
in favor of Fetch ()
. Using GetNext ()
is only justified if you need to get DETAIL_PAGE_URL
or SECTION_PAGE_URL
When filtering by a property of the list type, you should abandon the PROPERTY_VALUE
construction in favor of filtering by the ID
property
When filtering HL
blocks by exact match - you should explicitly specify =
in the property
Quick retrieval of the property value of an infoblock element by property id
$el = \ElementPropertyTable::getList([
'filter'=>['IBLOCK_ELEMENT_ID'=>$arParams['ELEMENT_ID'], 'IBLOCK_PROPERTY_ID'=>PROPERTY_ID], //where PROPERTY_ID is the id of the infoblock property
'select'=>['VALUE']
])->fetch();
Offloading big data
$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;
}
}
Comments