Список пользователей

1
Админ
Постов: 211
2
VIP
Постов: 72
3
Элита
Постов: 50
4
Проверенные
Постов: 35
5
VIP
Постов: 35
6
Проверенные
Постов: 32
7
Пользователи
Постов: 31
8
Проверенные
Постов: 29

  • Страница 1 из 1
  • 1
Отображения фотографий - новый функционал uCoz V8.0
Дата: Воскресенье, Вчера, 19:40 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

С самого начала меня не устраивала производительность старого скрипта. Он работал слишком медленно, фотографии отображались далеко не мгновенно, а счётчик вообще вёл себя нелогично: сначала показывал 0, и лишь после завершения полного подсчёта наконец-то реальное количество.

Такая логика была очень далека от той плавной и понятной механики, которую мы привыкли видеть ВКонтакте. С первых же дней я стремился максимально приблизиться именно к этому эталону поведения сделать всё так же удобно, быстро и предсказуемо.

Но, как это часто бывает, когда идеал недостижим в полной мере, приходится искать компромисс: если не получается реализовать задуманное на 100%, делаешь на максимум из того, что реально возможно в текущих условиях.

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



Разница между старым и новым (скриптами) довольно серьёзная. По сути новый скрипт , это оптимизированная версия старого, где убрал лишние механизмы и сделал мгновенный вывод миниатюр и счётчика.

Разберу по-человечески и по пунктам.

Главная идея нового скрипта

Новый скрипт работает по принципу:

1. Сначала мгновенно показывает кэш из localStorage

2. Параллельно тихо загружает свежие данные

3. Обновляет кэш, но интерфейс почти не дёргает

Поэтому фотографии и счётчик появляются сразу, без ожидания загрузки.

Как работал старый скрипт

Старый скрипт был более «осторожным».

Алгоритм примерно такой:

1. показать кэш

2. загрузить профиль

3. проверить изменился ли счётчик фото

4. если изменился загрузить альбом

5. только потом перерисовать DOM

То есть было больше проверок и блокировок.

Главное отличие - загрузка изображений

Старый скрипт

Загрузка была через предзагрузчик Image()

Код
const img = $('<img alt="Фото пользователя">').css({opacity:0});
const loader = new Image();
loader.onload = ()=>{
img.attr('src', item.src);
requestAnimationFrame(()=>img.css('opacity',1));
};
loader.src = item.src;


Что происходит:

1. создаётся скрытый

2. создаётся new Image()

3. картинка загружается в память

4. после загрузки показывается

Минусы

1. картинка появляется не сразу

2. DOM ждёт загрузки

3. лишняя память

4. больше кода

Новый скрипт

Теперь всё проще:

Код
const img = $('<img loading="lazy" alt="Фото пользователя">')
    .attr("src", p.src)
    .css({ opacity: 1, display: "block" });


Что происходит:

сразу получает src

браузер сам загружает

используется loading="lazy"

Плюсы

1. мгновенное появление

2. браузер сам оптимизирует

3. код проще

3. меньше CPU

Блокировка рендера

Старый скрипт

Есть защита:

Код
let renderLock = false;


и проверка:

Код
if (renderLock) return;


Это делалось чтобы DOM не перерисовывался несколько раз.

Новый скрипт

Этого механизма вообще нет.

Почему?

Потому что:

1. сначала выводится кэш

2. потом обновляется

3. перерисовка безопасна

4. Код стал проще.

Работа со счётчиком фото

Старый скрипт

Он проверял:

Код
const cachedCount = parseInt(localStorage.getItem(LS_KEY_COUNT)) || 0;
if(cachedCount === realCount) return;


Если число фотографий не изменилось, альбом вообще не загружался.

Плюс

экономия запросов.

Минус

иногда кэш мог устаревать.

Новый скрипт

Альбом загружается каждый раз:

Код
load();


Но UI уже показан из кэша.

Поэтому:

1. пользователь видит фотографии сразу

2. кэш обновляется тихо

