我有一个程序可以为用户生成 HTML 报告。这些报告有一些组件是用 jQuery 和 Bootstrap 制作的。它主要在某些标签上创建下拉组件,如下所示:
$(document).ready(function () {
$('.my-dropdown').each(function () {
// ...
$(this).collapse("hide");
// ...
}
}
问题是其中一些 HTML 报告有大量下拉菜单。有时,一个报告可能有超过 5,000 个下拉菜单,并导致整个页面在一分钟内没有响应。我已经验证删除上面的代码块修复了页面无响应(但失去了下拉切换功能)。
对于页面中下拉列表的数量,我们无能为力。用户更愿意等待几分钟让页面加载,因为在单个页面上找到他们需要的东西比在几个不同的页面上寻找他们需要的东西更容易。
由于无法将报告分解为不同的页面,是否有一种方法可以编写 JavaScript,以便在 JavaScript 创建组件时浏览器不会挂起?使用 defer
或 await
不起作用,因为它本质上不是大量正在执行的 Javascript,而是大量 HTML 导致大量 JavaScript 执行。
React 等框架或 Web Components 等更新的标准中是否有任何东西可以帮助解决这个问题?我并不期望浏览器能够在一秒钟内处理几千个组件,但如果有办法将无响应时间缩短到 15 秒左右,那会让用户感觉更好。
最佳答案
只是作为答案:
首先,这里的主要问题是默认情况下您的所有元素都是可见的,并且您通过 JavaScript 在数以千计的元素上调用 DOM 准备就绪的“折叠”。浏览器逐项关闭并重新计算元素流、重绘和渲染。
没有这样的框架可以帮助你——如果你用错了方法。
而是通过 CSS 默认隐藏所有元素,而不是通过 JS - 并且仅在单击(或其他任何方式)时打开目标元素。
其次,通过 AJAX 可以延迟加载 DOM 元素 - 取决于滚动,或者按照建议分页也是一个了不起的选择。
现代 web 编译器、框架,如 Svelte、Vue、React 可以帮助您跟踪当前数据的状态,但原理是相同的,框架或没有框架,在滚动时 AJAX 将触发 HTTP请求更多数据时,框架只会在数据更新时帮助毫不费力地更新 View 。 PS,15 秒对于业务定制的应用程序来说甚至太多了。
因此,对于现代浏览器而言,重述 5000 个元素应该不是主要问题。 只需反转您的逻辑。默认情况下使用 CSS 隐藏,仅在注册用户操作时使用折叠展开。
示例 1
表明如果处理得当,即使是 5000 个可折叠元素 或超过 80000 个 DOM 元素对浏览器来说也不是问题:
const tpl_collapse = (item) => `
<div class="Collapse">
<div class="Collapse-head">${item.i}. Character: &#x${item.hex};</div>
<div class="Collapse-body">
Hex: <code>&#x${item.hex};</code><br>
Num: <code>&#${item.i};</code><br>
CSS/JS: <code>\\${item.hex}</code><br>
</div>
</div>`;
const data = [...Array(10001).keys()].map((_, i) => ({i,hex: i.toString(16)}));
const accordions = data.map(tpl_collapse).join("");
const EL_container = document.querySelector("#container");
EL_container.innerHTML = accordions;
EL_container.addEventListener("click", (ev) => {
if (ev.target.closest(".Collapse-head")) {
ev.target.closest(".Collapse").classList.toggle("expanded");
}
})
.Collapse {
border: 1px solid #eee;
}
.Collapse-head {
cursor: pointer;
padding: 5px 10px;
}
.Collapse-body {
display: none;
background: #eee;
padding: 5px 10px;
}
.Collapse.expanded .Collapse-head {
background: #0bf;
}
.Collapse.expanded .Collapse-body {
display: block;
}
<div id="container"></div>
示例 2
动态加载 N 个可折叠元素:
如果您眼力好,您可能已经注意到上面的操作“仅” 几秒钟(在相对较好的台式机上)。
这也是 Not Acceptable 。因此,让我们尝试使用动态方法来获取和创建元素!
一种方法是将 Intersection Observer API 分配给最底部的元素.滚动页面和/或该元素进入视口(viewport)后,只需加载下一页(在本例中为 100 个项目集)。
您可以在此处使用 AJAX,但我将使用纯代码生成来达到此目的。无论如何,这是一个概念证明。
注意立即呈现(不再需要等待时间):
let page = 1; // Current page
const itemsPerPage = 100; // Items per page
const inViewport = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting && +entry.target.dataset.page === page) {
page += 1; // Increment page!
fetchPage(); // Fetch next page
}
});
};
const observer = new IntersectionObserver(inViewport);
const EL_container = document.querySelector("#container");
const NewEL = (tag, prop) => Object.assign(document.createElement(tag), prop);
const EL_item = (item) => {
const EL = NewEL("div", {
className: "Collapse",
innerHTML: `<div class="Collapse-head">${item.index}. Character: &#x${item.hex};</div>
<div class="Collapse-body">
Hex: <code>&#x${item.hex};</code><br>
Num: <code>&#${item.index};</code><br>
CSS/JS: <code>\\${item.hex}</code><br>
</div>`
});
EL.dataset.page = page; // Associate page to data-page attribute
// Observe only last element
if (!((item.index + 1) % itemsPerPage)) observer.observe(EL);
return EL;
}
// Just to programmatically emulate an AJAX call of N items in a page-range
const fetchPage = () => {
const DF_items = [...Array(itemsPerPage).keys()].reduce((DF, index) => {
const i = (page - 1) * itemsPerPage + index;
DF.append(EL_item({index: i,hex: i.toString(16), page}));
return DF;
}, new DocumentFragment());
EL_container.append(DF_items);
};
EL_container.addEventListener("click", (ev) => {
if (!ev.target.closest(".Collapse-head")) return;
ev.target.closest(".Collapse").classList.toggle("expanded");
});
// INIT!
fetchPage();
.Collapse {
border: 1px solid #eee;
}
.Collapse-head {
cursor: pointer;
padding: 5px 10px;
}
.Collapse-body {
display: none;
background: #eee;
padding: 5px 10px;
}
.Collapse.expanded .Collapse-head {
background: #0bf;
}
.Collapse.expanded .Collapse-body {
display: block;
}
<div id="container"></div>
示例 3
分页是解决这个问题最常用的方法
let page = 1; // Current page
const itemsPerPage = 50; // Items per page
const EL_container = document.querySelector("#container");
const EL_prev = document.querySelector("#prev");
const EL_next = document.querySelector("#next");
const EL_goto = document.querySelector("#goto");
const NewEL = (tag, prop) => Object.assign(document.createElement(tag), prop);
const EL_item = (item) => {
return NewEL("div", {
className: "Collapse",
innerHTML: `<div class="Collapse-head">${item.index}. Character: &#x${item.hex};</div>
<div class="Collapse-body">
Hex: <code>&#x${item.hex};</code><br>
Num: <code>&#${item.index};</code><br>
CSS/JS: <code>\\${item.hex}</code><br>
</div>`
});
}
const fetchPage = () => {
page = Math.abs(page) || 1; // Fix for negative or 0 page number
const DF_items = [...Array(itemsPerPage).keys()].reduce((DF, index) => {
const i = (page - 1) * itemsPerPage + index;
DF.append(EL_item({index: i, hex: i.toString(16), page }));
return DF;
}, new DocumentFragment());
EL_container.innerHTML = "";
EL_container.append(DF_items);
EL_goto.value = page;
};
EL_container.addEventListener("click", (ev) => {
if (!ev.target.closest(".Collapse-head")) return;
ev.target.closest(".Collapse").classList.toggle("expanded");
});
EL_goto.addEventListener("input", () => {
page = parseInt(EL_goto.value, 10);
if (page) fetchPage();
});
EL_prev.addEventListener("click", () => {
page -= 1;
fetchPage();
});
EL_next.addEventListener("click", () => {
page += 1;
fetchPage();
});
// INIT!
fetchPage();
#container {
height: calc(100vh - 40px);
overflow: auto;
}
.Collapse {
border: 1px solid #eee;
}
.Collapse-head {
cursor: pointer;
padding: 5px 10px;
}
.Collapse-body {
display: none;
background: #eee;
padding: 5px 10px;
}
.Collapse.expanded .Collapse-head {
background: #0bf;
}
.Collapse.expanded .Collapse-body {
display: block;
}
#goto {
text-align:center;
width: 100px;
}
<button id="prev" type="button">PREV</button>
<input id="goto" type="number" value="1" min="1">
<button id="next" type="button">NEXT</button>
<div id="container"></div>
关于javascript - 如何防止Javascript执行导致页面因大量HTML而无响应?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68400301/