Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <script>
- (function () {
- // === Глобальный счётчик уникальных H2 для всех wrap ===
- let globalHeadingIndex = 0;
- /* ===========================================================
- Стабильная работа с URL только на уровне article-wrapper
- =========================================================== */
- let currentWrapUrl = null;
- let lastSetUrl = null;
- let historyUpdateTimeout = null;
- // Мягкое обновление URL
- function replaceUrlThrottled(url) {
- if (lastSetUrl === url) return;
- clearTimeout(historyUpdateTimeout);
- historyUpdateTimeout = setTimeout(() => {
- history.replaceState(null, '', url);
- lastSetUrl = url;
- }, 200);
- }
- // Получаем активный article-wrapper при каждом скролле
- function getActiveWrap() {
- const pos = window.scrollY + window.innerHeight * 0.3;
- let active = null;
- document.querySelectorAll(".article-wrapper").forEach(wrap => {
- const rect = wrap.getBoundingClientRect();
- const top = window.scrollY + rect.top;
- const bottom = top + rect.height;
- if (pos >= top && pos <= bottom) {
- active = wrap;
- }
- });
- return active;
- }
- function updateActiveWrap() {
- const activeWrap = getActiveWrap();
- if (!activeWrap) return;
- const url = activeWrap.querySelector(".dynamic-lazy-url span")
- ?.textContent.trim();
- if (!url) return;
- if (url !== currentWrapUrl) {
- replaceUrlThrottled(url);
- currentWrapUrl = url;
- }
- }
- /* ==========================================
- Генерация TOC для конкретного article-wrapper
- ========================================== */
- function initTOCForWrap(wrap) {
- if (!wrap || wrap.dataset.tocReady) return;
- const tocContainer = wrap.querySelector(".toc-table");
- const articleContent = wrap.querySelector(".article-content");
- if (!tocContainer || !articleContent) return;
- function slugify(str) {
- return String(str).toLowerCase().trim()
- .replace(/[^\w\s-]/g, '')
- .replace(/\s+/g, '-')
- .replace(/-+/g, '-');
- }
- const headings = articleContent.querySelectorAll("h2");
- headings.forEach(heading => {
- const title = heading.textContent.trim();
- const anchorId = `toc-${slugify(title)}-${globalHeadingIndex}`;
- heading.setAttribute("id", anchorId);
- const a = document.createElement("a");
- a.href = "javascript:void(0)";
- a.dataset.anchor = anchorId;
- a.textContent = title;
- a.classList.add("articles-link");
- a.addEventListener("click", (e) => {
- e.preventDefault();
- const id = a.dataset.anchor;
- const target = document.getElementById(id);
- if (target) {
- const y = target.getBoundingClientRect().top + window.scrollY - 30;
- window.scrollTo({ top: y, behavior: "smooth" });
- }
- // При клике меняется URL на URL текущего wrap
- const dynamicUrlSpan = wrap.querySelector(".dynamic-lazy-url span");
- if (dynamicUrlSpan) {
- const articleUrl = dynamicUrlSpan.textContent.trim();
- history.pushState(null, '', articleUrl);
- lastSetUrl = articleUrl;
- currentWrapUrl = articleUrl;
- }
- });
- tocContainer.appendChild(a);
- globalHeadingIndex++;
- });
- /* -------- Подсветка active заголовка -------- */
- function activateHeadingOnScroll() {
- const links = Array.from(tocContainer.querySelectorAll(".articles-link"));
- let headingElements = links.map(link => {
- const id = link.dataset.anchor;
- return wrap.querySelector(`#${CSS.escape(id)}`);
- }).filter(Boolean);
- let currentActiveIndex = -1;
- function updateActive() {
- const scrollPos = window.scrollY + 100;
- let newIndex = -1;
- headingElements.forEach((heading, i) => {
- const top = heading.getBoundingClientRect().top + window.scrollY;
- if (top <= scrollPos) newIndex = i;
- });
- if (newIndex === currentActiveIndex) return;
- currentActiveIndex = newIndex;
- links.forEach((link, i) => {
- link.classList.toggle("active", i === newIndex);
- });
- }
- window.addEventListener("scroll", updateActive, { passive: true });
- window.addEventListener("resize", () => {
- headingElements = links.map(link => {
- const id = link.dataset.anchor;
- return wrap.querySelector(`#${CSS.escape(id)}`);
- }).filter(Boolean);
- updateActive();
- });
- updateActive();
- }
- activateHeadingOnScroll();
- wrap.dataset.tocReady = "1";
- }
- /* ==========================================
- Инициализация TOC на уже существующих блоках
- ========================================== */
- document.querySelectorAll(".article-wrapper").forEach(wrap => {
- initTOCForWrap(wrap);
- });
- /* ==========================================
- Lazyload: MutationObserver
- ========================================== */
- const observer = new MutationObserver(mutations => {
- for (const m of mutations) {
- m.addedNodes.forEach(node => {
- if (node.nodeType !== 1) return;
- // Новый wrap добавился
- if (node.matches(".article-wrapper")) {
- initTOCForWrap(node);
- }
- // Или внутри добавился wrap
- const inner = node.querySelectorAll?.(".article-wrapper");
- inner?.forEach(wrap => initTOCForWrap(wrap));
- // обновляем активный wrap после вставки контента
- requestAnimationFrame(updateActiveWrap);
- });
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- /* ==========================================
- Запуск отслеживания активного WRAP
- ========================================== */
- window.addEventListener("scroll", () => {
- requestAnimationFrame(updateActiveWrap);
- }, { passive: true });
- window.addEventListener("resize", updateActiveWrap);
- window.addEventListener("load", updateActiveWrap);
- })();
- </script>
Advertisement
Add Comment
Please, Sign In to add comment