Cкрипт предназначен для автоматического вывода последних фотографий пользователя в профиле с кэшированием и корректной обработкой пустого состояния. Скрипт работает на jQuery, не нагружает сервер лишними запросами и корректно ведёт себя даже при отсутствии фото.
Структура подключения
Код
<div id="user-photos"></div>
Контейнер, в который динамически подгружаются фотографии либо сообщение «фото отсутствуют».
Общий принцип работы
1. Скрипт выполняется сразу после загрузки страницы и работает по следующей логике:
2. Определяет ID пользователя и ссылку на его профиль
3. Проверяет наличие актуального кэша в localStorage
4. Если кэш свежий — мгновенно отображает данные
5. Если кэша нет или он устарел:
6. загружает страницу профиля
7. находит ссылку на фотоальбом
8. получает превью фотографий
9. сохраняет данные в кэш
10. отображает результат
Определение пользователя
Код
const userId = "$USER_ID$" || '0';
const profileUrl = "$PERSONAL_PAGE_LINK$";
$USER_ID$ — ID текущего пользователя
$PERSONAL_PAGE_LINK$ — ссылка на страницу профиля
Если пользователь не авторизован — скрипт не выполняется.
Кэширование (LocalStorage)
Используются ключи:
Код
user_photos_v3_<userId>
user_photos_v3_<userId>_time
Срок жизни кэша: 24 часа
Код
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000;
Кэшируется даже пустой результат, чтобы не делать повторные запросы.
Мгновенный показ кэша
1. Сначала скрипт пытается показать сохранённые данные:
2. если кэш существует
3. если он не старше 24 часов
Запросы к серверу не выполняются
Код
renderPhotos(photos);
return;
Это даёт:
1. быстрый рендер
2. отсутствие «миганий»
3. экономию ресурсов
Загрузка данных с профиля
Если кэш отсутствует или устарел:
1. Загружается HTML страницы профиля
2. Ищется ссылка вида:
Извлекается:
1. ссылка на альбом
2. общее количество фото
Количество фото сразу сохраняется и отображается в счётчике.
Загрузка фотографий из альбома
Из страницы альбома:
1. выбираются карточки .photo-entry-card
2. берутся изображения / _ph /
3. сохраняются:
JS
Максимум выводится 4 фотографии.
Отрисовка фотографий
Если фото есть:
1. создаётся сетка карточек
2. плавная загрузка изображений
3. hover-эффект (scale)
4. клик ведёт на оригинальную запись / тему
Если фото нет:
1. Отображается аккуратный блок:
2. «Пока здесь нет фотографий»
с предложением добавить их в фотоальбом
Детали
1. Удаление hash и query из URL
2. Защита от ошибок старого кэша
3. Плавное появление изображений
4. Корректная работа при 0 фото
5. Отсутствие лишних запросов
Скрипт:
автоматически показывает последние фото пользователя
работает быстро за счёт кэша
не ломается при пустых альбомах
минимально нагружает сервер
визуально выглядит аккуратно и современно
Код
<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_v3_' + userId;
const LS_KEY_COUNT = 'user_photos_count_v3_' + userId;
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 часа
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 (!items?.length) {
container.empty().html(`
<div style="
padding: 24px 16px;
text-align: center;
color: #666;
font-size: 15px;
background: #fafafa;
border-radius: 10px;
min-height: 100px;
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: 0.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');
return;
}
// Есть фото — отрисовка карточек
const grid = $('<div class="profile-photo-grid"></div>').css({
display: 'flex',
gap: '12px',
flexWrap: 'wrap'
});
items.slice(0, maxPhotos).forEach(item => {
const card = $('<div class="photo-card"></div>').css({
width: '129px',
height: '125px',
overflow: 'hidden',
borderRadius: '2px',
cursor: 'pointer',
background: '#f0f0f0',
boxShadow: '0 2px 6px rgba(0,0,0,0.08)',
transition: 'transform 0.15s ease'
});
card.hover(function() {
$(this).css('transform', 'scale(1.03)');
}, function() {
$(this).css('transform', 'scale(1)');
});
const img = $('<img alt="Фото пользователя">').css({
width: '100%',
height: '100%',
objectFit: 'cover',
opacity: 0,
transition: 'opacity 0.35s ease'
});
card.on('click', () => {
if (item.link) window.location.href = item.link;
});
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(items.length);
}
}
// 1. Показываем кэш сразу (даже если пустой!)
try {
const cached = localStorage.getItem(LS_KEY_PHOTOS);
const cachedTime = localStorage.getItem(LS_KEY_PHOTOS + '_time');
if (cached && cachedTime) {
const age = Date.now() - Number(cachedTime);
if (age < CACHE_DURATION_MS) {
const photos = JSON.parse(cached);
if (Array.isArray(photos)) {
renderPhotos(photos); // показываем даже если []
return; // кэш свежий — выходим, запрос не нужен!
}
}
}
} catch (e) {
// игнорируем ошибки старого кэша
}
// 2. Только если кэш устарел или отсутствует — делаем запрос
$.get(profileUrl)
.done(function(html) {
const $profile = $(html);
let albumLink = null;
let photoCount = 0;
$profile.find('a').each(function() {
const text = $(this).text().trim();
const match = text.match(/^Фото\s*\((\d+)\)$/i);
if (match) {
albumLink = this.href;
photoCount = parseInt(match[1], 10) || 0;
return false;
}
});
// Сохраняем количество сразу
localStorage.setItem(LS_KEY_COUNT, photoCount.toString());
if (counter.length) counter.text(photoCount);
if (!albumLink || photoCount === 0) {
// Нет альбома или 0 фото — кэшируем пустое состояние
localStorage.setItem(LS_KEY_PHOTOS, JSON.stringify([]));
localStorage.setItem(LS_KEY_PHOTOS + '_time', Date.now());
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"));
const linkEl = $(this).find(
"a.photo-card-title, a[href*='/topic/'], a[href*='/post/'], a[href*='/entry/']"
);
let link = linkEl.first().attr("href") || '';
if (link) {
link = normalizeUrl(link.startsWith('/') ? location.origin + link : link);
}
if (src && link) {
items.push({ src, link });
}
});
// Всегда сохраняем в кэш (даже если items пустой)
localStorage.setItem(LS_KEY_PHOTOS, JSON.stringify(items));
localStorage.setItem(LS_KEY_PHOTOS + '_time', Date.now());
renderPhotos(items);
});
})
.fail(function() {
// Если профиль не загрузился — оставляем старый кэш
});
});
})(jQuery);
</script>