要解决这个问题,思路其实就三个字:占、藏、缓存。
下面是按这个思路改好的版本,你可以直接替换你现在的 menu-loader.js。我在里面都写了注释,你看得懂的:
// javascript/menu-loader.js
(function () {
/* ========== 常量 ========== */
var MENU_STORAGE_KEY = 'artocarpus_menu_cache_v1';
/* ========== 1. 生成候选路径 ========== */
function getMenuCandidates() {
var base = location.pathname.replace(/[^\/]+$/, '');
var ts = 'ts=' + Date.now();
return [
base + 'menu.html?' + ts,
'/Artocarpus/menu.html?' + ts,
'/artocarpus/menu.html?' + ts
];
}
/* ========== 2. 立即把旧菜单藏起来 ========== */
function hideMenuTd() {
var td = document.querySelector('td.menu');
if (td) {
// 不用 display:none,避免布局塌陷,用 visibility
td.style.visibility = 'hidden';
td.style.opacity = '0';
}
}
/* ========== 3. 显示菜单 ========== */
function showMenuTd() {
var td = document.querySelector('td.menu');
if (td) {
td.style.visibility = 'visible';
td.style.opacity = '1';
}
}
/* ========== 4. 安全插入到 td.menu ========== */
function injectMenuHtml(html) {
var menuTd = document.querySelector('td.menu');
if (!menuTd) {
console.error('[menu-loader] 没找到 td.menu');
return;
}
// 清空原来的(不管是旧菜单还是占位)
while (menuTd.firstChild) {
menuTd.removeChild(menuTd.firstChild);
}
// 用容器解析,再一个个搬
var temp = document.createElement('div');
temp.innerHTML = html;
while (temp.firstChild) {
menuTd.appendChild(temp.firstChild);
}
// 插入之后再显示,避免抖动
showMenuTd();
console.log('[menu-loader] 菜单 DOM 已插入');
}
/* ========== 5. 绑定搜索 ========== */
function setupMenuSearch() {
window.handleMenuSearch = function () {
var form = document.getElementById('menu-search-form');
if (!form) {
console.error('[menu-loader] 未找到菜单搜索表单');
return false;
}
var input = form.querySelector('input[name="q"]');
var kw = input ? input.value.trim() : '';
if (!kw) {
alert('Please enter a search term');
return false;
}
form.action = '/cgi-bin/Artocarpus/simple-search.cgi';
form.target = '_self';
form.submit();
return true;
};
var input = document.querySelector('#menu-search-form input[name="q"]');
if (input) {
input.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
window.handleMenuSearch();
}
});
}
console.log('[menu-loader] 菜单搜索已绑定');
}
/* ========== 6. 依次尝试加载(并更新缓存) ========== */
function loadMenuFrom(candidates, index) {
if (index >= candidates.length) {
console.error('[menu-loader] 所有路径都失败了:', candidates);
return;
}
var url = candidates[index];
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function () {
var txt = xhr.responseText || '';
if (xhr.status === 200 && txt.trim() !== '') {
// 更新页面
injectMenuHtml(txt);
setupMenuSearch();
// 更新本地缓存
try {
localStorage.setItem(MENU_STORAGE_KEY, txt);
} catch (e) {
// 忽略缓存失败
}
console.log('[menu-loader] 从 ' + url + ' 取得菜单,长度=' + txt.length);
} else {
console.warn('[menu-loader] 加载 ' + url + ' 失败(HTTP=' + xhr.status + '),试下一个');
loadMenuFrom(candidates, index + 1);
}
};
xhr.onerror = function () {
console.warn('[menu-loader] 请求 ' + url + ' 出错,试下一个');
loadMenuFrom(candidates, index + 1);
};
xhr.send(null);
}
/* ========== 7. 页脚修改(保持你的) ========== */
function debugFooter(msg) {
console.log('[页脚修改] ' + msg);
}
function modifyFooter() {
var preloadImg = new Image();
preloadImg.src = '/Artocarpus/images/ZWY_LOGO.png';
var footerTables = document.querySelectorAll(
'table[width="1200px"][height="100%"], table[align="center"][width="1200px"]'
);
if (footerTables.length === 0) {
var allTables = document.querySelectorAll('table');
for (var i = 0; i < allTables.length; i++) {
if (allTables[i].textContent.indexOf('ArtocarpusGD is developed by') !== -1) {
footerTables = [allTables[i]];
break;
}
}
}
if (footerTables.length === 0) {
debugFooter('未找到页脚表格');
return;
}
var footerTable = footerTables[0];
var footerRow = footerTable.querySelector('tr');
if (!footerRow) {
debugFooter('未找到表格行');
return;
}
var cells = footerRow.querySelectorAll('td');
if (cells.length !== 2) {
debugFooter('未找到预期的2个单元格');
return;
}
var textCell = cells[0];
var logoCell = cells[1];
try {
var originalHeight = footerRow.offsetHeight;
footerRow.innerHTML = '';
footerRow.style.display = 'flex';
footerRow.style.alignItems = 'center';
footerRow.style.justifyContent = 'space-between';
var zwyCell = document.createElement('td');
zwyCell.style.flex = '0 0 15%';
zwyCell.style.display = 'flex';
zwyCell.style.justifyContent = 'flex-start';
zwyCell.style.alignItems = 'center';
zwyCell.style.height = originalHeight + 'px';
var zwyLink = document.createElement('a');
zwyLink.href = 'http://www.scib.ac.cn';
zwyLink.target = '_blank';
zwyLink.style.display = 'flex';
zwyLink.style.alignItems = 'center';
zwyLink.style.justifyContent = 'center';
zwyLink.style.height = '100%';
var zwyImg = document.createElement('img');
zwyImg.src = '/Artocarpus/images/ZWY_LOGO.png';
zwyImg.alt = 'ZWY logo';
zwyImg.style.maxWidth = '80px';
zwyImg.style.maxHeight = '80px';
zwyImg.style.objectFit = 'contain';
zwyImg.style.border = '0';
zwyLink.appendChild(zwyImg);
zwyCell.appendChild(zwyLink);
var centerCell = document.createElement('td');
centerCell.style.flex = '1';
centerCell.style.display = 'flex';
centerCell.style.flexDirection = 'column';
centerCell.style.justifyContent = 'center';
centerCell.style.alignItems = 'center';
centerCell.style.textAlign = 'center';
centerCell.style.height = originalHeight + 'px';
centerCell.style.padding = '0 20px';
var textDiv = document.createElement('div');
textDiv.innerHTML = textCell.innerHTML;
centerCell.appendChild(textDiv);
var rightCell = document.createElement('td');
rightCell.style.flex = '0 0 15%';
rightCell.style.display = 'flex';
rightCell.style.justifyContent = 'flex-end';
rightCell.style.alignItems = 'center';
rightCell.style.height = originalHeight + 'px';
var logoWrapper = document.createElement('div');
logoWrapper.style.display = 'flex';
logoWrapper.style.justifyContent = 'flex-end';
logoWrapper.style.alignItems = 'center';
logoWrapper.style.height = '100%';
var originalLogo = logoCell.querySelector('img');
if (originalLogo) {
var clonedLogo = originalLogo.cloneNode(true);
clonedLogo.style.maxWidth = '80px';
clonedLogo.style.maxHeight = '80px';
clonedLogo.style.objectFit = 'contain';
logoWrapper.appendChild(clonedLogo);
} else {
logoWrapper.innerHTML = logoCell.innerHTML;
var logoImg = logoWrapper.querySelector('img');
if (logoImg) {
logoImg.style.maxWidth = '80px';
logoImg.style.maxHeight = '80px';
logoImg.style.objectFit = 'contain';
}
}
rightCell.appendChild(logoWrapper);
footerRow.appendChild(zwyCell);
footerRow.appendChild(centerCell);
footerRow.appendChild(rightCell);
debugFooter('成功创建完美居中的三列布局页脚');
} catch (e) {
debugFooter('修改页脚时出错: ' + e.message);
footerRow.innerHTML = '';
footerRow.appendChild(textCell);
footerRow.appendChild(logoCell);
}
}
/* ========== 8. 初始化 ========== */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
// 1) 先藏旧菜单
hideMenuTd();
// 2) 如果有缓存,先用缓存占位,这样不抖
var cached = localStorage.getItem(MENU_STORAGE_KEY);
if (cached && cached.trim() !== '') {
injectMenuHtml(cached);
setupMenuSearch();
}
// 3) 无论有没有缓存,都去拉一次最新的
loadMenuFrom(getMenuCandidates(), 0);
// 页脚
modifyFooter();
});
} else {
hideMenuTd();
var cachedHtml = localStorage.getItem(MENU_STORAGE_KEY);
if (cachedHtml && cachedHtml.trim() !== '') {
injectMenuHtml(cachedHtml);
setupMenuSearch();
}
loadMenuFrom(getMenuCandidates(), 0);
modifyFooter();
}
})();
这个版本会比你现在的好在这几点:
如果你愿意改 HTML 源码的话,其实更简单:把首页 <td class="menu"> 里原来那一大坨静态菜单删掉,留个空的 <td class="menu"></td>,那连隐藏都不用了,肯定不抖。但考虑你现在是复用一个模板,这个 JS 方案已经能压住抖动了。
///var/www/html/Artocarpus/目录下
[root@localhost Artocarpus]# les menu.html
<!-- menu.html -->
<table width="1200px"><tr><td>
<div class="nav">
<ul style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
<div style="display: flex; flex-grow: 1;"> <!-- 添加flex-grow使菜单项占据更多空间 -->
<li style="width: 100px;"><a href="/Artocarpus/index.html">Home</a> </li> <!-- 增加宽度 -->
<li style="width: 130px;" onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="#">Genome</a> <!-- 增加宽度 -->
<ul>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArAl"><i>Artocarpus altilis</i></a></li>
<li onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHe"><i>Artocarpus heterophyllus</i></a>
<ul>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHe">ICRAFF_11314</a></li>
</ul>
</li>
<li onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHeCHN2"><i>Artocarpus heterophyllus</i></a>
<ul>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHeCHN2">S10</a></li>
</ul>
</li>
<li onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHeBD"><i>Artocarpus heterophyllus</i></a>
<ul>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHeBD">BARI_K3</a></li>
</ul>
</li>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArHi"><i>Artocarpus hirsutus</i></a></li>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArNa"><i>Artocarpus nanchuanensis</i></a></li>
<li><a href="../../cgi-bin/Artocarpus/genome.cgi?ID=ArCa"><i>Artocarpus camansi</i></a></li>
</ul>
</li>
<li style="width: 110px;" onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="#">Search</a> <!-- 增加宽度 -->
<ul>
<li><a href="/Artocarpus/search.htm">Basic Search</a></li>
<li><a href="/Artocarpus/mrnaSearch.htm">mRNA Search</a></li>
<li><a href="/Artocarpus/batchsearch.htm">Batch Search</a></li>
<li><a href="/Artocarpus/searchAnnotation.htm">Annotation Search</a></li>
<li><a href="/Artocarpus/IDtransfer.htm">ID Search</a></li>
</ul>
</li>
<li style="width: 110px;" onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="#">Tools</a> <!-- 增加宽度 -->
<ul>
<li><a href="/Artocarpus/RNA-seq.htm">Expression</a></li>
<li><a href="/Artocarpus/sequenceserver_blast.htm">BLAST</a></li>
<li><a href="/Artocarpus/jbrowse.htm">JBrowse</a></li>
<li><a href="/Artocarpus/synview.htm">Synteny Viewer</a></li>
<li><a href="/Artocarpus/GO_enrichment.htm">GO Enrichment</a></li>
<li><a href="/Artocarpus/KEGG_enrichment.htm">KEGG Enrichment</a></li>
<li><a href="/Artocarpus/ADH_Family.htm">ADH Family</a></li>
<li><a href="/Artocarpus/TPS_Family.htm">TPS Family</a></li>
<li><a href="/Artocarpus/BAHD_Family.htm">BAHD Family</a></li>
<li><a href="/Artocarpus/MISAweb.htm">MISAweb</a></li>
<li><a href="/Artocarpus/primer3.htm">Primer3</a></li>
</ul>
</li>
<li style="width: 130px;" onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="#">Document</a> <!-- 增加宽度 -->
<ul>
<li><a href="/Artocarpus/statistics.htm">Statistics</a></li>
<li><a href="/Artocarpus/link.htm">Resources</a></li>
<li><a href="/Artocarpus/Terms.htm">Terms</a></li>
</ul>
</li>
<li style="width: 130px;" onmouseover="displaySubMenu(this)" onmouseout="hideSubMenu(this)"><a href="#">Community</a> <!-- 增加宽度 -->
<ul>
<li><a href="/Artocarpus/manual.htm">Manual</a></li>
<!-- <li><a href="/Artocarpus/feedback.htm">Feedback</a></li> -->
<li><a href="/Artocarpus/download.htm">Download</a></li>
</ul>
</li>
</div>
<!-- 搜索框容器 - 右对齐 -->
<!-- 修改搜索框和按钮的容器样式 -->
<div id="menu-search-container" style="display: flex; align-items: center; height: 40px; padding: 0 10px; background: #78935d; margin-left: 10px; border-radius: 4px;">
<!-- menu.html -->
<form id="menu-search-form" method="get" style="display: flex; align-items: stretch; height: 25px;">
<!-- 搜索框 -->
<input class="selectstyle2" type="text" name="q" size="18" maxlength="200" value=""
placeholder="Site Search..."
style="height: 100%; padding: 0 8px; width: 160px; border-radius: 4px 0 0 4px;
border-right: none; box-sizing: border-box; line-height: normal;">
<!-- 搜索按钮 -->
<button type="button" class="button3" onclick="handleMenuSearch()"
style="height: 100%; width: 30px; padding: 0; border-radius: 0 4px 4px 0;
border-left: none; display: flex; align-items: center; justify-content: center;
box-sizing: border-box; background-color: #78935d;">
<span style="font-size: 16px;">🔍</span>
</button>
</form>
</div>
</ul>
</div>
</td></tr></table>
menu.html (END)