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

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

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

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

Логика была полностью переработана. Я пришёл к выводу, что нет необходимости использовать кэш с фиксированным сроком действия. Фактически, данные должны кэшироваться постоянно, без привязки ко времени.

Механизм обновления теперь строится на количестве фотографий: скрипт ориентируется на число изображений в профиле. Если добавляется новая фотография и счётчик увеличивается, скрипт автоматически обновляет кэш, сохраняя актуальные данные в локальной памяти. Все эти процессы выполняются в фоне, незаметно для пользователя.

После проверки новая версия скрипта работает корректно: обновления применяются мгновенно при добавлении новых фотографий, все ранее существовавшие ошибки устранены, и скрипт функционирует стабильно и надёжно.



Скрипт +CSS

Код
<style>
/* Сетка фото */
.profile-photo-grid {
display: flex;
gap: 10px;
flex-wrap: wrap;
}

/* Карточка фото */
.profile-photo-grid .photo-card {
width: 130px;
height: 130px;
border-radius: 2px;
overflow: hidden;
background: #e5ebf1;
box-shadow:
0 1px 0 rgba(0,0,0,0.08),
0 2px 6px rgba(0,0,0,0.12);
transition:
transform 0.18s ease,
box-shadow 0.18s ease;
position: relative;
}

/* Ховер как в ВК */
.profile-photo-grid .photo-card:hover {
transform: translateY(5px);
box-shadow:
0 4px 14px rgba(0,0,0,0.18);
}

/* Картинка */
.profile-photo-grid .photo-card img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.25s ease;
}

/* Лёгкий зум */
.profile-photo-grid .photo-card:hover img {
transform: scale(1.01);
}

/* Синяя обводка при наведении */
.profile-photo-grid .photo-card::after {
content: "";
position: absolute;
inset: 0;
border-radius: 2px;
border: 1px solid transparent;
transition: border-color 0.18s ease;
pointer-events: none;
}

.profile-photo-grid .photo-card:hover::after {
border-color: #c0d3fc; /* VK blue */
}

/* Мелкая оптимизация под retina */
@media (max-width: 480px) {
.profile-photo-grid .photo-card {
width: 100px;
height: 90px;
}
}
</style>

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

<script>
(function($){
$(function(){

if (typeof $ === 'undefined') return;

const userId = "$USER_ID$" || '0';
const profileUrl = "$PERSONAL_PAGE_LINK$";
if (!userId || userId === '0') return;

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

const LS_KEY_PHOTOS = 'user_photos_cache_' + userId;
const LS_KEY_COUNT  = 'user_photos_count_' + userId;

let renderLock = false;

/* ================= НОРМАЛИЗАЦИЯ ================= */
function normalizeUrl(url){
try{
  const u = new URL(url, location.origin);
  u.search = '';
  u.hash = '';
  return u.toString().replace(/\/$/, '');
}catch(e){
  return url;
}
}

/* ================= РЕНДЕР ================= */
function renderPhotos(items){

if (renderLock) return;
renderLock = true;

/* ===== НЕТ ФОТО ===== */
if (!items || !items.length){
  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) counter.text('0');
  renderLock = false;
  return;
}

/* ===== ЕСЛИ ЕСТЬ ФОТО ===== */
const grid = $('<div class="profile-photo-grid"></div>');

items.slice(0, maxPhotos).forEach(item=>{
  const card = $('<div class="photo-card"></div>');

  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;

  card.on('click',()=>{ if(item.link) location.href=item.link; });
  card.append(img);
  grid.append(card);
});

container.empty().append(grid);
if (counter.length) counter.text(items.length);

renderLock = false;
}

/* ================= ПОКАЗ КЭША ================= */
try{
const cachedPhotos = localStorage.getItem(LS_KEY_PHOTOS);
if (cachedPhotos){
  const arr = JSON.parse(cachedPhotos);
  if (Array.isArray(arr)) renderPhotos(arr);
}

const cachedCount = localStorage.getItem(LS_KEY_COUNT);
if (counter.length && cachedCount !== null) counter.text(Number(cachedCount));

}catch(e){}

/* ================= ЗАГРУЗКА РЕАЛЬНЫХ ДАННЫХ ================= */
$.get(profileUrl+'?_='+Date.now())
.done(function(html){
const $profile = $(html);
let realCount = 0, albumLink = null;

$profile.find('a').each(function(){
  const t = $(this).text().trim();
  const m = t.match(/^Фото\s*\((\d+)\)$/i);
  if(m){ realCount=parseInt(m[1],10); albumLink=this.href; return false; }
});

if(realCount===null) return;

let storedCount = localStorage.getItem(LS_KEY_COUNT);
storedCount = storedCount===null?null:Number(storedCount);

/* --- если число изменилось --- */
if(realCount!==storedCount){
  localStorage.setItem(LS_KEY_COUNT,realCount);
  if(counter.length) counter.text(realCount);

  if(!albumLink || realCount===0){
   localStorage.setItem(LS_KEY_PHOTOS,JSON.stringify([]));
   renderPhotos([]);
   return;
  }

  /* ===== ЗАГРУЗКА АЛЬБОМА ===== */
  $.get(albumLink)
   .done(function(albumHtml){
    const $page=$(albumHtml);
    const items=[];
    $page.find("div.entry-card.photo-entry-card").each(function(){
     if(items.length>=maxPhotos) return false;
     const $img=$(this).find("img[src*='/_ph/']");
     if(!$img.length) return;
     const src = normalizeUrl($img.attr("src"));
     let link=$(this).find("a").first().attr("href")||'';
     if(link) link=normalizeUrl(link.startsWith('/')?location.origin+link:link);
     if(src) items.push({src,link});
    });
    localStorage.setItem(LS_KEY_PHOTOS,JSON.stringify(items));
    renderPhotos(items);
   });
}
})
.fail(function(){});

});
})(jQuery);
</script>


