add js files
This commit is contained in:
231
src/scripts/ContentGrid.js
Normal file
231
src/scripts/ContentGrid.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// src/scripts/infinity-scroll.js
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class InfinityScroll {
|
||||||
|
constructor(sentinelElement) {
|
||||||
|
this.grid = document.getElementById('posts-grid');
|
||||||
|
this.sentinel = sentinelElement;
|
||||||
|
this.loadingIndicator = document.getElementById('loading-indicator');
|
||||||
|
this.noMorePosts = document.getElementById('no-more-posts');
|
||||||
|
this.postsCount = document.getElementById('posts-count');
|
||||||
|
this.observer = null;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hasMore = true;
|
||||||
|
this.endCursor = null;
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.gridColumns = 4;
|
||||||
|
this.loadMoreConfig = { type: 'latest', slug: '', gridColumns: 4 };
|
||||||
|
|
||||||
|
// Константы для разных сеток
|
||||||
|
this.CYCLE_LENGTH = {
|
||||||
|
3: 14, // Полный цикл для 3 колонок: 6 обычных + 1 большая + 6 обычных + 1 большая
|
||||||
|
4: 19 // Полный цикл для 4 колонок: 8 обычных + 1 большая + 9 обычных + 1 большая
|
||||||
|
};
|
||||||
|
|
||||||
|
this.OPTIMAL_LOADS = {
|
||||||
|
3: [9, 12, 15], // 3, 4, 5 полных рядов
|
||||||
|
4: [12, 16, 20] // 3, 4, 5 полных рядов
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (!this.sentinel || !this.grid) return;
|
||||||
|
|
||||||
|
// Получаем данные из sentinel
|
||||||
|
this.endCursor = this.sentinel.dataset.endCursor || null;
|
||||||
|
this.currentIndex = parseInt(this.sentinel.dataset.currentIndex || '0');
|
||||||
|
this.gridColumns = parseInt(this.sentinel.dataset.gridColumns || '4');
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loadMoreConfig = JSON.parse(this.sentinel.dataset.loadConfig || '{}');
|
||||||
|
} catch {
|
||||||
|
this.loadMoreConfig = { type: 'latest', slug: '', gridColumns: this.gridColumns };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting && !this.isLoading && this.hasMore) {
|
||||||
|
this.loadMorePosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootMargin: '200px',
|
||||||
|
threshold: 0.1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.observer.observe(this.sentinel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Определяет оптимальное количество постов для загрузки
|
||||||
|
*/
|
||||||
|
getOptimalLoadCount() {
|
||||||
|
const columns = this.gridColumns;
|
||||||
|
const cycleLength = this.CYCLE_LENGTH[columns];
|
||||||
|
const position = this.currentIndex % cycleLength;
|
||||||
|
const options = this.OPTIMAL_LOADS[columns];
|
||||||
|
|
||||||
|
// Выбираем оптимальное число в зависимости от позиции в цикле
|
||||||
|
if (position < columns) {
|
||||||
|
return options[0]; // В начале цикла - 3 полных ряда
|
||||||
|
} else if (position < columns * 2) {
|
||||||
|
return options[1]; // В середине цикла - 4 полных ряда
|
||||||
|
} else {
|
||||||
|
return options[2]; // Ближе к концу - 5 полных рядов
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadMorePosts() {
|
||||||
|
if (this.isLoading || !this.hasMore) return;
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loadCount = this.getOptimalLoadCount();
|
||||||
|
|
||||||
|
const response = await fetch('/load-more-posts', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
perLoad: loadCount,
|
||||||
|
after: this.endCursor,
|
||||||
|
type: this.loadMoreConfig?.type || 'latest',
|
||||||
|
slug: this.loadMoreConfig?.slug || '',
|
||||||
|
startIndex: this.currentIndex,
|
||||||
|
gridColumns: this.gridColumns
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка загрузки: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
const temp = document.createElement('div');
|
||||||
|
temp.innerHTML = html;
|
||||||
|
|
||||||
|
const newSentinel = temp.querySelector('#infinity-scroll-sentinel');
|
||||||
|
const hasNextPage = !!newSentinel;
|
||||||
|
const newEndCursor = newSentinel?.dataset.endCursor || null;
|
||||||
|
|
||||||
|
const articles = temp.querySelectorAll('article');
|
||||||
|
|
||||||
|
if (articles.length > 0) {
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
articles.forEach(article => fragment.appendChild(article.cloneNode(true)));
|
||||||
|
this.grid.appendChild(fragment);
|
||||||
|
|
||||||
|
this.currentIndex += articles.length;
|
||||||
|
this.endCursor = newEndCursor;
|
||||||
|
this.hasMore = hasNextPage;
|
||||||
|
|
||||||
|
if (this.postsCount) {
|
||||||
|
this.postsCount.textContent = ` (${this.currentIndex})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sentinel) {
|
||||||
|
this.sentinel.dataset.currentIndex = String(this.currentIndex);
|
||||||
|
this.sentinel.dataset.endCursor = newEndCursor || '';
|
||||||
|
|
||||||
|
if (!hasNextPage) {
|
||||||
|
this.showNoMorePosts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.hasMore = false;
|
||||||
|
this.showNoMorePosts();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки:', error);
|
||||||
|
this.hasMore = false;
|
||||||
|
this.showError();
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
if (this.loadingIndicator) {
|
||||||
|
this.loadingIndicator.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading() {
|
||||||
|
if (this.loadingIndicator) {
|
||||||
|
this.loadingIndicator.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showNoMorePosts() {
|
||||||
|
if (this.sentinel && this.observer) {
|
||||||
|
this.observer.unobserve(this.sentinel);
|
||||||
|
this.sentinel.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.noMorePosts) {
|
||||||
|
this.noMorePosts.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError() {
|
||||||
|
if (this.noMorePosts) {
|
||||||
|
this.noMorePosts.textContent = 'Ошибка загрузки. Попробуйте обновить страницу.';
|
||||||
|
this.noMorePosts.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.observer && this.sentinel) {
|
||||||
|
this.observer.unobserve(this.sentinel);
|
||||||
|
}
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Глобальная функция для инициализации
|
||||||
|
window.initInfinityScroll = function(sentinelElement) {
|
||||||
|
if (!sentinelElement) {
|
||||||
|
sentinelElement = document.getElementById('infinity-scroll-sentinel');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sentinelElement) {
|
||||||
|
// Очищаем предыдущий инстанс если есть
|
||||||
|
if (window.__infinityScrollInstance) {
|
||||||
|
window.__infinityScrollInstance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем новый инстанс
|
||||||
|
window.__infinityScrollInstance = new InfinityScroll(sentinelElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Автоматическая инициализация при загрузке страницы
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.initInfinityScroll();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.initInfinityScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка для динамической навигации Astro
|
||||||
|
document.addEventListener('astro:after-swap', () => {
|
||||||
|
// Переинициализируем после смены страницы
|
||||||
|
setTimeout(() => {
|
||||||
|
window.initInfinityScroll();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user