Мгновенный показ счётчика

Новый скрипт отдельно достаёт число:

Код
const cachedCount = parseInt(localStorage.getItem(LS_COUNT));
counter.text(!isNaN(cachedCount) ? cachedCount : count);


Поэтому число фотографий появляется мгновенно.

В старом скрипте оно часто появлялось после загрузки профиля.

Упрощение кода

Новый скрипт:

1. меньше функций

2. меньше условий

3. меньше блокировок

4. меньше манипуляций DOM

Например:

старый

Код
showCacheOrEmpty()
loadProfileAndPhotos()
renderPhotos()
makeCounterClickable()
normalizeUrl()


новый

Код
showCache()
load()
render()
makeCounterLink()
normalize()


Логика стала прямее.

Убрал cache-buster

В старом скрипте:

Код
$.get(profileUrl+'?_='+Date.now())


Это делалось чтобы обойти кэш браузера.

В новом это убрал.

Причина данные всё равно обновляются через localStorage.

Lazy loading

Новый скрипт использует:

Код
loading="lazy"


Это значит:

1. браузер не грузит картинки сразу

2. только когда они появляются на экране

Это ускоряет страницу.

Итог

Новый скрипт делает три ключевые вещи:

1. Мгновенно показывает кэш

2. Сразу ставит миниатюры

3. Обновляет данные в фоне

Поэтому визуально кажется, что:

фотографии и счётчик появляются моментально.

Исходной код:

Код
<link rel="stylesheet" href="https://bro.usite.pro/css/vk-poss2.css" media="all">
<!-- Подключаем шрифт Roboto для аккуратного вида -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600&display=swap" rel="stylesheet">

<div id="user-photos">
<span id="vkPhotosCount"></span>
</div>

<script>
window.UCOZ_DATA = {
userId: "$USER_ID$",
profileUrl: "$PERSONAL_PAGE_LINK$"
};

// Сразу показываем кэшированное число
const cachedCount = parseInt(localStorage.getItem('userPhotosCount_$USER_ID$'));
if(!isNaN(cachedCount) && cachedCount > 0){
document.getElementById('vkPhotosCount').textContent = cachedCount;
}
</script>

<script src="https://bro.usite.pro/js/profile-photos2.js?v=1.0"></script>


Исключительно в ознакомительных целях.

