文件内容 main.js 8 KB 8883 字符
/**
 * PasteApeart 官网 · 交互脚本
 * 原生 JS,无依赖
 */
(function () {
  'use strict';

  document.addEventListener('DOMContentLoaded', init);

  function init() {
    initYear();
    const nav = document.getElementById('nav');
    const backToTop = document.getElementById('backToTop');

    initScrollEffects(nav, backToTop);
    initMobileNav();
    initSmoothScroll();
    initRevealOnScroll();
    initActiveNavLink();
    initAttrSearch();
    initCodeCopy();
  }

  // ---------- 年份 ----------
  function initYear() {
    const el = document.getElementById('year');
    if (el) el.textContent = String(new Date().getFullYear());
  }

  // ---------- 滚动:导航栏样式 + 回顶按钮 ----------
  function initScrollEffects(nav, backToTop) {
    if (!nav && !backToTop) return;

    let ticking = false;

    function update() {
      const y = window.scrollY || window.pageYOffset;

      if (nav) {
        if (y > 8) nav.classList.add('scrolled');
        else nav.classList.remove('scrolled');
      }

      if (backToTop) {
        if (y > 400) backToTop.classList.add('show');
        else backToTop.classList.remove('show');
      }

      ticking = false;
    }

    window.addEventListener(
      'scroll',
      function () {
        if (!ticking) {
          ticking = true;
          window.requestAnimationFrame(update);
        }
      },
      { passive: true }
    );

    update();

    if (backToTop) {
      backToTop.addEventListener('click', function () {
        window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
      });
    }
  }

  // ---------- 移动端导航 ----------
  function initMobileNav() {
    const toggle = document.getElementById('navToggle');
    const links = document.getElementById('navLinks');
    if (!toggle || !links) return;

    const ICON_OPEN = '<i class="bi bi-list"></i>';
    const ICON_CLOSE = '<i class="bi bi-x-lg"></i>';

    function close() {
      links.classList.remove('active');
      toggle.innerHTML = ICON_OPEN;
      toggle.setAttribute('aria-expanded', 'false');
    }

    toggle.addEventListener('click', function (e) {
      e.stopPropagation();
      const opened = links.classList.toggle('active');
      toggle.innerHTML = opened ? ICON_CLOSE : ICON_OPEN;
      toggle.setAttribute('aria-expanded', opened ? 'true' : 'false');
    });

    links.addEventListener('click', function (e) {
      const a = e.target.closest('a');
      if (a) close();
    });

    document.addEventListener('click', function (e) {
      if (!links.contains(e.target) && !toggle.contains(e.target)) close();
    });

    document.addEventListener('keydown', function (e) {
      if (e.key === 'Escape') close();
    });

    window.addEventListener('resize', function () {
      if (window.innerWidth > 1080) close();
    });
  }

  // ---------- 平滑滚动(带导航栏偏移) ----------
  function initSmoothScroll() {
    var NAV_OFFSET = 72;

    document.querySelectorAll('a[href^="#"]').forEach(function (anchor) {
      anchor.addEventListener('click', function (e) {
        var href = anchor.getAttribute('href');
        if (!href || href === '#') return;

        var target = document.querySelector(href);
        if (!target) return;

        e.preventDefault();

        var top = target.getBoundingClientRect().top + (window.scrollY || window.pageYOffset) - NAV_OFFSET;
        window.scrollTo({ top: top, behavior: 'smooth' });

        // 更新 URL hash(不触发额外跳转)
        if (window.history && window.history.replaceState) {
          window.history.replaceState(null, '', href);
        }
      });
    });
  }

  // ---------- 滚动入场动画 ----------
  function initRevealOnScroll() {
    var els = document.querySelectorAll('.fade-up');
    if (!els.length) return;

    if (!('IntersectionObserver' in window)) {
      els.forEach(function (el) {
        el.classList.add('in');
      });
      return;
    }

    var observer = new IntersectionObserver(
      function (entries) {
        entries.forEach(function (entry) {
          if (entry.isIntersecting) {
            entry.target.classList.add('in');
            observer.unobserve(entry.target);
          }
        });
      },
      { threshold: 0.08, rootMargin: '0px 0px -20px 0px' }
    );

    els.forEach(function (el) {
      observer.observe(el);
    });
  }

  // ---------- 导航高亮当前板块 ----------
  function initActiveNavLink() {
    var links = document.querySelectorAll('#navLinks a[href^="#"]');
    if (!links.length) return;

    var map = {};
    var sections = [];

    links.forEach(function (link) {
      var id = link.getAttribute('href').slice(1);
      var section = document.getElementById(id);
      if (!section) return;
      map[id] = link;
      sections.push(section);
    });

    if (!sections.length) return;

    if (!('IntersectionObserver' in window)) return;

    var observer = new IntersectionObserver(
      function (entries) {
        entries.forEach(function (entry) {
          if (!entry.isIntersecting) return;
          var id = entry.target.id;
          links.forEach(function (l) {
            l.classList.remove('active');
          });
          if (map[id]) map[id].classList.add('active');
        });
      },
      { rootMargin: '-45% 0px -50% 0px', threshold: 0 }
    );

    sections.forEach(function (section) {
      observer.observe(section);
    });
  }

  // ---------- 特性系统搜索 ----------
  function initAttrSearch() {
    var input = document.getElementById('attrSearch');
    var clearBtn = document.getElementById('attrClear');
    var totalEl = document.getElementById('attrTotal');
    var emptyEl = document.getElementById('attrEmpty');
    var groups = Array.prototype.slice.call(document.querySelectorAll('.attr-group'));
    if (!input || !groups.length) return;

    var items = [];
    groups.forEach(function (group) {
      var groupItems = Array.prototype.slice.call(group.querySelectorAll('.attr-item'));
      groupItems.forEach(function (item) {
        var nameEl = item.querySelector('.attr-name');
        var descEl = item.querySelector('.attr-desc');
        var name = nameEl ? nameEl.textContent : '';
        var desc = descEl ? descEl.textContent : '';
        var keywords = item.getAttribute('data-keywords') || '';
        items.push({
          el: item,
          group: group,
          haystack: (name + ' ' + desc + ' ' + keywords).toLowerCase()
        });
      });
    });

    if (totalEl) totalEl.textContent = String(items.length);

    function apply(keyword) {
      var kw = keyword.trim().toLowerCase();
      var visible = 0;

      if (clearBtn) clearBtn.classList.toggle('show', kw.length > 0);

      items.forEach(function (entry) {
        var matched = kw === '' || entry.haystack.indexOf(kw) !== -1;
        entry.el.hidden = !matched;
        if (matched) visible++;
      });

      // 分组内无可见项时隐藏整个分组
      groups.forEach(function (group) {
        var anyVisible = group.querySelectorAll('.attr-item:not([hidden])').length > 0;
        group.hidden = kw !== '' && !anyVisible;
      });

      if (emptyEl) {
        emptyEl.hidden = visible !== 0;
      }
    }

    input.addEventListener('input', function () {
      apply(input.value);
    });

    if (clearBtn) {
      clearBtn.addEventListener('click', function () {
        input.value = '';
        apply('');
        input.focus();
      });
    }

    // 支持从 URL hash 之外的方式触发:#attributes?image 之类不做处理,保持简单
    apply('');
  }

  // ---------- 代码复制 ----------
  function initCodeCopy() {
    var btn = document.getElementById('codeCopy');
    var block = document.querySelector('.code-block');
    if (!btn || !block) return;

    var codeEl = block.querySelector('code');
    if (!codeEl) return;

    var ICON_COPY = btn.innerHTML;

    btn.addEventListener('click', function () {
      var text = codeEl.innerText;

      function done(ok) {
        btn.classList.toggle('copied', ok);
        btn.innerHTML = ok ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-x-lg"></i>';
        setTimeout(function () {
          btn.classList.remove('copied');
          btn.innerHTML = ICON_COPY;
        }, 1800);
      }

      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(
          function () {
            done(true);
          },
          function () {
            done(fallbackCopy(text));
          }
        );
      } else {
        done(fallbackCopy(text));
      }
    });

    function fallbackCopy(text) {
      try {
        var ta = document.createElement('textarea');
        ta.value = text;
        ta.setAttribute('readonly', '');
        ta.style.position = 'absolute';
        ta.style.left = '-9999px';
        document.body.appendChild(ta);
        ta.select();
        var ok = document.execCommand('copy');
        document.body.removeChild(ta);
        return ok;
      } catch (err) {
        return false;
      }
    }
  }
})();
​ ​