// ==UserScript==
// @name E-HENTAI-VIEW-ENHANCE
// @name:zh-CN E-HENTAI-VIEW-ENHANCE
// @namespace https://github.com/MapoMagpie/eh-view-enhance
// @homepageURL https://github.com/MapoMagpie/eh-view-enhance
// @version 3.0.4
// @license MIT
// @description e-hentai.org better viewer, All of thumbnail images exhibited in grid, and show the best quality image.
// @description:zh-CN 强化E绅士看图体验
// @author MapoMagpie
// @author zsyjklive.cn
// @match https://exhentai.org/g/*
// @match https://e-hentai.org/g/*
// @connect hath.network
// @icon https://exhentai.org/favicon.ico
// @grant GM.xmlHttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @downloadURL none
// ==/UserScript==
const regulars = {
// 有压缩的大图地址
normal: /\/,
// 大图重载地址
nlValue: /\/,
// 是否开启自动多页查看器
isMPV: /https?:\/\/e[-x]hentai.org\/mpv\/\w+\/\w+\/#page\w/,
// 多页查看器图片列表提取
mpvImageList: /\{"n":"(.*?)","k":"(\w+)","t":"(.*?)".*?\}/g,
};
//==================面向对象,图片获取器IMGFetcher,图片获取器调用队列IMGFetcherQueue=====================START
class IMGFetcher {
constructor(node) {
this.node = node;
this.imgElement = node.childNodes[0];
this.pageUrl = this.imgElement.getAttribute("ahref");
//当前处理阶段,1: 获取大图地址 2: 获取大图数据 3: 加载完成
this.stage = 1;
this.tryTime = 0;
this.lock = false;
this.rendered = false;
this.blobData = undefined;
this.title = this.imgElement.getAttribute("title");
/**
* 下载状态
* total: 图片数据量
* loaded: 已下载的数据量
* readyState: 0未开始下载; 1-3下载中; 4下载完毕
* rate:下载速率
*/
this.downloadState = { total: 100, loaded: 0, readyState: 0, rate: 0 };
/**
* 当获取完成时的回调函数,从其他地方进行事件注册
*/
this.onFinishedEventContext = new Map();
this.fetchOriginal = false;
}
// 刷新下载状态
setDownloadState(newDLState) {
const increased = (newDLState.loaded || 0) - this.downloadState.loaded;
this.downloadState.rate = increased;
this.downloadState = { ...this.downloadState, ...newDLState };
if (this.downloadState.readyState === 4) {
if (this.downloadBar) {
this.downloadBar.remove();
}
return;
}
if (!this.downloadBar) {
this.downloadBar = document.createElement("div");
this.downloadBar.classList.add("downloadBar");
this.downloadBar.innerHTML = `
`;
this.node.appendChild(this.downloadBar);
}
[...this.downloadBar.childNodes].filter((node) => node.nodeType === 1)[0].value = (this.downloadState.loaded / this.downloadState.total) * 100;
downloaderCanvas.drawDebouce();
}
async start(index) {
if (this.lock) return;
this.lock = true;
try {
this.changeStyle("add");
if (!(await this.fetchImg())) {
throw new Error("图片获取器失败,中止获取!");
}
this.changeStyle("remove", "success");
this.onFinishedEventContext.forEach((callback) => callback(index, this));
} catch (error) {
this.changeStyle("remove", "failed");
evLog(`图片获取器获取失败:`, error);
} finally {
this.lock = false;
}
}
onFinished(eventId, callback) {
this.onFinishedEventContext.set(eventId, callback);
}
async fetchImg() {
switch (this.stage) {
case 1:
return await this.stage1FetchUrl();
case 2:
return await this.stage2FetchImg();
case 3:
return this.stage3Done();
}
}
// 阶段一:获取大图的地址
async stage1FetchUrl() {
try {
this.changeStyle("add");
if (!(await this.fetchBigImageUrl())) {
evLog("获取大图地址失败");
return false;
}
//成功获取到大图的地址后,将本图片获取器的状态修改为1,表示大图地址已经成功获取到
if (!this.bigImageUrl) {
evLog("大图地址不存在!");
return false;
}
this.stage = 2;
return this.fetchImg();
} catch (error) {
evLog(`获取大图地址时出现了异常:`, error);
return false;
}
}
// 阶段二:获取大图数据
async stage2FetchImg() {
this.setDownloadState(this.downloadState);
try {
let ok = false;
if (conf["disableDownload"]) {
ok = await this.fetchBigImageWeird();
} else {
ok = await this.fetchBigImage();
}
if (!ok) {
throw new Error(`获取大图数据失败,大图地址:${this.bigImageUrl}`);
}
this.stage = 3;
return this.fetchImg();
} catch (error) {
evLog(`获取大图数据时出现了异常:`, error);
//如果失败了,则进行重试,重试会进行2次
++this.tryTime;
this.stage = 1;
// 重试2次后,直接失败,避免无限请求
evLog(`当前重试第${this.tryTime}次`);
if (this.tryTime > 2) {
return false;
}
return this.fetchImg();
}
}
// 阶段三:获取器结束
stage3Done() {
return true;
}
render() {
if (this.rendered) return;
// this.imgElement.style.height = "auto";
this.imgElement.src = this.imgElement.getAttribute("asrc");
this.rendered = true;
}
//立刻将当前元素的src赋值给大图元素
setNow(index) {
if (this.stage === 3) {
this.onFinishedEventContext.forEach((callback) => callback(index, this));
} else {
bigImageElement.src = this.imgElement.getAttribute("asrc");
pageHandler("fetching");
}
pageHandler("updateCurrPage", index + 1);
}
/**
* 获取大图地址
* @param {是否为重新换源状态,为true时,不再进行新的换源动作,避免无限递归} originChanged
* @return boolean
*/
async fetchBigImageUrl(originChanged) {
let text = "";
try {
text = await window.fetch(this.pageUrl).then(resp => resp.text());
} catch (error) {
evLog("获取大图页面内容失败!", error);
}
if (!text) return false;
//抽取最佳质量的图片的地址
if (conf["fetchOriginal"] || this.fetchOriginal) {
const matchs = regulars["original"].exec(text);
if (matchs && matchs.length > 0) {
this.bigImageUrl = matchs[1].replace(/&/g, "&");
} else {
const normalMatchs = regulars["normal"].exec(text);
if (normalMatchs == null || normalMatchs.length == 0) {
evLog("获取大图地址失败,内容为: ", text);
return false;
} else {
this.bigImageUrl = normalMatchs[1];
}
}
return true;
}
//抽取正常的有压缩的大图地址
if (this.tryTime === 0 || originChanged) {
this.bigImageUrl = regulars["normal"].exec(text)[1];
return true;
} else { //如果是重试状态,则进行换源
const nlValue = regulars["nlValue"].exec(text)[1];
this.pageUrl += ((this.pageUrl + "").indexOf("?") > -1 ? "&" : "?") + "nl=" + nlValue;
evLog(`获取到重试地址:${this.pageUrl}`);
return await this.fetchBigImageUrl(true);
}
}
async fetchBigImageWeird() {
const imgFetcher = this;
return new Promise(async (resolve) => {
imgFetcher.imgElement.onloadend = () => {
window.clearTimeout(imgFetcher.timeoutId);
imgFetcher.setDownloadState({ total: 1, loaded: 1, readyState: 4 });
resolve(true);
};
imgFetcher.imgElement.onloadstart = () => {
imgFetcher.timeoutId = window.setTimeout(() => {
imgFetcher.imgElement.onloadstart = null;
imgFetcher.imgElement.onloadend = null;
imgFetcher.imgElement.src = this.imgElement.getAttribute("asrc");
resolve(false);
}, conf["timeout"] * 1000);
};
imgFetcher.blobUrl = imgFetcher.bigImageUrl;
imgFetcher.imgElement.src = imgFetcher.blobUrl;
imgFetcher.rendered = true;
});
}
async fetchBigImage() {
const imgFetcher = this;
return new Promise(async (resolve) => {
xhrWapper(imgFetcher.bigImageUrl, "blob", {
onload: function (response) {
let data = response.response;
if (!(data instanceof Blob)) throw new Error("未下载到有效的数据!");
imgFetcher.blobData = data;
imgFetcher.blobUrl = URL.createObjectURL(data);
imgFetcher.imgElement.src = imgFetcher.blobUrl;
imgFetcher.rendered = true;
imgFetcher.setDownloadState({ total: response.total, loaded: response.loaded, readyState: response.readyState });
resolve(true);
},
onerror: function (response) {
evLog("加载大图失败:", response);
resolve(false);
},
ontimeout: function (response) {
evLog("加载大图超时:", response);
resolve(false);
},
onprogress: function (response) {
imgFetcher.setDownloadState({ total: response.total, loaded: response.loaded, readyState: response.readyState });
},
});
});
}
changeStyle(action, fetchStatus) {
if (action === "remove") {
//当获取到内容,或者获取失败,则移除本缩略图的边框效果
this.imgElement.classList.remove("fetching");
} else if (action === "add") {
//给当前缩略图元素添加一个获取中的边框样式
this.imgElement.classList.add("fetching");
}
if (fetchStatus === "success") {
this.imgElement.classList.add("fetched");
this.imgElement.classList.remove("fetch-failed");
} else if (fetchStatus === "failed") {
this.imgElement.classList.add("fetch-failed");
this.imgElement.classList.remove("fetched");
}
}
}
class IMGFetcherQueue extends Array {
constructor() {
super();
//可执行队列
this.executableQueue = [];
//当前的显示的大图的图片请求器所在的索引
this.currIndex = 0;
//已经完成加载的
this.finishedIndex = [];
this.debouncer = new Debouncer();
}
isFinised() {
return this.finishedIndex.length === this.length;
}
push(...IFs) {
IFs.forEach((imgFetcher) => imgFetcher.onFinished("QUEUE-REPORT", (index) => this.finishedReport(index)));
super.push(...IFs);
}
unshift(...IFs) {
IFs.forEach((imgFetcher) => imgFetcher.onFinished("QUEUE-REPORT", (index) => this.finishedReport(index)));
super.unshift(...IFs);
}
do(start, oriented) {
oriented = oriented || "next";
//边界约束
this.currIndex = this.fixIndex(start, oriented);
if (downloader.downloading) {
//立即加载和展示当前的元素
this[this.currIndex].setNow(this.currIndex);
return;
}
//立即中止空闲加载器
idleLoader.abort(this.currIndex);
//立即加载和展示当前的元素
this[this.currIndex].setNow(this.currIndex);
//从当前索引开始往后,放入指定数量的图片获取器,如果该图片获取器已经获取完成则向后延伸.
//如果最后放入的数量为0,说明已经没有可以继续执行的图片获取器,可能意味着后面所有的图片都已经加载完毕,也可能意味着中间出现了什么错误
if (!this.pushInExecutableQueue(oriented)) return;
/* 300毫秒的延迟,在这300毫秒的时间里,可执行队列executableQueue可能随时都会变更,100毫秒过后,只执行最新的可执行队列executableQueue中的图片请求器
在对大图元素使用滚轮事件的时候,由于速度非常快,大量的IMGFetcher图片请求器被添加到executableQueue队列中,如果调用这些图片请求器请求大图,可能会被认为是爬虫脚本
因此会有一个时间上的延迟,在这段时间里,executableQueue中的IMGFetcher图片请求器会不断更替,300毫秒结束后,只调用最新的executableQueue中的IMGFetcher图片请求器。
*/
this.debouncer.addEvent("IFQ-EXECUTABLE", () => {
this.executableQueue.forEach((imgFetcherIndex) => this[imgFetcherIndex].start(imgFetcherIndex));
}, 300);
}
//等待图片获取器执行成功后的上报,如果该图片获取器上报自身所在的索引和执行队列的currIndex一致,则改变大图
finishedReport(index) {
const imgFetcher = this[index];
if (imgFetcher.stage !== 3) return;
if (downloader) {
if (this.finishedIndex.indexOf(index) < 0) {
downloader.addToDownloadZip(imgFetcher);
}
}
this.pushFinishedIndex(index);
if (downloader && downloader.downloading && this.isFinised()) {
downloader.download();
}
pageHandler("updateFinished", this.finishedIndex.length);
evLog(`第${index + 1}张完成,大图所在第${this.currIndex + 1}张`);
if (index !== this.currIndex) return;
if (!conf.keepScale) {
//是否保留缩放
bigImageElement.style.width = "100%";
bigImageElement.style.height = "100%";
bigImageElement.style.top = "0px";
}
pageHandler("fetched");
bigImageElement.src = imgFetcher.blobUrl;
this.scrollTo(index);
}
scrollTo(index) {
const imgFetcher = this[index];
let scrollTo = imgFetcher.node.offsetTop - window.screen.availHeight / 3;
scrollTo = scrollTo <= 0 ? 0 : scrollTo >= fullViewPlane.scrollHeight ? fullViewPlane.scrollHeight : scrollTo;
fullViewPlane.scrollTo({ top: scrollTo, behavior: "smooth" });
}
//如果开始的索引小于0,则修正索引为0,如果开始的索引超过队列的长度,则修正索引为队列的最后一位
fixIndex(start) {
return start < 0 ? 0 : start > this.length - 1 ? this.length - 1 : start;
}
/**
* 将方向前|后 的未加载大图数据的图片获取器放入待加载队列中
* 从当前索引开始,向后或向前进行遍历,
* 会跳过已经加载完毕的图片获取器,
* 会添加正在获取大图数据或未获取大图数据的图片获取器到待加载队列中
* @param {方向 前后} oriented
* @returns 是否添加成功
*/
pushInExecutableQueue(oriented) {
//把要执行获取器先放置到队列中,延迟执行
this.executableQueue = [];
for (let count = 0, index = this.currIndex; this.pushExecQueueSlave(index, oriented, count); oriented === "next" ? ++index : --index) {
if (this[index].stage === 3) continue;
this.executableQueue.push(index);
count++;
}
return this.executableQueue.length > 0;
}
// 如果索引已到达边界且添加数量在配置最大同时获取数量的范围内
pushExecQueueSlave(index, oriented, count) {
return ((oriented === "next" && index < this.length) || (oriented === "prev" && index > -1)) && count < conf["threads"];
}
findIndex(imgElement) {
for (let index = 0; index < this.length; index++) {
if (this[index] instanceof IMGFetcher && this[index].imgElement === imgElement) {
return index;
}
}
return 0;
}
pushFinishedIndex(index) {
const fd = this.finishedIndex;
if (fd.length === 0) {
fd.push(index);
return;
}
for (let i = 0; i < fd.length; i++) {
if (index === fd[i]) return;
if (index < fd[i]) {
fd.splice(i, 0, index);
return;
}
}
fd.push(index);
}
}
//空闲自加载
class IdleLoader {
constructor(IFQ) {
//图片获取器队列
this.queue = IFQ;
//当前处理的索引列表
this.processingIndexList = [0];
this.lockVer = 0;
//中止后的用于重新启动的延迟器的id
this.restartId;
this.maxWaitMS = 1000;
this.minWaitMS = 300;
}
async start(lockVer) {
evLog("空闲自加载启动:" + this.processingIndexList.toString());
//如果被中止了,则停止
if (this.lockVer != lockVer || !conf["autoLoad"]) return;
// 如果已经没有要处理的列表
if (this.processingIndexList.length === 0) {
return;
}
for (let i = 0; i < this.processingIndexList.length; i++) {
const processingIndex = this.processingIndexList[i];
// 获取索引所对应的图片获取器,并添加完成事件,当图片获取完成时,重新查找新的可获取的图片获取器,并递归
const imgFetcher = this.queue[processingIndex];
// 当图片获取器还没有获取图片时,则启动图片获取器
if (imgFetcher.lock || imgFetcher.stage === 3) {
continue;
}
imgFetcher.onFinished("IDLE-REPORT", () => {
this.wait().then(() => {
this.checkProcessingIndex(i);
this.start(lockVer);
});
});
imgFetcher.start(processingIndex);
}
}
/**
* @param {当前处理列表中的位置} i
*/
checkProcessingIndex(i) {
const processedIndex = this.processingIndexList[i];
let restart = false;
// 从图片获取器队列中获取一个还未获取图片的获取器所对应的索引,如果不存在则从处理列表中删除该索引,缩减处理列表
for (let j = processedIndex, max = this.queue.length - 1; j <= max; j++) {
const imgFetcher = this.queue[j];
// 如果图片获取器正在获取或者图片获取器已完成获取,
if (imgFetcher.stage === 3 || imgFetcher.lock) {
if (j === max && !restart) {
j = -1;
max = processedIndex - 1;
restart = true;
}
continue;
}
this.processingIndexList[i] = j;
return;
}
this.processingIndexList.splice(i, 1);
}
async wait() {
const { maxWaitMS, minWaitMS } = this;
return new Promise(function (resolve) {
const time = Math.floor(Math.random() * maxWaitMS + minWaitMS);
window.setTimeout(() => resolve(), time);
});
}
abort(newIndex) {
this.lockVer++;
evLog(`终止空闲自加载, 下次将从第${this.processingIndexList[0] + 1}张开始加载`);
if (!conf.autoLoad) return;
// 中止空闲加载后,会在等待一段时间后再次重启空闲加载
window.clearTimeout(this.restartId);
this.restartId = window.setTimeout(() => {
this.processingIndexList = [newIndex];
this.checkProcessingIndex(0);
this.start(this.lockVer);
}, conf["restartIdleLoader"]);
}
}
//页获取器,可获取下一个列表页,以及下一个图片页
class PageFetcher {
constructor(IFQ, idleLoader) {
this.queue = IFQ;
//所有页的地址
this.pageUrls = [];
//当前页所在的索引
this.currPage = 0;
//每页的图片获取器列表,用于实现懒加载
this.imgAppends = { prev: [], next: [] };
//平均高度,用于渲染未加载的缩略图,单位px
this.idleLoader = idleLoader;
this.fetched = false;
}
async init() {
this.initPageUrls();
await this.initPageAppend();
this.loadAllPageImg();
this.renderCurrView(fullViewPlane.scrollTop, fullViewPlane.clientHeight);
}
initPageUrls() {
const pager = document.querySelector(".gtb");
if (!pager) {
throw new Error("未获取到分页元素!");
}
const tds = pager.querySelectorAll("td");
if (!tds || tds.length == 0) {
throw new Error("未获取到有效的分页元素!");
}
const curr = [...tds].filter((p) => p.className.indexOf("ptds") != -1)[0];
const currPageNum = PageFetcher.findPageNum(!curr ? "" : curr.firstElementChild.href);
const lastPage = PageFetcher.findPageNum(tds[tds.length - 2].firstElementChild.href);
const firstPageUrl = tds[1].firstElementChild.href;
this.pageUrls.push(firstPageUrl);
for (let i = 1; i <= lastPage; i++) {
this.pageUrls.push(`${firstPageUrl}?p=${i}`);
if (i == currPageNum) {
this.currPage = i;
}
}
evLog("所有页码地址加载完毕:", this.pageUrls);
}
async initPageAppend() {
for (let i = 0; i < this.pageUrls.length; i++) {
const pageUrl = this.pageUrls[i];
if (i == this.currPage) {
await this.appendDefaultPage(pageUrl);
} else {
const oriented = i < this.currPage ? "prev" : "next";
this.imgAppends[oriented].push(async () => await this.appendPageImg(pageUrl, oriented));
}
}
}
async loadAllPageImg() {
if (this.fetched) return;
for (let i = 0; i < this.imgAppends["next"].length; i++) {
const executor = this.imgAppends["next"][i];
await executor();
}
for (let i = this.imgAppends["prev"].length - 1; i > -1; i--) {
const executor = this.imgAppends["prev"][i];
await executor();
}
}
static findPageNum(pageUrl) {
if (pageUrl) {
const arr = pageUrl.split("?");
if (arr && arr.length > 1) {
return parseInt(/p=(\d*)/.exec(arr[1]).pop());
}
}
return 0;
}
async appendDefaultPage(pageUrl) {
const doc = await this.fetchDocument(pageUrl);
const imgNodeList = await this.obtainImageNodeList(doc);
const IFs = imgNodeList.map((imgNode) => new IMGFetcher(imgNode));
fullViewPlane.firstElementChild.nextElementSibling.after(...imgNodeList);
this.queue.push(...IFs);
pageHandler("updateTotal", this.queue.length);
}
async appendPageImg(pageUrl, oriented) {
try {
const doc = await this.fetchDocument(pageUrl);
const imgNodeList = await this.obtainImageNodeList(doc);
const IFs = imgNodeList.map((imgNode) => new IMGFetcher(imgNode));
switch (oriented) {
case "prev":
fullViewPlane.firstElementChild.nextElementSibling.after(...imgNodeList);
this.queue.unshift(...IFs);
this.idleLoader.processingIndexList[0] += IFs.length;
this.queue.scrollTo(this.idleLoader.processingIndexList[0]);
break;
case "next":
fullViewPlane.lastElementChild.after(...imgNodeList);
this.queue.push(...IFs);
break;
}
pageHandler("updateTotal", this.queue.length);
return true;
} catch (error) {
evLog(`从下一页或上一页中提取图片元素时出现了错误!`, error);
return false;
}
}
//从文档的字符串中创建缩略图元素列表
async obtainImageNodeList(docString) {
const list = [];
if (!docString) return list;
const domParser = new DOMParser();
const doc = domParser.parseFromString(docString, "text/html");
const aNodes = doc.querySelectorAll("#gdt a");
if (!aNodes || aNodes.length == 0) {
evLog("wried to get a nodes from document, but failed!");
return list;
}
const aNode = aNodes[0];
// make node template
const imgNodeTemplate = document.createElement("div");
imgNodeTemplate.classList.add("img-node");
const imgTemplate = document.createElement("img");
imgTemplate.setAttribute("decoding", "async");
imgTemplate.style.height = "auto";
imgTemplate.setAttribute("src", "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==");
imgNodeTemplate.appendChild(imgTemplate);
// MPV
if (regulars.isMPV.test(aNode.href)) {
const mpvDoc = await this.fetchDocument(aNode.href);
const matchs = mpvDoc.matchAll(regulars.mpvImageList);
const gid = location.pathname.split("/")[2];
let i = 0;
for (const match of matchs) {
i++;
const newImgNode = imgNodeTemplate.cloneNode(true);
const newImg = newImgNode.firstChild;
newImg.setAttribute("title", match[1]);
newImg.setAttribute("ahref", `${location.origin}/s/${match[2]}/${gid}-${i}`);
newImg.setAttribute("asrc", match[3].replaceAll("\\", ""));
newImg.addEventListener("click", showBigImageEvent);
list.push(newImgNode);
}
this.fetched = true;
}
// normal
else {
for (const aNode of aNodes) {
const imgNode = aNode.querySelector("img");
const newImgNode = imgNodeTemplate.cloneNode(true);
const newImg = newImgNode.firstChild;
newImg.setAttribute("ahref", aNode.href);
newImg.setAttribute("asrc", imgNode.src);
newImg.setAttribute("title", imgNode.getAttribute("title"));
newImg.addEventListener("click", showBigImageEvent);
list.push(newImgNode);
}
}
return list;
}
//通过地址请求该页的文档
async fetchDocument(pageUrl) {
return await window.fetch(pageUrl).then((response) => response.text());
}
/**
*当滚动停止时,检查当前显示的页面上的是什么元素,然后渲染图片
* @param {当前滚动位置} currTop
* @param {窗口高度} clientHeight
*/
renderCurrView(currTop, clientHeight) {
// 当前视图,即浏览器显示的内容、滚动到的区域
// 当前视图上边位置
const viewTop = currTop;
// 当前视图下边位置
const viewButtom = currTop + clientHeight;
const colCount = conf["colCount"];
const IFs = this.queue;
let startRander = 0;
let endRander = 0;
for (let i = 0, findBottom = false; i < IFs.length; i += colCount) {
const { node } = IFs[i];
// 查询最靠近当前视图上边的缩略图索引
// 缩略图在父元素的位置 - 当前视图上边位置 = 缩略图与当前视图上边的距离,如果距离 >= 0,说明缩略图在当前视图内
if (!findBottom) {
const distance = node.offsetTop - viewTop;
if (distance >= 0) {
startRander = Math.max(i - colCount, 0);
findBottom = true;
}
}
// 查询最靠近当前试图下边的缩略图索引
if (findBottom) {
// 当前视图下边的位置 - (缩略图在父元素的位置 + 缩略图的高度) = 缩略图与当前视图下边的距离,如果距离 <= 0 说明缩略图在当前视图内,但仍有部分图片内容在视图外,当然此缩略图之后的图片也符合这样的条件,但此为顺序遍历
const distance = viewButtom - (node.offsetTop + node.offsetHeight);
endRander = Math.min(i + colCount, IFQ.length);
if (distance <= 0) break;
}
}
evLog(`要渲染的范围是:${startRander + 1}-${endRander + 1}`);
IFs.slice(startRander, endRander + 1).forEach((f) => f.render());
}
}
//防反跳,延迟执行,如果有新的事件则重置延迟时间,到达延迟时间后,只执行最后一次的事件
class Debouncer {
constructor() {
this.tids = {};
}
addEvent(id, event, timeout) {
window.clearTimeout(this.tids[id]);
this.tids[id] = window.setTimeout(event, timeout);
}
}
//图片获取器调用队列
const IFQ = new IMGFetcherQueue();
//空闲自加载器
const idleLoader = new IdleLoader(IFQ);
//页加载器
const PF = new PageFetcher(IFQ, idleLoader);
//==================面向对象,图片获取器IMGFetcher,图片获取器调用队列IMGFetcherQueue=====================START
//========================================配置管理器=================================================START
const signal = { first: true };
let conf = JSON.parse(window.localStorage.getItem("cfg_"));
//获取宽度
const screenWidth = window.screen.availWidth;
if (!conf || conf.version !== "3.0.4") {
//如果配置不存在则初始化一个
let colCount = screenWidth > 2500 ? 8 : screenWidth > 1900 ? 7 : 5;
conf = {
backgroundImage: `data:image/png;base64,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`,
colCount: colCount, //每行显示的数量
followMouse: false, //大图是否跟随鼠标
keepScale: false, //是否保留缩放
autoLoad: true, //是否启用空闲加载器
fetchOriginal: false, //是否获取最佳质量的图片
restartIdleLoader: 8000, //中止空闲加载器后的重新启动时间
threads: 3, //同时加载的图片数量
downloadThreads: 3, //同时下载的图片数量
timeout: 16, //超时时间(秒),默认16秒
version: "3.0.4", //配置版本
debug: true, // 是否打印控制台日志
first: true, // 是否初次使用脚本
disableDownload: false, // 禁用下载功能
};
window.localStorage.setItem("cfg_", JSON.stringify(conf));
}
const i18in = {
download: ["DL", "下载"],
config: ["CONF", "配置"],
collapse: ["FOLD", "收起"],
columns: ["Columns", "每行数量"],
maxPreloadThreads: ["PreloadThreads", "最大同时加载"],
maxDownloadThreads: ["DonloadThreads", "最大同时下载"],
timeout: ["Timeout(second)", "超时时间(秒)"],
bestQuality: ["RawImage", "最佳质量"],
autoLoad: ["AutoLoad", "自动加载"],
followMouse: ["FollowMouse", "大图跟随鼠标"],
keepScale: ["KeepScale", "保持缩放"],
maxPreloadThreadsTooltip: ["Max Preload Threads", "大图浏览时,每次滚动到下一张时,预加载的图片数量,大于1时体现为越看加载的图片越多,将提升浏览体验。"],
maxDownloadThreadsTooltip: ["Max Download Threads, suggest: <5", "下载模式下,同时加载的图片数量,建议小于等于5"],
bestQualityTooltip: ["enable will download the original source, cost more traffic and quotas", "启用后,将加载未经过压缩的原档文件,下载打包后的体积也与画廊所标体积一致。
注意:这将消耗更多的流量与配额,请酌情启用。"],
autoLoadTooltip: ["", "进入本脚本的浏览模式后,即使不浏览也会一张接一张的加载图片。直至所有图片加载完毕。"],
forceDownload: ["Take Loaded", "强制下载已加载的"],
startDownload: ["Start Download", "开始下载"],
downloading: ["Downloading...", "下载中..."],
downloaded: ["Downloaded", "下载完成"],
originalCheck: ["Enable RawImage Transient", "未启用最佳质量图片,点击此处临时开启最佳质量"],
help: [`