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

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

  • Страница 1 из 1
  • 1
Модуль ВК «Подарки» для Ucoz v 8.0 стиль вконтакте
Дата: Суббота, 07.02.2026, 22:04 | Сообщение # 1 | | Написал: Узнаваемый
Автор темы
Мурчанн не в сети
        Сообщений:211
         Регистрация:20.10.2016

Динамическое обновление: как скрипт сам следит за подарками

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

Вот как это работает:

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

Удаление устаревших данных
Чтобы не перегружать память и не показывать устаревшую информацию, старые данные автоматически очищаются. При этом кэш в localStorage обновляется, а старые записи заменяются новыми.

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

Сортировка по дате
Все подарки сортируются по времени отправки от самых новых к старым. Это создаёт эффект «живого потока»: пользователь всегда видит свежее событие первым, а устаревшие записи постепенно уходят вниз списка.

Автоматическая актуализация кэша
Каждый подарок сохраняется в локальном хранилище. При последующих визитах скрипт проверяет: совпадает ли количество подарков с текущим состоянием. Если да используются данные из кэша. Если количество изменилось — данные обновляются, и интерфейс снова синхронизируется с сервером.

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



Новый взгляд на виртуальные подарки: как современный скрипт меняет UX

В мире веб-разработки небольшие улучшения интерфейса могут кардинально изменить восприятие пользователем привычных функций. Один из таких примеров работа с виртуальными подарками на пользовательских страницах. Ранее многие сталкивались с проблемами: устаревший скрипт медленно загружал данные, некорректно отображал ники и аватары, а кнопка «Все подарки» могла исчезнуть в самый неподходящий момент. Новый скрипт решает эти проблемы и делает процесс приятным, удобным и красивым.

Мгновенный обзор: превью последних подарков

Новая версия скрипта сразу отображает три последних подарка пользователя. Благодаря локальному кэшу браузера (localStorage) данные сохраняются между сессиями, а значит, даже при медленном интернете превью показывается почти мгновенно.

Пользователь видит:

изображение подарка;

ник отправителя;

количество подарков.

И всё это с плавной анимацией при наведении, которая делает интерфейс живым и приятным глазу.

Кнопка «Все подарки» — больше не теряется

Раньше кнопка «Все подарки» иногда пропадала из-за ошибок в логике отображения. Новый скрипт автоматически добавляет её, если подарков больше трёх. Она работает как «вход в полный мир подарков»: открывает попап со всеми деталями, где каждый подарок представлен в отдельной карточке.

Попап с деталями: каждый подарок на виду

Попап — это сердце нового скрипта. Он аккуратно отображает:

ник пользователя (кликабельный, ведёт на профиль);

аватар отправителя;

дату отправки подарка;

комментарий (если он был оставлен);

изображение подарка.

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




Особое внимание уделено UX:

аватар при наведении слегка увеличивается;

крестик закрытия анимирован: при наведении плавно вращается;

кнопка «Вручить подарок» доступна только для других пользователей.

Локальный кэш: ускорение без потери данных

Каждый подарок сохраняется в localStorage после первой загрузки:

ник, аватар, комментарий, дата и ссылка на профиль;

при повторном открытии попапа данные подгружаются мгновенно;

при необходимости асинхронно обновляются.

Это делает работу скрипта стабильной и экономит трафик.

Этот скрипт пример того, как внимание к мелочам обычный интерфейс в современный и удобный.

Он сочетает:

стабильность работы;

красивый визуальный стиль;

удобство использования;

и современные UX-решения.

Пользователь получает не просто список подарков он получает живой, интерактивный опыт, который приятно использовать каждый день.

Хотя мы говорим о сортировке по дате, стоит отметить: это громкое слово для платформы Юкоз. Дело в том, что сервер не всегда отдаёт XML в правильном порядке. Но скрипт умён , он закладывает механизм и логику упорядочивания подарков, и старается максимально корректно расположить их от новых к старым, насколько это позволяет исходная информация.

Полностью исходник .

Код
<!-- ======= Блок подарков ======= -->
<div class="gifts-block">
<div class="gifts-header">Подарки</div>
<div class="gifts-footer" id="gifts-count">Загрузка…</div>
<div class="gifts-container" id="gifts-container"></div>
</div>

<link rel="stylesheet" href="/css/vk-gifts.css" media="all">

