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

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

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

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

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

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

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



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

Код
<!-- MINI BLOCK ДРУЗЬЯ -->
<div class="vk-old-block">
<div class="vk-old-header">Друзья</div>
<div class="vk-old-footer" id="friends-count">0 друзей</div>

<div class="vk-old-content">
<div class="vk-friends"></div>
</div>

<div class="vk-old-footer" id="friends-footer" title="Показать всех друзей">
Показать всех друзей →
</div>
</div>

<link rel="stylesheet" href="/css/friends02.css" media="all">

<div id="uc-online-list" style="display:none;">
$ONLINE_USERS_LIST$
</div>

<script>
(function(){

const USER_ID = window.UCOZ_DATA?.userId || "0";
if(USER_ID === "0") return;

const $container = $('.vk-friends');
const $counter = $('#friends-count');
const $footer = $('#friends-footer');

const DEFAULT_AVA = '/.s/src/profile/img/profile_photo_thumbnail.png';
const LS_FRIENDS = 'friends_' + USER_ID;

let allFriends = [];

/* =========================
РЕНДЕР МИНИ-БЛОКА
========================= */
function renderMini(friends){
const onlineHtml = $('#uc-online-list').text().toLowerCase();

if(!friends || friends.length === 0){
const empty = document.createElement('div');
empty.className = 'vk-empty-wrap';

empty.innerHTML = `
<div class="vk-empty-icon">
<svg width="58" height="58" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 11c1.66 0 3-1.34 3-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zM8 11c1.66 0 3-1.34 3-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3z" fill="#99a2ad"/>
<path d="M8 13c-2.67 0-8 1.34-8 4v2h10v-2c0-1.2.6-2.3 1.6-3.2C10.3 13.3 9 13 8 13zm8 0c-.9 0-2.3.2-3.6.8 1 .9 1.6 2 1.6 3.2v2h10v-2c0-2.66-5.33-4-8-4z" fill="#99a2ad"/>
</svg>
</div>
<div class="vk-empty-title">У вас нет друзей</div>
<div class="vk-empty-sub">Добавляйте людей, чтобы видеть их здесь</div>

<div class="vk-empty-action">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Добавить друзей
</div>
`;

empty.querySelector('.vk-empty-action').onclick = () => {
location.href = '/index/15-2';
};

$container[0].replaceChildren(empty);
$counter.html('0 друзей');
return;
}

/* Обычный список друзей */
$container.empty();
friends.slice(0, 6).forEach(f => {
const isOnline = onlineHtml.includes(f.nick.toLowerCase());
const statusDot = `<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>`;

const card = $(`
<div class="vk-friend" title="${f.nick}">
<img src="${f.ava}">
<span>${f.nick}</span>
${statusDot}
</div>
`);

card.on('click', () => location.href = f.profile);
$container.append(card);
});

$counter.html(`${friends.length} друзей`);
}

/* =========================
POPUP (стрелка + удаление)
========================= */
function showPopup(){
if(!allFriends.length) return;

const overlay = $('<div class="vk-ui-overlay"></div>');

const popup = $(`
<div class="vk-ui-popup">
<div class="vk-ui-header">
<div class="vk-ui-title">
Друзья <span class="vk-ui-title-count">${allFriends.length}</span>
</div>

<div class="vk-ui-search">
<input type="text" placeholder="Поиск друзей" id="friend-search">
</div>

<button class="vk-ui-close" title="Закрыть">×</button>
</div>

<div class="vk-ui-list"></div>
</div>
`);

const list = popup.find('.vk-ui-list');
const onlineHtml = $('#uc-online-list').text().toLowerCase();

function renderList(filter = ''){
list.empty();

allFriends
.filter(f => f.nick.toLowerCase().includes(filter.toLowerCase()))
.forEach(f => {

let badge = 'Без статуса', cls = 'nostatus', letter = '';
const g = f.group.toLowerCase();

if(g.includes('кумир')) { badge='Кумир'; cls='idol'; letter='К'; }
else if(g.includes('друг')) { badge='Друг'; cls='friend'; letter='Д'; }
else if(g.includes('сем')) { badge='Семья'; cls='family'; letter='С'; }
else if(g.includes('знаком')) { badge='Знакомый'; cls='acquaintance'; letter='З'; }
else if(g.includes('прият')) { badge='Приятель'; cls='buddy'; letter='П'; }

const isOnline = onlineHtml.includes(f.nick.toLowerCase());

const row = $(`
<div class="vk-ui-row ${cls}">

<!-- аватар -->
<div class="vk-ui-ava">
<img src="${f.ava}">
${letter ? `<span class="vk-ui-letter ${cls}">${letter}</span>` : ''}
<span class="statusDot ${isOnline ? 'onlineDot' : 'offlineDot'}"></span>
</div>

<!-- инфо -->
<div class="vk-ui-info">
<div class="vk-ui-name">${f.nick}</div>
<div class="vk-ui-badge ${cls}">${badge}</div>
</div>

<!-- правая часть -->
<div class="vk-ui-actions">

<!-- КОРЗИНА (удаление) -->
<button class="vk-del-btn" title="Удалить">✕</button>

<!-- СТРЕЛКА (переход) -->
<div class="vk-ui-arrow">›</div>

</div>

</div>
`);

/* =========================
ПЕРЕХОД В ПРОФИЛЬ (стрелка + строка)
========================= */
row.on('click', () => location.href = f.profile);

/* =========================
УДАЛЕНИЕ (крестик) — UPGRADE
========================= */
row.find('.vk-del-btn').on('click', function(e){
  e.stopPropagation();

  const btn = $(this);

  // защита от двойного клика
  if(btn.data('loading')) return;

  if(!f.del){
    alert('Ошибка: нет ссылки удаления');
    return;
  }

  const originalText = btn.text();

  // МОДАЛКА ПОДТВЕРЖДЕНИЯ
  openDeleteModal(f.nick, f.ava, () => {

    btn.data('loading', true);

    // состояние загрузки
    btn.text('...');
    btn.css({
      'pointer-events':'none',
      'opacity':'0.6'
    });

    $.get(f.del)
      .done(() => {

        // удаляем из массива
        allFriends = allFriends.filter(x => x.nick !== f.nick);

        // АНИМАЦИЯ УДАЛЕНИЯ
        row.css({
          transition: 'all 0.55s cubic-bezier(.2,.9,.2,1)',
          transform: 'translateX(140px) translateY(120px) scale(0.25) rotate(12deg)',
          opacity: '0',
          filter: 'blur(8px)',
          boxShadow: '0 30px 60px rgba(0,0,0,0.25)'
        });

        // микро-задержка перед удалением
        setTimeout(() => {
          row.css({
            transition: 'all 0.2s ease',
            opacity: '0'
          });

          setTimeout(() => {
            row.remove();
          }, 200);

        }, 350);

        // обновляем UI
        renderMini(allFriends);
        $counter.html(`${allFriends.length} друзей`);
        localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));

      })
      .fail(() => {

        alert('Ошибка удаления');

        // откат кнопки
        btn.text(originalText);
        btn.css({
          'pointer-events':'auto',
          'opacity':'1'
        });

        btn.data('loading', false);
      });

  });

});

list.append(row);
});
}

renderList();

popup.find('#friend-search').on('input', function(){
renderList($(this).val());
});

popup.find('.vk-ui-close').on('click', () => overlay.remove());

overlay.on('click', e => {
if(e.target === overlay[0]) overlay.remove();
});

overlay.append(popup);
$('body').append(overlay);
}

