MediaWiki:Common.js: відмінності між версіями

нема опису редагування
Немає опису редагування
Немає опису редагування
Рядок 48: Рядок 48:


     var $items = $lbox.find('.l-box-item');
     var $items = $lbox.find('.l-box-item');
     var HEADER_OFFSET = 78;
     var HEADER_OFFSET = 80;
    var DURATION = 320;          // фіксована тривалість, мс
     var isAnimating = false;
     var isAnimating = false;
    var rafId = null;


    // Знаходить елемент-заголовок для якоря (міряємо ЗАВЖДИ в момент виклику)
     function getTargetEl(target) {
     function getTargetEl(target) {
         if (target === 'top') return null;
         if (target === 'top') return null;
         var el = document.getElementById(target);
         var el = document.getElementById(target);
         if (!el) return null;
         if (!el) return null;
         var $el = $(el);
         var $heading = $(el).closest('h1, h2, h3, h4');
        var $heading = $el.closest('h1, h2, h3, h4');
         return $heading.length ? $heading[0] : el;
         return $heading.length ? $heading[0] : el;
     }
     }
Рядок 65: Рядок 65:
         var el = getTargetEl(target);
         var el = getTargetEl(target);
         if (!el) return null;
         if (!el) return null;
         // getBoundingClientRect + поточний скрол — актуально на момент виклику
         var top = el.getBoundingClientRect().top + window.pageYOffset - HEADER_OFFSET;
        return el.getBoundingClientRect().top + window.pageYOffset - HEADER_OFFSET;
        // не даємо вийти за межі документа
        var max = document.documentElement.scrollHeight - window.innerHeight;
        return Math.max(0, Math.min(top, max));
     }
     }


  function scrollToTarget(target) {
    function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
 
    function scrollToTarget(target) {
        if (rafId) cancelAnimationFrame(rafId);
 
        var start = window.pageYOffset;
        var end = getTargetTop(target);
        if (end === null) return;
 
        if (Math.abs(end - start) < 2) { isAnimating = false; return; }
 
         isAnimating = true;
         isAnimating = true;
        var startTime = null;


         if (target === 'top') {
         function frame(now) {
             window.scrollTo({ top: 0, behavior: 'smooth' });
            if (startTime === null) startTime = now;
        } else {
            var elapsed = now - startTime;
             var el = getTargetEl(target);
             var t = Math.min(1, elapsed / DURATION);
             if (!el) { isAnimating = false; return; }
 
             el.scrollIntoView({ behavior: 'smooth', block: 'start' });
            // Переміряємо ціль на льоту — layout міг зсунутись
        }
             var currentEnd = getTargetTop(target);
             if (currentEnd === null) currentEnd = end;
 
             window.scrollTo(0, start + (currentEnd - start) * easeOutCubic(t));


        // Знімаємо прапорець, коли прокрутка зупинилась
            if (t < 1) {
        var timer = null;
                rafId = requestAnimationFrame(frame);
        function onScroll() {
             } else {
            clearTimeout(timer);
                 rafId = null;
             timer = setTimeout(function () {
                 window.removeEventListener('scroll', onScroll);
                 isAnimating = false;
                 isAnimating = false;
                 updateActive();
                 updateActive();
             }, 120);
             }
         }
         }
        window.addEventListener('scroll', onScroll, { passive: true });
 
         onScroll(); // на випадок, якщо сторінка вже на місці й скролу не буде
         rafId = requestAnimationFrame(frame);
     }
     }


     function updateActive() {
     function updateActive() {
         if (isAnimating) return;           // не перебиваємо підсвітку під час переходу
         if (isAnimating) return;
         var scrollY = window.pageYOffset + 120;
         var scrollY = window.pageYOffset;
         var current = null;
         var current = null;


         $items.each(function () {
         $items.each(function () {
             var target = $(this).data('target');
             var top = getTargetTop($(this).data('target'));
            var top = (target === 'top') ? 0 : getTargetTop(target);
             if (top !== null && top <= scrollY + 10) current = $(this);
             if (top !== null && top - HEADER_OFFSET <= scrollY) current = $(this);
         });
         });


Рядок 110: Рядок 123:


     $items.on('click', function () {
     $items.on('click', function () {
        // Блокуємо повторний клік на вже активному пункті
        if ($(this).hasClass('active')) return;
        if (isAnimating) return;
         var target = $(this).data('target');
         var target = $(this).data('target');
         $items.removeClass('active');
         $items.removeClass('active');
Рядок 128: Рядок 145:
     $(window).on('scroll', function () {
     $(window).on('scroll', function () {
         if (!ticking) {
         if (!ticking) {
             window.requestAnimationFrame(function () {
             requestAnimationFrame(function () {
                 updateActive();
                 updateActive();
                 ticking = false;
                 ticking = false;
Рядок 138: Рядок 155:
     $(window).on('resize', updateActive);
     $(window).on('resize', updateActive);
     $(document).on('mcc:content-loaded', updateActive);
     $(document).on('mcc:content-loaded', updateActive);
    // Перерахунок після довантаження картинок і шрифтів
     $(window).on('load', updateActive);
     $(window).on('load', updateActive);
     if (document.fonts && document.fonts.ready) {
     if (document.fonts && document.fonts.ready) {