<script>
(async function(){

const container = document.getElementById('gifts-container');
const counter = document.getElementById('gifts-count');
const userId = <?$JSENCODE$($USER_ID$)?>;
const STORAGE_KEY = `giftsData_${userId}`;

let allGifts = [];

// ──────────────────────────────────────────────
// Стили (твои оригинальные, без изменений)
// ──────────────────────────────────────────────
const style = document.createElement('style');
style.innerHTML = `
.gifts-block {background:#fff;border-radius:2px;box-shadow:0 2px 12px rgba(0,0,0,0.1);overflow:hidden;margin:8px 0;position:relative}
.gifts-header {background:linear-gradient(135deg,#4a76a8,#5a88c0);color:#fff;padding:12px 16px;font-weight:600;font-size:14px}
#gifts-container {display:flex;gap:5px;justify-content:center;padding:16px 16px 28px;flex-wrap:wrap;position:relative}
.gift-card {width:86px;height:100px;border-radius:12px;overflow:hidden;transition:transform .25s ease,box-shadow .25s ease;cursor:pointer}
.gift-card img {width:180%;height:140%;object-fit:contain}
.gift-card:hover {transform:scale(1.06);box-shadow:0 10px 25px rgba(74,118,168,0.3)}
.show-all-btn {position:absolute;bottom:0;right:16px;background:#4a76a8;color:white;font-size:12px;font-weight:600;padding:6px 14px;border-radius:8px 8px 0 0;border:none;box-shadow:0 -3px 8px rgba(0,0,0,0.12);cursor:pointer;transition:all 0.2s ease;z-index:2}
.show-all-btn:hover {background:#3b5e8c;box-shadow:0 -4px 12px rgba(0,0,0,0.18)}
.show-all-btn:active {transform:translateY(1px)}
.gifts-popup-overlay {position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;backdrop-filter:blur(5px)}
.gifts-popup {background:#fff;width:520px;max-height:92vh;border-radius:4px;overflow:hidden;box-shadow:0 20px 70px rgba(0,0,0,0.45);border:0px solid #d0d9e4;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
.gifts-popup-header {background:#4a76a8;color:white;padding:14px 20px;font-size:17px;font-weight:600;display:flex;justify-content:space-between;align-items:center}
.gifts-popup-header span {cursor:pointer;font-size:26px;opacity:0.85}
.gifts-popup-header span:hover {opacity:1}
.gifts-popup-list {padding:16px;overflow-y:auto;max-height:68vh;background:#f9fbff}
.vk-gift {padding:14px 0;border-bottom:1px solid #e8ecf2;transition:background 0.2s}
.vk-gift:hover {background:#f0f4fa}
.vk-user img {width:80px;height:80px;border-radius:50%;border:3px solid #4a76a8;object-fit:cover;transition:all 0.28s ease;box-shadow:0 3px 10px rgba(0,0,0,0.12)}
.vk-user:hover img {transform:scale(1.12);box-shadow:0 8px 24px rgba(74,118,168,0.4);filter:brightness(1.1)}
.vk-user {display:flex;gap:16px;align-items:center;margin-bottom:10px}
.vk-user .name {font-weight:700;color:#2c3e50;font-size:15.5px}
.vk-user .name a {color:#2c3e50;text-decoration:none}
.vk-user .name a:hover {color:#4a76a8;text-decoration:underline}
.vk-user .recipient-label {font-size:12px;color:#7f8c9d;margin-top:3px}
.vk-user .date {font-size:12.5px;color:#95a5a6}
.vk-user .date-loading {color:#aaa;font-style:italic}
.vk-comment {margin-top:8px;font-size:14px;color:#34495e;background:#ecf4ff;padding:10px 14px;border-radius:10px;border-left:4px solid #4a76a8}
.vk-gift-img {text-align:center;margin:14px 0}
.vk-gift-img img {max-width:260px;border-radius:12px;box-shadow:0 5px 18px rgba(0,0,0,0.12)}
.vk-reply {text-align:center;margin-top:14px}
.vk-reply a {background:#4a76a8;color:white;padding:9px 22px;border-radius:8px;font-weight:600;text-decoration:none;transition:background 0.2s}
.vk-reply a:hover {background:#3b5e8c}
.gifts-popup-header span { cursor: pointer;font-size: 26px;opacity: 0.85; transition: transform 0.3s ease, opacity 0.3s ease;}
.gifts-popup-header span:hover {transform: rotate(50deg); opacity: 2; }
`;
document.head.appendChild(style);
// ──────────────────────────────────────────────
// Функции для даты (надёжная логика)
// ──────────────────────────────────────────────

/* ─────────────── ФУНКЦИИ ─────────────── */
function parseGiftDate(str){
if(!str) return null;
const m = str.match(/(\d{1,2})\.(\d{1,2})\.(\d{4}),?\s*(\d{1,2}):(\d{2})/);
if(!m) return null;
const dt = new Date(m[3], m[2]-1, m[1], m[4], m[5]);
return isNaN(dt.getTime()) ? null : dt;
}

function formatGiftDate(str){
const dt = parseGiftDate(str);
if(!dt) return str || 'дата неизвестна';
return dt.toLocaleString('ru-RU',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
}

async function fetchGiftDetails(detailUrl){
if(!detailUrl) return {date:null,nick:null,comment:null,profile:'#'};
try{
const res = await fetch(detailUrl);
if(!res.ok) return {date:null,nick:null,comment:null,profile:'#'};
const text = await res.text();
const xml = new DOMParser().parseFromString(text,'application/xml');
const cmd = xml.querySelector('cmd[p="content"]');
if(!cmd) return {date:null,nick:null,comment:null,profile:'#'};
const html = cmd.textContent;
const date = html.match(/(\d{1,2}\.\d{1,2}\.\d{4},?\s*\d{1,2}:\d{2})/)?.[1] || null;
const legend = html.match(/<legend><b><a href="([^"]+)"[^>]*>([^<]+)<\/a><\/b>/);
const nick = legend ? legend[2].trim() : null;
const profile = legend ? legend[1] : '#';
const comment = html.match(/id="mtx\d+"[^>]*>([\s\S]*?)(?=<div)/)?.[1]?.trim() || null;
return {date,nick,comment,profile};
}catch(e){console.warn(e); return {date:null,nick:null,comment:null,profile:'#'};}
}

/* ─────────────── ЗАГРУЗКА СПИСКА ПОДАРКОВ ─────────────── */
async function loadGifts(){
allGifts = [];
container.innerHTML = '';
try{
const res = await fetch(`/index/54-${userId}-`);
const text = await res.text();
const xml = new DOMParser().parseFromString(text,'application/xml');
const cmd = xml.querySelector('cmd[p="content"]');
if(!cmd){ counter.textContent='0 подарков'; return; }
const temp = document.createElement('div');
temp.innerHTML = cmd.textContent;
const tds = [...temp.querySelectorAll('td[onclick*="_uWnd.reload"]')];
if(!tds.length){ counter.textContent='0 подарков'; return; }

let total=0;
const cached = JSON.parse(localStorage.getItem(STORAGE_KEY)||'[]');

for(const td of tds){
const img = td.querySelector('img');
if(!img) continue;
const count = td.querySelector('b') ? +td.querySelector('b').textContent || 1 : 1;
const onclick = td.getAttribute('onclick');
for(let i=0;i<count;i++){
// проверяем кэш по src+onclick
const saved = cached.find(c=>c.img===img.src && c.onclick===onclick) || {};
allGifts.push({
img: img.src,
onclick,
nick: saved.nick || 'Загрузка...',
avatar: saved.avatar || '/.s/src/profile/img/profile_photo_thumbnail.png',
date: saved.date || '',
comment: saved.comment || '',
profile: saved.profile || '#',
recipientId: saved.recipientId || null
});
}
total+=count;
}

counter.textContent = `${total} ${total===1?'подарок':total<5?'подарка':'подарков'}`;

// сортировка по дате
allGifts.sort((a,b)=>{
const ta = parseGiftDate(a.date)?.getTime() || 0;
const tb = parseGiftDate(b.date)?.getTime() || 0;
return tb - ta;
});

// показываем превью 3 последних
container.innerHTML='';
allGifts.slice(0,3).forEach(g=>{
  const div = document.createElement('div');
  div.className='gift-card';
  div.title = g.nick;
  div.innerHTML=`<img src="${g.img}" alt="">`;
  container.appendChild(div);
});

// ─────────────── КНОПКА "ВСЕ ПОДАРКИ" ───────────────
if(allGifts.length > 3){
  const btn = document.createElement('button');
  btn.className = 'show-all-btn';
  btn.textContent = 'Все подарки';
  btn.onclick = showPopup;
  container.appendChild(btn);
}

}catch(e){ console.error(e); counter.textContent='Ошибка загрузки'; }
}

/* ─────────────── POPUP ─────────────── */
async function showPopup(){
if(!allGifts.length) return;

const overlay = document.createElement('div');
overlay.className='gifts-popup-overlay';
const popup = document.createElement('div');
popup.className='gifts-popup';
popup.innerHTML=`
<div class="gifts-popup-header">Мои подарки <span>×</span></div>
<div class="gifts-popup-list"></div>
`;
overlay.appendChild(popup);
document.body.appendChild(overlay);

popup.querySelector('span').onclick=()=>overlay.remove();
overlay.onclick=e=>{ if(e.target===overlay) overlay.remove(); };

const list = popup.querySelector('.gifts-popup-list');
const cached = JSON.parse(localStorage.getItem(STORAGE_KEY)||'[]');

// создаём DOM сразу, детали будут догружаться
allGifts.forEach((g,idx)=>{
const div = document.createElement('div');
div.className='vk-gift';
div.dataset.idx=idx;
div.innerHTML=`
<div class="vk-user">
  <a href="${g.profile && g.profile!=='#'?g.profile:'javascript:;'}" target="_blank" rel="noopener noreferrer">
    <img src="${g.avatar}" alt="${g.nick || 'Пользователь'}">
  </a>
  <div>
    <div class="name">
      <a href="${g.profile && g.profile!=='#'?g.profile:'javascript:;'}" target="_blank" rel="noopener noreferrer">
        ${g.nick || 'Пользователь'}
      </a>
    </div>
    <div class="date">${g.date}</div>
    ${g.comment ? `<div class="vk-comment">${g.comment}</div>` : ''}
  </div>
</div>

<div class="vk-gift-img"><img src="${g.img}"></div>
<div class="vk-reply"></div>
`;
list.appendChild(div);

// Кнопка вручения
const replyDiv = div.querySelector('.vk-reply');
if(g.recipientId && g.recipientId != userId){
const btn = document.createElement('a');
btn.href="javascript:;";
btn.textContent='🎁 Вручить подарок';
btn.onclick = ()=>new _uWnd('AwD','Вручить награду',380,200,
{maxh:300,minh:100,closeonesc:1},{url:'/index/55-'+g.recipientId});
replyDiv.appendChild(btn);
} else {
replyDiv.textContent='Нельзя вручить себе';
}
});

// догружаем детали асинхронно
allGifts.forEach(async (g,idx)=>{
if(!g.date || g.nick==='Загрузка...' || !g.comment){
const details = await fetchGiftDetails(g.onclick?.match(/url:'([^']+)'/)?.[1]);
if(details){
g.date = details.date || g.date;
g.nick = details.nick || g.nick;
g.comment = details.comment || g.comment;
g.profile = details.profile || g.profile;
if(details.profile){
const match = details.profile.match(/-(\d+)$/);
if(match) g.recipientId = match[1];
}

const div = list.children[idx];
div.querySelector('.name').textContent = g.nick;
div.querySelector('.date').textContent = formatGiftDate(g.date);
if(!div.querySelector('.vk-comment') && g.comment){
const cdiv = document.createElement('div');
cdiv.className='vk-comment';
cdiv.textContent=g.comment;
div.querySelector('.vk-user div').appendChild(cdiv);
}

const replyDiv = div.querySelector('.vk-reply');
replyDiv.innerHTML='';
if(g.recipientId && g.recipientId!=userId){
const btn = document.createElement('a');
btn.href="javascript:;";
btn.textContent='🎁 Вручить подарок';
btn.onclick = ()=>new _uWnd('AwD','Вручить награду',380,200,
{maxh:300,minh:100,closeonesc:1},{url:'/index/55-'+g.recipientId});
replyDiv.appendChild(btn);
} else replyDiv.textContent='Нельзя вручить себе';

// обновляем кэш
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)||'[]');
stored[idx] = g;
localStorage.setItem(STORAGE_KEY,JSON.stringify(stored));
}
}
});

}

/* ─────────────── СТАРТ ─────────────── */
counter.style.cursor='pointer';
counter.onclick = showPopup;

loadGifts();

})();
</script>


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

Мурчанн

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