С точки зрения логики и функционала скрипт сейчас уже полностью самодостаточный модуль:

1. Считает количество фотографий.

2. Обновляет кэш только при изменении данных.

3. Сохраняет и удаляет устаревшие данные корректно.

4. Мгновенно выводит фотографии и счётчик.

5. Работает скрыто, без лишних запросов и задержек.

То есть, с точки зрения JS-логики дальнейших улучшений почти нет , всё отлажено, ошибок нет, процесс полностью автоматизирован.

Единственное поле для прокачки - это визуальная часть (CSS):

Карточки можно сделать более динамичными с анимацией.

Можно улучшить адаптивность под мобильные устройства.

Добавить плавные переходы при наведении или лёгкие эффекты появления.

Но даже текущий минималистичный стиль уже выглядит оригинально и чисто.

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Воскресенье, 08.02.2026, 17:34 | Сообщение # 2 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

Исправленная версия модуля
Как говорится, дьявол кроется в деталях.

Почему возникла проблема и почему я сразу её не заметил? Объясняю. Когда я создавал скрипт, я заложил в него логику управления кэшем. Скрипт сохранял в localStorage не просто ссылки на фотографии -он полностью запоминал настройки модуля. Поэтому, когда я вносил изменения в код, память не обновлялась, и я не видел изменений: система всегда показывала одно и то же.

Логика обновления кэша была устроена так, что только при добавлении новой фотографии старый кэш удалялся из системы, а скрипт после этого обновлялся и начинал работать корректно.

Вот почему систему нужно постоянно тестировать и наблюдать - предугадать все детали заранее невозможно.

Рабочий вариант.

Код
<link rel="stylesheet" href="/css/vk-post.css" media="all">

<style>
/* Сетка фото */
.profile-photo-grid {
display: flex;
gap: 10px;
flex-wrap: wrap;
}

/* Карточка фото */
.profile-photo-grid .photo-card {
width: 130px;
height: 130px;
border-radius: 2px;
overflow: hidden;
background: #e5ebf1;
box-shadow:
0 1px 0 rgba(0,0,0,0.08),
0 2px 6px rgba(0,0,0,0.12);
transition:
transform 0.18s ease,
box-shadow 0.18s ease;
position: relative;
}

/* Ховер как в ВК */
.profile-photo-grid .photo-card:hover {
transform: translateY(5px);
box-shadow:
0 4px 14px rgba(0,0,0,0.18);
}

/* Картинка */
.profile-photo-grid .photo-card img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.25s ease;
}

/* Лёгкий зум */
.profile-photo-grid .photo-card:hover img {
transform: scale(1.01);
}

/* Синяя обводка при наведении */
.profile-photo-grid .photo-card::after {
content: "";
position: absolute;
inset: 0;
border-radius: 2px;
border: 1px solid transparent;
transition: border-color 0.18s ease;
pointer-events: none;
}

.profile-photo-grid .photo-card:hover::after {
border-color: #c0d3fc; /* VK blue */
}

/* Мелкая оптимизация под retina */
@media (max-width: 480px) {
.profile-photo-grid .photo-card {
width: 100px;
height: 90px;
}
}
</style>

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