Код
(function(){

function waitJQ(cb){
  if(typeof jQuery === "undefined"){
    setTimeout(()=>waitJQ(cb),100);
  }else{
    cb(jQuery);
  }
}

waitJQ(function($){

$(function(){

if(!window.UCOZ_DATA) return;

const userId = window.UCOZ_DATA.userId || "0";
const profileUrl = window.UCOZ_DATA.profileUrl || "";

if(userId==="0" || !profileUrl) return;

const maxPhotos = 4;

const container = $("#user-photos");
const counter = $("#vkPhotosCount");

if(!container.length) return;

const LS_PHOTOS = "userPhotos_"+userId;
const LS_COUNT  = "userPhotosCount_"+userId;
const LS_ALBUM  = "userPhotosAlbum_"+userId;

/* ---------------- utils ---------------- */

function normalize(url){
  try{
    const u = new URL(url,location.origin);
    u.search="";
    u.hash="";
    return u.toString().replace(/\/$/,"");
  }catch(e){
    return url;
  }
}

function makeCounterLink(album,count){
  if(!counter.length) return;

  if(count>0 && album){
    if(!counter.parent("a").length){
      counter.wrap('<a href="'+album+'" style="text-decoration:none;color:inherit;"></a>');
      counter.css("cursor","pointer");
    }
  }else{
    if(counter.parent("a").length){
      counter.unwrap();
      counter.css("cursor","default");
    }
  }
}

/* ---------------- render только миниатюры ---------------- */
function render(items, count, album){
    const grid = $('<div class="profile-photo-grid"></div>');

    if(items && items.length){
        items.slice(0, maxPhotos).forEach(p => {
            const card = $('<a class="photo-card"></a>')
                .attr("href", p.link)          // при клике идём на полноразмер
                .css({ display: "block", overflow: "hidden", borderRadius: "3px" });

            // здесь сразу ставим миниатюру, без Base64
            const img = $('<img loading="lazy" alt="Фото пользователя">')
                .attr("src", p.src)
                .css({ opacity: 1, display: "block" });

            card.append(img);
            grid.append(card);
        });

        container.empty().append(grid);

    } else {
        // пустая галерея
        container.html(`
          <div style="padding:24px 16px;text-align:center;color:#666;font-size:15px;
                      background:#fafafa;border-radius:10px;min-height:110px;
                      display:flex;flex-direction:column;justify-content:center;gap:10px;">
            <div style="font-size:1.1em;color:#555;font-weight:500;">Пока здесь нет фотографий</div>
            <div style="font-size:.93em;color:#777;line-height:1.45;">
              Добавьте свои снимки в<br>
              <a href="/photo/0-0-0-1-2" style="color:#3b82f6;text-decoration:none;font-weight:500;">
                Фотоальбом
              </a><br>
              и они красиво отобразятся в профиле
            </div>
          </div>
        `);
    }

    // счётчик
    if(counter.length){
        const cachedCount = parseInt(localStorage.getItem(LS_COUNT));
        counter.text(!isNaN(cachedCount) ? cachedCount : count);
    }

    makeCounterLink(album, count);
}

/* ---------------- showCache ---------------- */
function showCache(){
    try{
        const photos = JSON.parse(localStorage.getItem(LS_PHOTOS) || "[]");
        const cachedCount = parseInt(localStorage.getItem(LS_COUNT));
        const album = localStorage.getItem(LS_ALBUM) || null;

        if(counter.length && !isNaN(cachedCount) && cachedCount > 0){
            counter.text(cachedCount);
        }

        if(photos.length){
            render(photos, cachedCount, album);
        } else {
            render([], cachedCount || 0, null);
        }

    } catch(e){
        render([], 0, null);
    }
}

/* ---------------- load profile ---------------- */
function load(){

  $.get(profileUrl).done(function(html){

    const $page=$(html);

    let count=0;
    let album=null;

    $page.find("a").each(function(){
      const t=$(this).text().trim();
      const m=t.match(/^Фото\s*\((\d+)\)$/i);
      if(m){
        count=parseInt(m[1]);
        album=this.href;
        return false;
      }
    });

    if(!album || count===0){
      localStorage.setItem(LS_PHOTOS,"[]");
      localStorage.setItem(LS_COUNT,"0");
      localStorage.removeItem(LS_ALBUM);
      render([],0,null);
      return;
    }

    localStorage.setItem(LS_ALBUM,album);

    /* -------- album -------- */
    $.get(album).done(function(albumHtml){

      const $album=$(albumHtml);
      const items=[];

      $album.find("a.photo-card-title").each(function(){
        let link=$(this).attr("href");
        if(!link) return;
        if(link.startsWith("/")) link=location.origin+link;
        link=normalize(link);

        const img=$(this).closest(".entry-card").find("img[src*='/_ph/']").attr("src");
        if(!img) return;

        const src=normalize(img);
        items.push({src,link});
        if(items.length>=maxPhotos) return false;
      });

      // обновляем кэш и DOM
      localStorage.setItem(LS_PHOTOS,JSON.stringify(items));
      localStorage.setItem(LS_COUNT,count);

      render(items,count,album);

    });

  });

}

/* ---------------- start ---------------- */
showCache(); // сначала показываем кэш
load();      // подтягиваем свежие данные, но DOM уже не дергается

});
});

})();


Это видео демонстрирует реальную работу нового скрипта в действии.
Разница ощутимая: он почти полностью воспроизводит поведение сложных, отлаженных систем именно то, к чему я стремился с самого начала.

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
  • Страница 1 из 1
  • 1
Поиск: