匿名
未登录
中文(中国大陆)
登录
深色模式
「荏苒之境」
搜索
查看“︁MediaWiki:Timeless.js”︁的源代码
来自「荏苒之境」
命名空间
系统消息
讨论
更多
更多
页面操作
阅读
查看源代码
历史
清除缓存
←
MediaWiki:Timeless.js
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。 如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此JavaScript页面,因为编辑此页面可能会影响所有访问者。
您可以查看和复制此页面的源代码。
/* 将为Timeless皮肤的用户加载此处的所有JavaScript */ const ua = navigator.userAgent; // 精确判断 Safari:Chrome / Edge 的 UA 里也带 "Safari" const isSafari = /Safari/i.test(ua) && !/Chrome|Chromium|Edg|OPR|CriOS/i.test(ua); // ---------- 标题追踪 ---------- class HeadingTracker { constructor(rootElement) { this.rootElement = rootElement; this.headings = []; this.cacheExpired = true; this.init(); } init() { this.collectHeadings(); window.addEventListener('resize', () => { this.cacheExpired = true; }); // 图片/字体加载完可能改变标题位置,加载结束后重新测一次 window.addEventListener('load', () => this.collectHeadings()); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.collectHeadings()); } } collectHeadings() { const rawHeadings = Array.from( this.rootElement.querySelectorAll('h1, h2, h3, h4, h5, h6') ); this.headings = rawHeadings .map(heading => ({ element: heading, top: heading.getBoundingClientRect().top + window.scrollY })) .sort((a, b) => a.top - b.top); this.cacheExpired = false; } getNearestHeadingAboveViewport() { if (this.cacheExpired) { this.collectHeadings(); } const scrollY = window.scrollY + 60; let left = 0; let right = this.headings.length - 1; let result = null; while (left <= right) { const mid = Math.floor((left + right) / 2); if (this.headings[mid].top <= scrollY) { result = this.headings[mid].element; left = mid + 1; } else { right = mid - 1; } } return result; } } // ---------- 浮动目录 ---------- class FloatingToC { constructor(toc) { this.element = toc; this.lastHeading = null; // 点击目录项后的一小段时间内,忽略滚动事件带来的高亮更新 this.ignoreScrollUntil = 0; // 用标题 id 关联目录项与正文标题,而不是用文字匹配 // (标题重名时文字会撞 key,id 不会) this.headingToTocText = new Map(); for (const text of toc.querySelectorAll('.toctext')) { const link = text.closest('a'); if (!link) { continue; } const id = decodeURIComponent( (link.getAttribute('href') || '').replace(/^#/, '') ); const heading = id ? document.getElementById(id) : null; if (!heading) { continue; } this.headingToTocText.set(heading, text); link.addEventListener('click', () => { this.ignoreScrollUntil = Date.now() + 500; this.setCurrentHeading(heading); }); } this.tracker = new HeadingTracker( document.getElementById('mw-content-text') ); this.scrollHandler = this.onScroll.bind(this); window.addEventListener('scroll', this.scrollHandler); } remove() { window.removeEventListener('scroll', this.scrollHandler); this.element.remove(); } setCurrentHeading(heading) { if (heading === this.lastHeading) { return; } if (this.lastHeading) { const text = this.headingToTocText.get(this.lastHeading); if (text) { text.classList.remove('current-heading'); } } this.lastHeading = heading; if (heading) { const text = this.headingToTocText.get(heading); if (text) { text.classList.add('current-heading'); } } } onScroll() { if (window.innerWidth < 1100) { return; } if (Date.now() < this.ignoreScrollUntil) { return; } const heading = this.tracker.getNearestHeadingAboveViewport(); this.setCurrentHeading(heading); } } // ---------- 状态 ---------- let toc = null; let tocInitialParent = null; // 目录原本在哪个容器里 let tocNextSibling = null; // 目录原本插在哪个元素前面(还原用) let floatingToC = null; let siteNavigation = null; // ---------- 目录的移动逻辑 ---------- function isWideEnough() { return isSafari ? window.innerWidth > 1354 : window.innerWidth > 1339; } function findArticleTOC() { const content = document.getElementById('mw-content-text'); return content ? content.querySelector('#toc') : null; } function restoreTOCToArticle() { if (tocInitialParent == null) { return; } if (tocNextSibling && tocNextSibling.parentNode === tocInitialParent) { tocInitialParent.insertBefore(toc, tocNextSibling); } else { tocInitialParent.appendChild(toc); } } function updateToCParent() { if (siteNavigation == null || toc == null) { return; } if (isWideEnough()) { // 宽屏:进侧边栏 if (toc.parentNode !== siteNavigation) { siteNavigation.appendChild(toc); } return; } // 窄屏:放回文章里原本的位置 if (toc.parentNode === siteNavigation) { restoreTOCToArticle(); } } // ---------- 初始化 / 清理 ---------- function teardownFloatingToC() { if (floatingToC != null) { floatingToC.remove(); floatingToC = null; } toc = null; tocInitialParent = null; tocNextSibling = null; } function initFloatingToC(newToc) { if (newToc == null || newToc === toc) { return; } if (floatingToC != null) { // 旧的目录可能还挂在侧边栏里,先摘掉再重新初始化 floatingToC.remove(); floatingToC = null; } toc = newToc; tocInitialParent = toc.parentNode; tocNextSibling = toc.nextSibling; floatingToC = new FloatingToC(toc); updateToCParent(); } // 等待元素出现:先检查当前 DOM,没有再挂 MutationObserver。 // 之前只挂 observer,页面加载完就绪、没有任何后续变更时回调永远不会触发。 function waitForElement(getElement, callback) { const element = getElement(); if (element) { callback(element); return; } const observer = new MutationObserver(() => { const el = getElement(); if (el) { observer.disconnect(); callback(el); } }); observer.observe(document.body, { childList: true, subtree: true }); } // VE 保存后内容重渲染,重走一遍:清掉旧目录,等新目录出现 document.addEventListener('pageupdate', () => { teardownFloatingToC(); waitForElement(findArticleTOC, initFloatingToC); }); // 启动:两个元素谁先出现都能收敛到正确状态 waitForElement(() => document.getElementById('mw-site-navigation'), elem => { siteNavigation = elem; updateToCParent(); // 目录可能先出现,这里补一次移动 }); waitForElement(findArticleTOC, initFloatingToC); // 跨断点缩放时也要重新摆放目录 window.addEventListener('resize', () => updateToCParent());
返回
MediaWiki:Timeless.js
。
导航
导航
最近更改
随机页面
特殊页面
模板列表
wiki工具
wiki工具
Cargo数据
页面工具
页面工具
用户页面工具
更多
链入页面
相关更改
页面信息
页面日志