/
G33K
/
Portfolio
Обзор
Документация
Войти
/
G33K
/
Portfolio
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
script.js
215 строк
7 KB
G33K
Implement carousel functionality for project images and enhance reveal animations. Update styles for improved visual presentation and responsiveness.
24 июл 2026, 07:50
24 июл 2026, 07:50
60db0c0
Код
Авторство
О чём код?
(() => { const yearEl = document.getElementById("year"); if (yearEl) { yearEl.textContent = String(new Date().getFullYear()); } const header = document.querySelector(".header"); const nav = document.getElementById("nav"); const toggle = document.querySelector(".nav-toggle"); const onScroll = () => { if (!header) return; header.classList.toggle("is-scrolled", window.scrollY > 8); }; onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); if (toggle && nav) { const setOpen = (open) => { nav.classList.toggle("is-open", open); toggle.setAttribute("aria-expanded", open ? "true" : "false"); toggle.setAttribute("aria-label", open ? "Закрыть меню" : "Открыть меню"); }; toggle.addEventListener("click", () => { setOpen(!nav.classList.contains("is-open")); }); nav.querySelectorAll("a").forEach((link) => { link.addEventListener("click", () => setOpen(false)); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") setOpen(false); }); } const initCarousel = (root) => { const track = root.querySelector(".carousel__track"); const slides = Array.from(root.querySelectorAll(".carousel__slide")); const prevBtn = root.querySelector(".carousel__btn--prev"); const nextBtn = root.querySelector(".carousel__btn--next"); const dotsWrap = root.querySelector(".carousel__dots"); if (!track || slides.length < 2) { if (prevBtn) prevBtn.hidden = true; if (nextBtn) nextBtn.hidden = true; if (dotsWrap) dotsWrap.hidden = true; return; } let index = 0; let startX = 0; let deltaX = 0; let dragging = false; const dots = slides.map((_, i) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "carousel__dot" + (i === 0 ? " is-active" : ""); btn.setAttribute("aria-label", `Слайд ${i + 1}`); btn.addEventListener("click", () => goTo(i)); dotsWrap.appendChild(btn); return btn; }); const render = () => { track.style.transform = `translateX(-${index * 100}%)`; slides.forEach((slide, i) => { slide.classList.toggle("is-active", i === index); }); dots.forEach((dot, i) => { dot.classList.toggle("is-active", i === index); }); }; const goTo = (next) => { index = (next + slides.length) % slides.length; render(); }; prevBtn?.addEventListener("click", () => goTo(index - 1)); nextBtn?.addEventListener("click", () => goTo(index + 1)); const onPointerDown = (event) => { if (event.target.closest(".carousel__btn, .carousel__dot")) return; dragging = true; startX = event.clientX; deltaX = 0; track.style.transition = "none"; root.setPointerCapture?.(event.pointerId); }; const onPointerMove = (event) => { if (!dragging) return; deltaX = event.clientX - startX; const width = root.clientWidth || 1; const offset = -index * 100 + (deltaX / width) * 100; track.style.transform = `translateX(${offset}%)`; }; const onPointerUp = () => { if (!dragging) return; dragging = false; track.style.transition = ""; if (Math.abs(deltaX) > 40) { goTo(deltaX < 0 ? index + 1 : index - 1); } else { render(); } }; root.addEventListener("pointerdown", onPointerDown); root.addEventListener("pointermove", onPointerMove); root.addEventListener("pointerup", onPointerUp); root.addEventListener("pointercancel", onPointerUp); render(); }; document.querySelectorAll("[data-carousel]").forEach(initCarousel); const prepareAssemble = () => { document.querySelectorAll("[data-assemble]").forEach((title) => { if (title.dataset.assembled === "1") return; const text = title.textContent.trim(); const words = text.split(/\s+/); title.textContent = ""; title.setAttribute("aria-label", text); words.forEach((word, i) => { const span = document.createElement("span"); span.className = "assemble-word"; span.style.setProperty("--i", String(i)); span.textContent = word; title.appendChild(span); if (i < words.length - 1) { title.appendChild(document.createTextNode(" ")); } }); title.dataset.assembled = "1"; }); document.querySelectorAll(".reveal--stagger").forEach((list) => { Array.from(list.children).forEach((item, i) => { item.style.setProperty("--i", String(i)); }); }); }; prepareAssemble(); const revealItems = Array.from(document.querySelectorAll(".reveal")); if (!revealItems.length) return; const setProgress = (el, value) => { el.style.setProperty("--reveal-progress", value.toFixed(4)); }; const showAll = () => { revealItems.forEach((el) => setProgress(el, 1)); }; const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); if (motionQuery.matches) { showAll(); return; } // Map element travel through the viewport to 0→1. // 0: top near bottom edge (entering); 1: top past ~32% (assembled / held). // If the page can't scroll the block that high (footer), end at the // highest achievable top so progress hits 1 at document bottom. const progressFor = (el) => { const rect = el.getBoundingClientRect(); const vh = window.innerHeight || 1; const scrollY = window.scrollY || window.pageYOffset || 0; const maxScroll = Math.max( 0, document.documentElement.scrollHeight - vh ); const start = vh * 0.92; const idealEnd = vh * 0.32; const topAtMaxScroll = rect.top - (maxScroll - scrollY); const end = Math.max(idealEnd, Math.min(start - 1, topAtMaxScroll)); const span = Math.max(1, start - end); const raw = (start - rect.top) / span; return Math.min(1, Math.max(0, raw)); }; let ticking = false; const updateReveals = () => { revealItems.forEach((el) => setProgress(el, progressFor(el))); ticking = false; }; const requestUpdate = () => { if (ticking) return; ticking = true; requestAnimationFrame(updateReveals); }; window.addEventListener("scroll", requestUpdate, { passive: true }); window.addEventListener("resize", requestUpdate); updateReveals(); const onMotionChange = (event) => { if (event.matches) showAll(); else requestUpdate(); }; if (typeof motionQuery.addEventListener === "function") { motionQuery.addEventListener("change", onMotionChange); } else if (typeof motionQuery.addListener === "function") { motionQuery.addListener(onMotionChange); } })();