<script>
(function($){
$(function(){

if (typeof $ === 'undefined') return;

const userId = "$USER_ID$" || '0';
const profileUrl = "$PERSONAL_PAGE_LINK$";
if (!userId || userId === '0') return;

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

const LS_KEY_PHOTOS = 'user_photos_cache_' + userId;
const LS_KEY_COUNT = 'user_photos_count_' + userId;

let renderLock = false;

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

function renderPhotos(items, realCount){
if (renderLock) return;
renderLock = true;

if (!items || !items.length){
   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) counter.text('0');
   renderLock = false;
   return;
}

const grid = $('<div class="profile-photo-grid"></div>');

items.slice(0, maxPhotos).forEach(item=>{
   const card = $('<a class="photo-card"></a>')
     .attr('href', item.link)
     .css({ cursor:'pointer', display:'block' });

   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;

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

container.empty().append(grid);

if(counter.length) counter.text(realCount);

renderLock = false;
}

/* ================= ЗАГРУЗКА И ПРОВЕРКА КЭША ================= */
function loadProfileAndPhotos(){
$.get(profileUrl+'?_='+Date.now())
.done(function(html){
   const $profile = $(html);
   let realCount = 0, albumLink = null;

   $profile.find('a').each(function(){
     const t = $(this).text().trim();
     const m = t.match(/^Фото\s*\((\d+)\)$/i);
     if(m){ realCount=parseInt(m[1],10); albumLink=this.href; return false; }
   });

   if(!albumLink || realCount===0){
     localStorage.setItem(LS_KEY_PHOTOS, JSON.stringify([]));
     localStorage.setItem(LS_KEY_COUNT, 0);
     renderPhotos([], 0);
     return;
   }

   // Проверка кэша
   const cachedCount = parseInt(localStorage.getItem(LS_KEY_COUNT)) || 0;
   if(cachedCount === realCount){
     const cachedPhotos = JSON.parse(localStorage.getItem(LS_KEY_PHOTOS) || '[]');
     renderPhotos(cachedPhotos, realCount);
     return; // всё актуально, кэш не трогаем
   }

   // Кэш устарел или изменилось число фото → загружаем новый альбом
   $.get(albumLink)
   .done(function(albumHtml){
     const $page = $(albumHtml);
     const items = [];

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

       if(link.includes("photo/0-0-") || link.includes("edit")) return;

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

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

     // Сохраняем кэш
     localStorage.setItem(LS_KEY_PHOTOS, JSON.stringify(items));
     localStorage.setItem(LS_KEY_COUNT, realCount);

     renderPhotos(items, realCount);
   });
});
}

/* ================= ПЕРВОЕ ОТОБРАЖЕНИЕ ИЗ КЭША ================= */
try{
const cachedPhotos = JSON.parse(localStorage.getItem(LS_KEY_PHOTOS) || '[]');
const cachedCount = parseInt(localStorage.getItem(LS_KEY_COUNT)) || 0;
if(cachedPhotos.length && cachedCount){
   renderPhotos(cachedPhotos, cachedCount);
}
}catch(e){}

/* ================= ЗАГРУЗКА И ОБНОВЛЕНИЕ ================= */
loadProfileAndPhotos();

});
})(jQuery);
</script>



Скорее всего, в будущем буду создавать другой модуль с иной логикой.

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

Такой подход имеет несколько преимуществ:

Мгновенное отображение изображений – миниатюры будут храниться прямо в памяти браузера, без необходимости обращаться к серверу за каждой картинкой.

Более стабильная работа модуля ,исключаются задержки и «мерцания» при загрузке, которые могут возникать при обычных ссылках.

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

Снижение нагрузки на сервер , нет постоянных HTTP-запросов к каждому изображению, что ускоряет загрузку страницы.

Как это можно реализовать

Генерация миниатюр: при загрузке альбома сервером или с помощью скрипта создаются маленькие версии изображений (например, 100–150px по ширине).

Конвертация в Base64: каждая миниатюра превращается в строку Base64 (data:image/jpeg;base64,...), которую можно хранить как обычный текст.

Хранение в браузере: Base64-строки можно сохранять в LocalStorage или IndexedDB для постоянного кэша.

Отображение: при рендере модуля картинки подставляются напрямую из Base64, мгновенно отображаясь в сетке, без ожидания загрузки с сервера.

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

Пока что это лишь планы.

Говорить о них одно, а воплотить в жизнь совсем другое. Даже мне порой бывает непросто реализовать подобное, фантазировать умеют многие, но создать рабочее решение без лагов и ошибок под силу далеко не каждому. 11

Мурчанн

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