function ensureModal(){
  if ($('.vk-modal-overlay').length) return;

  $('body').append(`
    <div class="vk-modal-overlay">
      <div class="vk-modal">

<div class="vk-modal-user">
  <div class="vk-modal-avatar">
    <img src="" alt="">
  </div>

  <div>
    <div class="vk-modal-user-name"></div>
    <div class="vk-modal-user-sub">будет удалён из друзей</div>
  </div>
</div>

        <div class="vk-modal-title">Удаление друга</div>

        <div class="vk-modal-text"></div>

        <div class="vk-modal-warning">
          Это действие нельзя отменить
        </div>

        <div class="vk-modal-actions">
          <button class="vk-modal-cancel">Отмена</button>
          <button class="vk-modal-confirm">Удалить</button>
        </div>

      </div>
    </div>
  `);
}

function openDeleteModal(name, ava, onConfirm){
  ensureModal();

  const modal = $('.vk-modal-overlay');
  const img = modal.find('.vk-modal-avatar img');
  const nameEl = modal.find('.vk-modal-user-name');

  // заполнение данных
  modal.find('.vk-modal-text')
    .text(`Удалить "${name}" из друзей?`);

img.attr('src', ava || '/.s/src/profile/img/profile_photo_thumbnail.png');
nameEl.text(name);

  // открытие
  modal.addClass('active');
  $('body').addClass('vk-modal-open');

  function closeModal(){
    modal.removeClass('active');
    $('body').removeClass('vk-modal-open');
  }

  // предотвращаем двойные клики
  let locked = false;

  // cancel
  modal.find('.vk-modal-cancel')
    .off('click')
    .on('click', function(){
      if (locked) return;
      closeModal();
    });

  // confirm
  modal.find('.vk-modal-confirm')
    .off('click')
    .on('click', function(){

      if (locked) return;
      locked = true;

      const btn = $(this);

      btn.text('Удаление...');
      btn.css({
        'pointer-events': 'none',
        'opacity': '0.7'
      });

      setTimeout(() => {
        closeModal();

        if (typeof onConfirm === 'function') {
          onConfirm();
        }

        // reset кнопки (на случай повторного открытия)
        btn.text('Удалить');
        btn.css({
          'pointer-events': 'auto',
          'opacity': '1'
        });

        locked = false;

      }, 180);
    });

  // клик по фону
  modal.off('click').on('click', function(e){
    if (e.target === this && !locked) {
      closeModal();
    }
  });

  // ESC
  $(document)
    .off('keydown.vkmodal')
    .on('keydown.vkmodal', function(e){
      if (e.key === 'Escape' && !locked) {
        closeModal();
      }
    });
}

