仅在检测到页面中存在数学公式时才加载 MathJax 并进行渲染,支持动态内容和格式容错。
// ==UserScript==
// @name 按需加载 MathJax(数学公式自渲染)
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 仅在检测到页面中存在数学公式时才加载 MathJax 并进行渲染,支持动态内容和格式容错。
// @author KiwiFruit
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/* global MathJax */
// 如果已经加载 MathJax,则跳过
if (typeof MathJax !== 'undefined') {
console.log('MathJax 已经加载,跳过重复加载。');
return;
}
let mathjaxLoaded = false;
// 跳过不处理公式的标签
const skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'PRE', 'TEXTAREA', 'CODE'];
// 安全地检测是否包含未转义的 $ 或 $$ 公式(兼容不规范写法,但避免误判)
function hasMathContent() {
// 1. 检查是否存在 MathJax 脚本标签
if (document.querySelector('script[type="math/tex"], script[type="math/tex; mode=display"]')) {
return true;
}
// 2. 遍历所有文本节点
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: function (node) {
const parent = node.parentElement;
if (parent && skipTags.includes(parent.tagName)) {
return NodeFilter.FILTER_SKIP;
}
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
// 手动模拟“非转义 $”检测(避免使用后瞻断言以兼容 Safari 等)
function hasUnescapedDollar(text) {
let i = 0;
while (i < text.length) {
const idx = text.indexOf('$', i);
if (idx === -1) break;
// 检查是否为转义:\$ 不算
if (idx === 0 || text[idx - 1] !== '\\') {
return true;
}
i = idx + 1;
}
return false;
}
while (walker.nextNode()) {
const text = walker.currentNode.textContent;
if (hasUnescapedDollar(text)) {
return true;
}
}
return false;
}
// 加载 MathJax 并配置
function loadMathJax() {
if (mathjaxLoaded) return;
mathjaxLoaded = true;
window.MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
processEscapes: true, // 支持 \$ 转义
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
ignoreHtmlClass: 'tex2jax_ignore'
}
};
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
script.async = true;
script.onload = function () {
console.log('MathJax 加载完成,开始渲染数学公式...');
MathJax.typesetPromise().catch(err => console.error('MathJax 渲染失败:', err));
// 暴露全局重渲染函数(供其他脚本或调试使用)
window.typesetMath = () => {
if (typeof MathJax !== 'undefined') {
MathJax.typesetPromise().catch(console.error);
}
};
};
script.onerror = function () {
console.error('MathJax 加载失败,请检查网络连接或 CDN 地址。');
alert('数学公式渲染失败:无法加载 MathJax,请检查网络连接。');
};
document.head.appendChild(script);
}
// 防抖函数
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 尝试检测并加载 MathJax
function tryLoadIfNeeded() {
if (mathjaxLoaded || typeof MathJax !== 'undefined') return;
if (hasMathContent()) {
loadMathJax();
}
}
// 初始检测(异步,避免阻塞)
setTimeout(tryLoadIfNeeded, 0);
// 监听后续 DOM 变化(适用于 SPA、动态加载内容等)
const observer = new MutationObserver(debounce(tryLoadIfNeeded, 600));
observer.observe(document.body, { childList: true, subtree: true });
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址