您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
KamePT种子列表无限下拉瀑布流视图(描述不能与名称相同, 乐)
当前为
// ==UserScript== // @name KamePT种子列表无限下拉瀑布流视图 // @name:en KamePT_waterfall_torrent // @namespace https://github.com/KesaubeEire/PT_TorrentList_Masonry // @version 0.3.0 // @description KamePT种子列表无限下拉瀑布流视图(描述不能与名称相同, 乐) // @description:en KamePT torrent page waterfall view // @author Kesa // @match https://kamept.com/torrents.php* // @match https://kamept.com/special.php* // @icon https://kamept.com/favicon.ico // @grant none // @license MIT // ==/UserScript== // ------------------ 下面是声明区 // FIXME: // 0. 一些顶层设计 -------------------------------------------------------------------------------------- // |-- 0.1 顶层参数&对象 /** 获取主题背景色 */ const mainOuterDOM = document.querySelector("table.mainouter"); const themeColor = window.getComputedStyle(mainOuterDOM)["background-color"]; console.log("背景颜色:", themeColor); /** kame 域名 */ const domain = "https://kamept.com/"; /** 瀑布流对象 */ let masonry; window.masonry = masonry; /** 瀑布流卡片相关参数顶层对象 */ const CARD = { /** 瀑布流卡片宽度 */ CARD_WIDTH: 200, /** 瀑布流卡片边框宽度 -> 这个2是真值, 但是边框好像是会随着分辨率和缩放变化, 给高有利大分辨率, 给低有利于小分辨率 */ CARD_BORDER: 3, /** 瀑布流卡片索引 */ CARD_INDEX: 0, }; window.CARD_WIDTH = CARD.CARD_WIDTH; /** 翻页相关参数顶层对象 */ const PAGE = { /** 翻页: 底部检测时间间隔 */ GAP: 900, /** 翻页: 底部检测视点与底部距离 */ DISTANCE: 300, /** 翻页: 是否为初始跳转页面 */ IS_ORIGIN: true, /** 翻页: 当前页数 */ PAGE_CURRENT: 0, /** 翻页: 下一页数 */ PAGE_NEXT: 0, /** 翻页: 下一页的链接 */ NEXT_URL: "", }; /** 网站图标链接 */ const ICON = { /** 大小图标 */ SIZE: '<img class="size" src="pic/trans.gif" style=" transform: translateY(-0.4px);" alt="size" title="大小">', /** 评论图标 */ COMMENT: '<img class="comments" src="pic/trans.gif" alt="comments" title="评论数">', /** 上传人数图标 */ SEEDERS: '<img class="seeders" src="pic/trans.gif" alt="seeders" title="种子数">', /** 下载人数图标 */ LEECHERS: '<img class="leechers" src="pic/trans.gif" alt="leechers" title="下载数">', /** 已完成人数图标 */ SNATCHED: '<img class="snatched" src="pic/trans.gif" alt="snatched" title="完成数">', /** 下载图标 */ DOWNLOAD: '<img class="download" src="pic/trans.gif" style=" transform: translateY(1px);" alt="download" title="下载本种">', /** 未收藏图标 */ COLLET: '<img class="delbookmark" src="pic/trans.gif" alt="Unbookmarked" title="收藏">', /** 已收藏图标 */ COLLETED: '<img class="bookmark" src="pic/trans.gif" alt="Bookmarked">', }; // |-- 0.1 顶层方法 /** * 将 种子列表dom 的信息变为 json对象列表 * @param {DOM} torrent_list_Dom 种子列表dom * @returns {list} 种子列表信息的 json对象列表 */ function TORRENT_LIST_TO_JSON(torrent_list_Dom) { // 获取表格中的所有行 const rows = torrent_list_Dom.querySelectorAll("tr"); // const rows = div.querySelectorAll('tr'); // 种子信息 -> 存储所有行数据的数组 const data = []; // index // let index = 0; // 遍历每一行并提取数据 rows.forEach((row) => { // 获取种子分类 const categoryImg = row.querySelector("td:nth-child(1) > a > img"); const category = categoryImg ? categoryImg.alt : ""; // 若没有分类则退出 if (!category) return; // 加index const torrentIndex = CARD.CARD_INDEX++; // 获取种子名称 const torrentNameLink = row.querySelector(".torrentname a"); const torrentName = torrentNameLink ? torrentNameLink.textContent.trim() : ""; // 获取种子详情链接 const torrentLink = torrentNameLink.href; // console.log(torrentLink); // 获取种子id const pattern = /id=(\d+)&hit/; const match = torrentLink.match(pattern); const torrentId = match ? parseInt(match[1]) : null; // 获取预览图片链接 const picLink = row .querySelector(".torrentname img") .getAttribute("data-src"); // 获取置顶信息 const place_at_the_top = row.querySelector(".torrentname img.sticky"); const pattMsg = place_at_the_top ? place_at_the_top.title : ""; // 获取下载链接 const downloadLink = `${domain}download.php?id=${torrentId}`; // 获取收藏链接 const collectLink = `javascript: bookmark(${torrentId},${torrentIndex});`; // 获取收藏状态 const collectDOM = row.querySelector(".torrentname a[id^='bookmark']"); const collectState = collectDOM.children[0].alt; // console.log(collectState); // 获取免费折扣类型 const freeTypeImg = row.querySelector('img[class^="pro_"]'); // console.log(freeTypeImg); // console.log(freeTypeImg.alt); const freeType = freeTypeImg ? "_" + freeTypeImg.alt.replace(/\s+/g, "") : ""; // 获取免费剩余时间 const freeRemainingTimeSpan = row.querySelector("font"); const freeRemainingTime = freeRemainingTimeSpan ? freeRemainingTimeSpan.innerText : ""; // 获取标签 const tagSpans = row.querySelectorAll(".torrentname span"); // const raw_tags = row.querySelector(".torrentname"); const tagsDOM = Array.from(tagSpans); let tags = tagSpans ? tagsDOM.map((span) => span.textContent.trim()) : []; // console.log(index); // console.log(torrentName); // console.log(tags); if (freeType != "") { // console.log(tags[0]); tags.shift(); tagsDOM.shift(); } const raw_tags = tagsDOM.map((el) => el.outerHTML).join(""); // console.log(raw_tags); // 获取描述 const descriptionCell = row.querySelector(".torrentname td:nth-child(2)"); const str = descriptionCell.innerHTML; let desResult; // -- 前处理 if (str.lastIndexOf("</span>") > str.lastIndexOf("<br>")) { desResult = str.substring(str.lastIndexOf("</span>") + 7); // 加 7 是为了去掉 "</span>" 的长度 } else { desResult = str.substring(str.lastIndexOf("<br>") + 4); // 加 7 是为了去掉 "</span>" 的长度 } // -- 后处理 desResult = desResult.split("<div")[0]; const description = desResult ? desResult.trim() : ""; // 获取评论数量 const commentsLink = row.querySelector("td.rowfollow:nth-child(3) a"); // console.log(commentsLink.innerHTML); const comments = commentsLink ? parseInt(commentsLink.textContent) : 0; // 获取上传日期 const uploadDateSpan = row.querySelector("td:nth-child(4) span"); const uploadDate = uploadDateSpan ? uploadDateSpan.title : ""; // 获取文件大小 const sizeCell = row.querySelector("td:nth-child(5)"); const size = sizeCell ? sizeCell.textContent.trim() : ""; // 获取做种人数 const seedersLink = row.querySelector("td:nth-child(6) a"); const seeders = seedersLink ? parseInt(seedersLink.textContent) : 0; // 获取下载人数 const leechersCell = row.querySelector("td:nth-child(7)"); const leechers = leechersCell ? parseInt(leechersCell.textContent) : 0; // 获取完成下载数 const snatchedLink = row.querySelector("td:nth-child(8) a"); const snatched = snatchedLink ? parseInt(snatchedLink.textContent) : 0; // 将当前行的数据格式化为 JSON 对象 const rowData = { torrentIndex, category, torrent_name: torrentName, torrentLink, torrentId, picLink, pattMsg, downloadLink, collectLink, collectState, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tags, description, comments, upload_date: uploadDate, size, seeders, leechers, snatched, }; // 将当前行的 JSON 对象添加到数组中 data.push(rowData); }); return data; } /** * 将种子列表信息渲染为卡片放入瀑布流 * @param {DOM} waterfallNode 瀑布流容器dom * @param {list} torrent_json 种子列表信息的 json对象列表 * @param {boolean} isFirst 是否是第一次渲染, 默认为是, 新增渲染要写 false */ function RENDER_TORRENT_JSON_IN_MASONRY( waterfallNode, torrent_json, isFirst = true ) { const cardTemplate = (data) => { const { torrentIndex, category, torrent_name: torrentName, torrentLink, torrentId, picLink, pattMsg, downloadLink, collectLink, collectState, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tags, description, comments, upload_date: uploadDate, size, seeders, leechers, snatched, } = data; return ` <!-- 分区类别 --> <div class="card-holder"> <div class="card-category"> ${category} </div> <!-- 标题 & 跳转详情链接 --> <div class="card-title"> <a class="two-lines" src="${torrentLink}" href="${torrentLink}" target="_blank"> <b>${torrentName}</b> </a> </div> <div class="card-body"> <div class="card-image" onclick="window.open('${torrentLink}')"> <img class="card-image--img nexus-lazy-load_Kesa" src="pic/misc/spinner.svg" data-src="${picLink}" alt="${torrentName}" /> <div class="card-index"> ${torrentIndex + 1} </div> </div> <div class="card-alter"> <!-- 免费类型 & 免费剩余时间 --> ${ freeType ? ` <div class="free_flag ${freeType}"> <b>${freeType}: ${freeRemainingTime}_</b> </div> ` : "" } <!-- 置顶等级 --> ${pattMsg ? `<div><b>置顶等级:</b> ${pattMsg}</div>` : ""} </div> <!-- 副标题 --> ${ description ? `<a class="card-description" href='${torrentLink}'> ${description}</a>` : "" } <!-- 标签 Tags --> <div class="card-line cl-tags"> ${raw_tags} <!-- <b>Tags:</b> ${tags.join(", ")} --> </div> <div class="card-details"> <div class="card-line"> <!-- 大小 --> <div class="cl-center"> ${ICON.SIZE} ${size} </div> <!-- 下载 --> <div class="cl-center"> ${ICON.DOWNLOAD} <b><a src="${downloadLink}" href="${downloadLink}">下载</a></b> </div> <!-- 收藏 --> <div class="cl-center"> <div class="btnCollet cl-center" id="tI_${torrentIndex}" onclick='COLLET_AND_ICON_CHANGE("${collectLink}", "tI_${torrentIndex}")'> ${collectState == "Unbookmarked" ? ICON.COLLET : ICON.COLLETED} <b>收藏</b> </div> </div> </div> <!-- 种子id, 默认不显示 --> <!--<div class="card-line"><b>Torrent ID:</b> ${torrentId}</div> --> <!-- 上传时间 --> <div class="card-line"><b>上传时间:</b> ${uploadDate}</div> <div class="card-line"> ${ICON.COMMENT} <b>${comments}</b> ${ICON.SEEDERS} <b>${seeders}</b> ${ICON.LEECHERS} <b>${leechers}</b> ${ICON.SNATCHED} <b>${snatched}</b> </div> </div> </div> </div>`; }; for (const rowData of torrent_json) { const card = document.createElement("div"); card.classList.add("card"); card.innerHTML = cardTemplate(rowData); // 生成新的时候再改一次图片宽度 card.style.width = `${CARD.CARD_WIDTH}px`; // z-index 逐渐变小, 使得展开标题时本卡片的内容可以不被下面的卡片遮挡 card.style.zIndex = 10000 - rowData.torrentIndex; // |--|-- 3.1.1 渲染完成图片后调整构图 const card_img = card.querySelector(".card-image--img"); card_img.onload = function () { // 加载完图片后重新布局 Masonry if (masonry) { // TODO: 这里可以写个防抖优化性能, 但是人好像自带防抖的, 哈哈...... masonry.layout(); } }; // |--|-- 3.1.2 插入生成的元素 // |--|--|-- 3.1.2.1 第一次默认生成 waterfallNode.appendChild(card); // |--|--|-- 3.1.2.2 非第一次生成 if (!isFirst) { // console.log("not first ----------------------------"); // console.log(card); masonry.appended(card); } } } /** * 整合上面两个函数: 将种子列表转为瀑布流 * @param {DOM} torrent_list_Dom 种子列表dom * @param {DOM} waterfallNode 瀑布流容器dom * @param {boolean} isFirst 是否是第一次渲染, 默认为是, 新增渲染要写 false */ function PUT_TORRENT_INTO_MASONRY( torrent_list_Dom, waterfallNode, isFirst = true ) { /** 种子列表信息的 json对象列表 */ const data = TORRENT_LIST_TO_JSON(torrent_list_Dom); // DEBUG:打印获得的数据 console.log(`渲染行数: ${data.length}`); // console.log(data); // 将种子列表信息渲染为卡片放入瀑布流 RENDER_TORRENT_JSON_IN_MASONRY(waterfallNode, data, isFirst); // nexus 工具组 NEXUS_TOOLS(); } /** * 根据容器宽度和卡片宽度动态调整卡片间隔 gutter * @param {DOM} containerDom 容器dom * @param {number} card_width 卡片宽度 */ function GET_CARD_GUTTER(containerDom, card_width) { // 获取容器宽度 const _width = containerDom.clientWidth; // 获取一个合适的 gutter const card_real_width = card_width + CARD.CARD_BORDER; const columns = Math.floor(_width / card_real_width); const gutter = (_width - columns * card_real_width) / (columns - 1); // console.log(`列数:${columns} 间隔:${gutter}`); // console.log( // `容器宽:${_width} 列宽:${masonry ? masonry.columnWidth : "对象"}` // ); return Math.floor(gutter); } /** * 动态调整卡片宽度 * @param {number} targetWidth * @param {DOM} containerDom 容器dom * @param {object} masonry 瀑布流对象 */ function CHANGE_CARD_WIDTH(targetWidth, containerDom, masonry) { // 改变卡片宽度 for (const card of containerDom.childNodes) { // console.log(CARD.CARD_WIDTH); card.style.width = `${targetWidth}px`; } // 调整卡片间隔 gutter masonry.options.gutter = GET_CARD_GUTTER(containerDom, targetWidth); // 重新布局瀑布流 masonry.layout(); } /** * 执行收藏动作并对制定卡片切换图标 * @param {string} jsCodeLink js的收藏代码 * @param {string} card_id 种子卡片id */ function COLLET_AND_ICON_CHANGE(jsCodeLink, card_id) { // console.log(jsCodeLink, card_id); try { // 收藏链接 window.location.href = jsCodeLink; // 操作相应的收藏图片 const btn = document.querySelector(`div#${card_id}`); const img = btn.children[0]; img.className = img.className == "delbookmark" ? "bookmark" : "delbookmark"; // console.log(btn); console.log(`执行脚本${jsCodeLink}成功, 已经收藏或者取消~`); } catch (error) { // GUI 通知一下捏 console.error(error); } } window.COLLET_AND_ICON_CHANGE = COLLET_AND_ICON_CHANGE; /** * 防抖函数 * @param {function} func 操作函数 * @param {number} delay 延迟 * @returns */ function debounce(func, delay) { var timer; return function () { var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function () { func.apply(context, args); }, delay); }; } /** * NEXUS 预览工具箱, 提供图片预览和图片懒加载, 神器 */ function NEXUS_TOOLS() { jQuery(document).ready(function () { console.log("----jQuery 加载完毕 | Kesa 改版 nexus 工具启动!---"); /** * 获取图片位置 * @param {*} e * @param {*} imgEle * @returns */ function getImgPosition(e, imgEle) { // console.log(e, imgEle) let imgWidth = imgEle.prop("naturalWidth"); let imgHeight = imgEle.prop("naturalHeight"); let ratio = imgWidth / imgHeight; let offsetX = 10; let offsetY = 10; let width = window.innerWidth - e.clientX; let height = window.innerHeight - e.clientY; let changeOffsetY = 0; let changeOffsetX = false; if ( e.clientX > window.innerWidth / 2 && e.clientX + imgWidth > window.innerWidth ) { changeOffsetX = true; width = e.clientX; } if (e.clientY > window.innerHeight / 2) { if (e.clientY + imgHeight / 2 > window.innerHeight) { changeOffsetY = 1; height = e.clientY; } else if (e.clientY + imgHeight > window.innerHeight) { changeOffsetY = 2; height = e.clientY; } } let log = `innerWidth: ${window.innerWidth}, innerHeight: ${window.innerHeight}, pageX: ${e.pageX}, pageY: ${e.pageY}, imgWidth: ${imgWidth}, imgHeight: ${imgHeight}, width: ${width}, height: ${height}, offsetX: ${offsetX}, offsetY: ${offsetY}, changeOffsetX: ${changeOffsetX}, changeOffsetY: ${changeOffsetY}`; console.log(log); if (imgWidth > width) { imgWidth = width; imgHeight = imgWidth / ratio; } if (imgHeight > height) { imgHeight = height; imgWidth = imgHeight * ratio; } if (changeOffsetX) { offsetX = -(e.clientX - width + 10); } if (changeOffsetY == 1) { offsetY = -(imgHeight - (window.innerHeight - e.clientY)); } else if (changeOffsetY == 2) { offsetY = -imgHeight / 2; } return { imgWidth, imgHeight, offsetX, offsetY }; } /** * 获取展示位置 * @param {*} e * @param {*} position * @returns */ function getPosition(e, position) { return { left: e.pageX + position.offsetX, top: e.pageY + position.offsetY, width: position.imgWidth, height: position.imgHeight, }; } // preview const previewEle = jQuery("#nexus-preview"); let imgEle; const selector = "img.preview_Kesa"; let imgPosition; jQuery("body") .on("mouseover", selector, function (e) { imgEle = jQuery(this); // previewEle = jQuery('<img style="display: none;position:absolute;">').appendTo(imgEle.parent()) imgPosition = getImgPosition(e, imgEle); let position = getPosition(e, imgPosition); let src = imgEle.attr("src"); if (src) { previewEle.attr("src", src).css(position).fadeIn("fast"); } }) .on("mouseout", selector, function (e) { // previewEle.remove() // previewEle = null previewEle.hide(); }) .on("mousemove", selector, function (e) { let position = getPosition(e, imgPosition); previewEle.css(position); }); // lazy load if ("IntersectionObserver" in window) { let imgList = [...document.querySelectorAll(".nexus-lazy-load_Kesa")]; console.log(imgList); const io = new IntersectionObserver((entries) => { entries.forEach((entry) => { const el = entry.target; const intersectionRatio = entry.intersectionRatio; // console.log(`el, ${el.getAttribute("data-src")}, intersectionRatio: ${intersectionRatio}`); if ( intersectionRatio > 0 && intersectionRatio <= 1 && !el.classList.contains("preview_Kesa") ) { // 懒加载成功 // console.log(`el, ${el.getAttribute("data-src")}, loadImg`); const source = el.dataset.src; el.src = source; el.classList.add("preview_Kesa"); // 加载完图片后重新布局 Masonry if (masonry) { // TODO: 这里可以写个防抖优化性能, 但是人好像自带防抖的, 哈哈...... masonry.layout(); } } el.onload = el.onerror = () => io.unobserve(el); }); }); imgList.forEach((img) => io.observe(img)); } }); } // ------------------ 下面是非声明区 (function () { "use strict"; // FIXME: // 1. 隐藏原种子列表并进行前置操作 -------------------------------------------------------------------------------------- // 表格节点 const tableNode = document.querySelector("table.torrents"); // 表格父节点 const parentNode = tableNode.parentNode; // 删除原有视图 // parentNode.removeChild(tableNode); // 隐藏原有视图 tableNode.style.display = "none"; // 放置瀑布流的节点 const waterfallNode = document.createElement("div"); // 添加class waterfallNode.classList.add("waterfall"); // 将瀑布流节点放置在表格节点上面 parentNode.insertBefore(waterfallNode, tableNode.nextSibling); // 生成按钮 -> 可以随时显示原来的表格 const btnViewOrigin = document.getElementById("btnViewOrigin"); // 创建一个按钮元素 const toggleBtn = document.createElement("button"); toggleBtn.classList.add("debug"); toggleBtn.setAttribute("id", "toggle_oldTable"); toggleBtn.innerText = "显示原种子表格"; toggleBtn.style.zIndex = 10001; // 为按钮添加事件监听器 toggleBtn.addEventListener("click", function () { if (tableNode.style.display === "none") { tableNode.style.display = "block"; toggleBtn.innerText = "隐藏原种子表格"; } else { tableNode.style.display = "none"; toggleBtn.innerText = "显示原种子表格"; } }); // 将按钮插入到文档中 document.body.appendChild(toggleBtn); // 生成按钮 -> Masonry 重新排列 const btnReLayout = document.getElementById("btnReLayout"); // 创建一个按钮元素 const reLayoutBtn = document.createElement("button"); reLayoutBtn.classList.add("debug"); reLayoutBtn.setAttribute("id", "btnReLayout"); reLayoutBtn.innerText = "单列宽度切换(200/300)"; reLayoutBtn.style.zIndex = 10002; // 为按钮添加事件监听器 reLayoutBtn.addEventListener("click", function () { if (masonry) { masonry.layout(); } // 动态调整卡片宽度 CARD.CARD_WIDTH = CARD.CARD_WIDTH == 200 ? 300 : 200; CHANGE_CARD_WIDTH(CARD.CARD_WIDTH, waterfallNode, masonry); masonry.layout(); // // 改变卡片宽度 // for (const card of waterfallNode.childNodes) { // console.log(CARD.CARD_WIDTH); // card.style.width = `${CARD.CARD_WIDTH}px`; // } // // 调整卡片间隔 gutter // masonry.options.gutter = GET_CARD_GUTTER(waterfallNode, CARD.CARD_WIDTH); // // 重新布局瀑布流 // masonry.layout(); }); // 将按钮插入到文档中 document.body.appendChild(reLayoutBtn); // FIXME: // 2. 将种子列表信息搞下来 html -> json 对象 -------------------------------------------------------------------------------------- // 获取表格 Dom const table = document.querySelector("table.torrents"); // /** 种子列表信息的 json对象列表 */ // const data = TORRENT_LIST_TO_JSON(table); // // FIXME: // // 3. 开整瀑布流 -------------------------------------------------------------------------------------- // // -- 3.1 搞定卡片模板 // // 将种子列表信息渲染为卡片放入瀑布流 // RENDER_TORRENT_JSON_IN_MASONRY(waterfallNode, data); // ----------- // 一步到位整合上面步骤: 将种子列表转为瀑布流 PUT_TORRENT_INTO_MASONRY(table, waterfallNode); // -- 3.2 调整 css // 使用中的css const css = ` /* 瀑布流主容器 */ div.waterfall{ width: 100%; padding-top: 20px; padding-bottom: 20px; border-radius: 20px; height: 100%; /* margin: 0 auto; */ margin: 20px auto; } /* 调试按键统一样式 */ button.debug { position: fixed; top: 10px; right: 10px; padding: 4px; background-color: #333; color: #fff; border: none; border-radius: 5px; cursor: pointer; } /* 调试按键1: 显示隐藏原种子列表 */ button#toggle_oldTable { top: 10px; } /* 调试按键2: Masonry 重新排列 */ button#btnReLayout { top: 40px; } /* 卡片 */ .card { width: ${CARD.CARD_WIDTH}px; border: 1px solid rgba(255, 255, 255, 0.5); border-radius: 5px; background-color: ${themeColor}; /* box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3); */ /* margin: 10px; */ margin: 6px 0; overflow: hidden; } .card:hover { } .card-holder{ background-color: rgba(255, 255, 255, 0.5); background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0)); padding-bottom: 6px; } /* 卡片行默认样式 */ .card-line{ margin-bottom: 1px; display: flex; justify-content: space-evenly; align-items: center; } /* 卡片标题: 默认两行 */ .two-lines { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; transition: color 0.3s; } /* 卡片标题: hover时变为正常 */ .two-lines:hover { -webkit-line-clamp: 100; } /* 卡片信息: flex 居中 */ .cl-center{ display: flex; justify-content: space-evenly; align-items: center; } /* 卡片信息行: 标签行 */ .cl-tags{ display: flex; justify-content: left; align-items: center; transform: translateX(4px); } /* 卡片简介总容器 */ .card-details{ display: flex; justify-content: center; align-items: center; flex-direction: column; } /* 卡片图像div */ .card-image { height: 100%; position: relative; margin-bottom: 2px; } /* 卡片图像div -> img标签 */ .card-image img { width: 100%; object-fit: cover; } /* 卡片可选信息 */ .card-alter{ text-align: center; } /* 免费类型&剩余时间 */ .free_flag{ padding: 2px; border-radius: 4px; margin-bottom: 2px; } ._Free{ color: blue; background-color: #00e6 } ._2XFree{ color: green; background-color: #0e0 } /* 卡片索引 */ .card-index{ position: absolute; top: 0; left: 0; padding-right: 9px; padding-left: 2px; margin: 0; height: 20px; line-height: 16px; font-size: 16px; background-color: rgba(0,0,0,0.7); color: yellow; border-top-right-radius: 100px; border-bottom-right-radius: 100px; display: flex; align-items: center; } /* 卡片索引 */ .btnCollet{ padding: 1px 2px; cursor: pointer; } /* 上面是我自己脚本的css */ /* --------------------------------------- */ /* 下面是改进原有的css */ /* 卡片索引 */ #nexus-preview{ z-index: 20000; } `; // 注释中的css const css_commented = ` `; // -- 3.3 引入 Masonry 库 const style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); // 创建script标签 const script = document.createElement("script"); // 设置script标签的src属性为Masonry库的地址 script.src = "https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js"; // 将script标签添加到head标签中 document.getElementsByTagName("head")[0].appendChild(script); // -- 3.3.1 初始化 Masonry 参数 script.onload = function () { // 初始化瀑布流布局 masonry = new Masonry(waterfallNode, { itemSelector: ".card", columnWidth: ".card", gutter: GET_CARD_GUTTER(waterfallNode, CARD.CARD_WIDTH), }); // console.log(masonry); // -- 3.3.2 监听窗口大小变化事件 window.addEventListener("resize", function () { // 调整卡片间隔 gutter masonry.options.gutter = GET_CARD_GUTTER(waterfallNode, CARD.CARD_WIDTH); // 重新布局瀑布流 masonry.layout(); }); // 重新布局瀑布流 masonry.layout(); // 绑定 Masonry 对象到 window window.masonry = masonry; }; // FIXME: // 4. 底部检测 & 加载下一页 -------------------------------------------------------------------------------------- // |-- 4.1 检测是否到了底部 /** 延迟加载事件变量名 */ let debounceLoad; window.addEventListener("scroll", function () { const scrollHeight = document.body.scrollHeight; const clientHeight = document.documentElement.clientHeight; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop + clientHeight >= scrollHeight - PAGE.DISTANCE) { debounceLoad(); } }); // |-- 4.2 加载下一页 debounceLoad = debounce(function () { console.log("到页面底部啦!!! Scrolled to bottom!"); // |--|-- 4.2.1 获取下一页的链接 // 使用 URLSearchParams 对象获取当前网页的查询参数 const urlSearchParams = new URLSearchParams(window.location.search); // 获取名为 "page" 的参数的值 -> 初始为页面值, 更新为更新值 PAGE.PAGE_CURRENT = PAGE.IS_ORIGIN ? urlSearchParams.get("page") : PAGE.PAGE_CURRENT; // 如果 "page" 参数不存在,则将页数设为 0,否则打印当前页数 if (!PAGE.PAGE_CURRENT) { console.log( `网页链接没有page参数, 无法跳转下一页, 生成PAGE.PAGE_CURRENT为0` ); PAGE.PAGE_CURRENT = 0; } else { console.log("当前页数: " + PAGE.PAGE_CURRENT); } // 将页数加 1,并设置为新的 "page" 参数的值 PAGE.PAGE_NEXT = parseInt(PAGE.PAGE_CURRENT) + 1; urlSearchParams.set("page", PAGE.PAGE_NEXT); // 生成新的链接,包括原网页的域名、路径和新的查询参数 PAGE.NEXT_URL = window.location.origin + window.location.pathname + "?" + urlSearchParams.toString(); // 打印新的链接 console.log("New URL:", PAGE.NEXT_URL); // TODO: 搞个 list 放入所有生成的新链接, 如果新链接存在就不 fetch 新数据 // |--|-- 4.2.2 加载下一页 html 获取 json 信息对象 fetch(PAGE.NEXT_URL) .then((response) => response.text()) .then((html) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); const table = doc.querySelector("table.torrents"); // console.log(table); // |--|-- 4.2.3 渲染 下一页信息 并 加到 waterfallNode 里面来 PUT_TORRENT_INTO_MASONRY(table, waterfallNode, false); // 生成新的时候再改一次图片宽度 CHANGE_CARD_WIDTH(CARD.CARD_WIDTH, waterfallNode, masonry); // 页数更新, 在上面几行更新会导致没有下一页的情况下仍然触发 PAGE.IS_ORIGIN = false; PAGE.PAGE_CURRENT = PAGE.PAGE_NEXT; }) .catch((error) => { // console.error(error); console.warn("获取不到下页信息, 可能到头了"); console.warn(error); }); }, PAGE.DISTANCE); })();