/* =========================
ЗАГРУЗКА ДАННЫХ С СЕРВЕРА
========================= */
function loadFriends(){
$.get(`/blog/0-0-1-0-17-${USER_ID}`, html => {

const $tmp = $('<div>').html(html);
const updated = [];

$tmp.find('.friend').each(function(){
const $el = $(this);

let group = $el.find('.gr').text().trim() || 'Без статуса';
const g = group.toLowerCase();

if(!['друг','кумир','семья','знакомый','приятель'].some(k => g.includes(k))) {
group = 'Приятель';
}

// ВАЖНО: добавляем ссылку удаления
const delLink = $el.find('.del').text().trim();

updated.push({
nick: $el.find('.nick').text().trim(),
ava: $el.find('.ava').text().trim() || DEFAULT_AVA,
profile: $el.find('.url').text().trim(),
group: group,
del: delLink || null // ← теперь у каждого друга есть ссылка удаления
});
});

// сравнение теперь учитывает del
const changed = JSON.stringify(updated) !== JSON.stringify(allFriends);

if(changed){
allFriends = updated;

// сохраняем в кеш
localStorage.setItem(LS_FRIENDS, JSON.stringify(allFriends));

// обновляем UI
renderMini(allFriends);

console.log('Друзья обновлены:', allFriends.length);
}
})
.fail(() => {
console.warn('Не удалось загрузить список друзей');
});
}

/* =========================
ИНИЦИАЛИЗАЦИЯ
========================= */
$counter.add($footer)
.css('cursor','pointer')
.attr('title','Показать всех друзей')
.on('click', showPopup);

// Показываем кэш сразу
try {
const cached = JSON.parse(localStorage.getItem(LS_FRIENDS) || "[]");
if(cached.length){
allFriends = cached;
renderMini(allFriends);
} else {
renderMini([]);
}
} catch(e){
renderMini([]);
}

// Тихая загрузка актуальных данных
setTimeout(loadFriends, 800); // небольшая задержка, чтобы страница успела загрузиться

// Дополнительно обновляем каждые 30 секунд (на случай удаления/добавления друга)
setInterval(loadFriends, 30000);

})();
</script>


Мурчанн

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