纪念衫征集
查看: 2821|回复: 2

论坛网页版增强脚本:适配任意倍率缩放页面,大幅提升窄屏阅读体验(Tampermonkey脚本)

[复制链接]
阅读字号:
发表于 2023-12-11 20:18 | 显示全部楼层 |阅读模式
上次由于考虑不周,擅自发布了破坏性脚本,于是这次想着痛改前非,来搞一个好脚本。

发现问题:有的时候看四格漫画,或者看很小的文字,就会本能地想要放大页面。可是当页面放大到一定程度以后,有一部分就会跑到屏幕外面,此时要拖动页面底部的滚动条,相当地不方便。比如说下面的这个漫画(相当有趣!):
屏幕截图 2023-12-11 195043.jpg

如果想要进一步放大,就会变成这个样子:
屏幕截图 2023-12-11 195117.jpg

另外,如果我们想要缩小页面,或者显示器本来就相当巨大,或缩放比例过低,那么页面会变成这样:
屏幕截图 2023-12-11 195331.jpg

如果说上面的都只是有些难受的话,那么看小说的时候,如果页面过窄或缩放比例过高,那么看到的将会是:
屏幕截图 2023-12-11 195500.jpg

想看小说要不断地拖动横向滚动条,恶心到家了。

解决方法有没有呢?或许可以换手机版,但是电脑上是打不开手机版的,点开链接显示的是「立即使用手机访问,获得极速移动体验」,好可恶啊。如果能让电脑端也可以自由点开手机版,或许这个问题也算解决了吧。

但是,总之还是花了点时间写了这个脚本。实话实说,是因为我当时没有想到这一点。先展示一下做了什么改动吧:
Screenshot 2023-12-11 at 20-08-13 【汉化工房九九组】 こかむも 胡桃同学是人造人 .png

隐藏了侧边栏,让页面始终处于屏幕内,缩小了页边距,让图片能够自适应页面宽度,此外将图片自动居中,不论是否有[align]标签。

考虑到很多图片的宽度是直接写的900px,所以在页面宽度大于900px的时候没有选择放大图片,而是保持原大小,只在页面宽度小于图片宽度的时候,适当缩小图片,使之始终保持在页面范围之内。

小说的体验也和这个差不多:
屏幕截图 2023-12-11 201308.jpg

总之就是这样了,依然还是直接放代码,因为论坛不能直接上传.user.js后缀名的文件。如何使用呢?一个方法是在浏览器里安装Tampermonkey插件,然后创建一个新脚本,用下面的代码完全替换新窗口里面的内容并保存。当然还有其他的方法,只要能让浏览器自动在页面加载完成后执行下面的代码即可。所以也可以建立一个反向代理服务器,转发www.yamibo.com的请求,并在每个HTML响应后加上一个<script>,里面放上这个代码,不过这纯属于杀鸡用牛刀了。

  1. // ==UserScript==
  2. // [url=home.php?mod=space&uid=192264]@name[/url]         Yamibo Script
  3. // [url=home.php?mod=space&uid=291848]@namespace[/url]    http://tampermonkey.net/
  4. // [url=home.php?mod=space&uid=121086]@version[/url]      0.0
  5. // @description  小幅增强百合会网页版观看体验
  6. // [url=home.php?mod=space&uid=389072]@author[/url]       VoltaXTY
  7. // [url=home.php?mod=space&uid=286521]@Match[/url]        https://bbs.yamibo.com/*
  8. // [url=home.php?mod=space&uid=35369]@icon[/url]         http://yamibo.com/favicon.ico
  9. // [url=home.php?mod=space&uid=147958]@grant[/url]        none
  10. // @run-at       document-end
  11. // ==/UserScript==
  12. let ID = 1;
  13. const GenerateID = () => {
  14.     return ID++;
  15. };
  16. const HTML = (tagname, attrs, ...children) => {
  17.     if(attrs === undefined) return document.createTextNode(tagname);
  18.     const ele = document.createElement(tagname);
  19.     if(attrs) for(const [key, value] of Object.entries(attrs)){
  20.         if(value === null || value === undefined) continue;
  21.         if(key.charAt(0) === "_"){
  22.             const type = key.slice(1);
  23.             ele.addEventListener(type, value);
  24.         }
  25.         else if(key.charAt(0) === "#" && ele.getAttribute("id") !== null){
  26.             const type = key.slice(1);
  27.             persistentEventListeners.set(ele.getAttribute("id"), {type: type, value: value});
  28.             ele.addEventListener(type, value);
  29.         }
  30.         else if(key === "eventListener"){
  31.             for(const listener of value){
  32.                 ele.addEventListener(listener.type, listener.listener, listener.options);
  33.             }
  34.         }
  35.         else ele.setAttribute(key, value);
  36.     }
  37.     for(const child of children) if(child) ele.append(child);
  38.     return ele;
  39. };
  40. const ParseHTML = (html) => {
  41.     const t = document.createElement("template");
  42.     t.innerHTML = html;
  43.     return t.content;
  44. }
  45. const ThreadIDToURL = (tid, page = 1) => {
  46.     return `https://bbs.yamibo.com/thread-${tid}-${page}-1.html`;
  47. }
  48. const FetchThread = async (url) => {
  49.     let retry = false;
  50.     do{
  51.         retry = false;
  52.         let res = await fetch(url, {
  53.             credentials: "include",
  54.         });
  55.         let text = await res.text();
  56.         if(text.startsWith("<script")){
  57.             let jumploc = "";
  58.             let location = {
  59.                 get href(){
  60.                     return jumploc;
  61.                 },
  62.                 set href(_){
  63.                     if(!jumploc) jumploc = _;
  64.                 },
  65.                 assign(_){
  66.                     if(!jumploc) jumploc = _;
  67.                 },
  68.                 replace(_){
  69.                     if(!jumploc) jumploc = _;
  70.                 }
  71.             };
  72.             let window = {
  73.                 get href(){
  74.                     return jumploc;
  75.                 },
  76.                 set href(_){
  77.                     if(!jumploc) jumploc = _;
  78.                 }
  79.             };
  80.             const t = document.createElement("template");
  81.             t.innerHTML = text;
  82.             const s = t.content.firstElementChild;
  83.             if(s.tagName === "SCRIPT"){
  84.                 const _ = s.innerHTML;
  85.                 try{ eval(_); } catch (error){
  86.                     console.error(_);
  87.                     console.error(error);
  88.                     jumploc = location = "";
  89.                     retry = true;
  90.                 }
  91.             }
  92.             if(typeof location === "string" && location.length > jumploc.length) jumploc = location;
  93.             if(jumploc !== ""){
  94.                 let res2 = await fetch(`https://bbs.yamibo.com${jumploc}`, {
  95.                     credentials: "include",
  96.                 });
  97.                 let text = await res2.text();
  98.                 return text;
  99.             }
  100.         }
  101.         else return text;
  102.     }while(retry);
  103.     return "Should not reach here.";
  104. }
  105. const inj_style =
  106. `
  107. .zoom{
  108.     margin: 0px auto;
  109.     width: auto;
  110.     height: auto;
  111.     max-width: 100%;
  112.     min-width: 50px;
  113. }
  114. .zoom[initialized="true"]{
  115.     width: auto;
  116.     height: auto;
  117.     max-width: 100%;
  118.     min-width: 900px;
  119. }
  120. img.zoom{
  121.     display: block;
  122.     counter-increment: img-counter 1;
  123. }
  124. img.zoom::before{
  125.     display: block;
  126.     text-align: center;
  127. }
  128. td.plc{
  129.     counter-reset: img-counter;
  130. }

  131. body#nv_forum{
  132.     min-width: 0px;
  133. }
  134. @media(min-width: 900px){
  135.     .author-id__added{
  136.         display: none;
  137.     }
  138. }
  139. @media(max-width: 900px){
  140.     #hd .wp, #wp{
  141.         min-width: calc(100% - 20px);
  142.     }
  143.     div > table[summary] > tbody > tr{
  144.         display: grid;
  145.         grid-template-columns: 1fr;
  146.         width: 100%;
  147.         max-width: 9999px;
  148.     }
  149.     div > table[summary] > tbody > tr:nth-child(1) > td.pls{
  150.         grid-column: 1/1;
  151.         grid-row: 1/1;
  152.         margin-top: 37px;
  153.         width: 100%;
  154.         z-index: 1;
  155.     }
  156.     div > table[summary] > tbody > tr:nth-child(1) > td.pls > div > div.pi{
  157.         display: none;
  158.     }
  159.     div > table[summary] > tbody > tr:nth-child(1) > td.plc{
  160.         grid-column: 1/1;
  161.         grid-row: 1/1;
  162.         z-index: 0;
  163.     }
  164.     .author-id__added{
  165.         margin-left: 8px;
  166.     }
  167.     div.pls.cl.favatar{
  168.         height: auto;
  169.         width: auto;
  170.         display: none;
  171.     }
  172.     .favatar-group-top .pi{
  173.         display: none;
  174.     }
  175.     td.pls{
  176.         visibility: hidden;
  177.     }
  178.     .pls.ptn.pbn{
  179.         visibility: visible;
  180.     }
  181. }
  182. `
  183. const InsertStyleSheet = (style) => {
  184.     const s = new CSSStyleSheet();
  185.     s.replaceSync(style);
  186.     document.adoptedStyleSheets = [...document.adoptedStyleSheets, s];
  187. };
  188. const IsForumPost = () => {
  189.     const forumview_url = /https:\/\/bbs\.yamibo\.com\/thread-[0-9]+-[0-9]+-[0-9]+\.html/;
  190.     if(forumview_url.test(location.toString())) return true;
  191.     if((new URLSearchParams(window.location.search)).get("mod") === "viewthread") return true;
  192.     else return false;
  193. }
  194. const AddPreviewButton = () => {
  195.     // TODO
  196.     document.querySelectorAll("#threadlisttableid tbody[id]").forEach((tbody) => {
  197.         if(tbody.hasAttribute("preview-button-added")) return;
  198.         else tbody.setAttribute("preview-button-added", "");
  199.         const id_pattern = /[a-z]+_([0-9]+)/;
  200.         if(!id_pattern.test(tbody.id)) return;
  201.         const title_ele = tbody.querySelector(":scope tr th");
  202.         title_ele.insertAdjacentElement("afterbegin",
  203.             HTML("td", {class: "preview-button"}, "test")
  204.         );
  205.     })
  206. };
  207. const ImproveForumPost = () => {
  208.     if(!IsForumPost()) return;
  209.     document.querySelectorAll("div[id^="post_"]").forEach((div) => {
  210.         if(!(/post_[0-9]+/.test(div.id)) || div.hasAttribute("modified")) return;
  211.         div.setAttribute("modified", "");
  212.         const tok = GenerateID();
  213.         const author_id = div.querySelector(":scope .pi .authi .xw1").textContent;
  214.         const post_content = div.querySelector(":scope .plc");
  215.         const post_content_header = post_content.querySelector(":scope .authicn");
  216.         post_content_header.insertAdjacentElement("afterend",
  217.             HTML("strong", {class: "author-id__added"}, author_id),
  218.         );
  219.         const post_content_author = div.querySelector(":scope div.pls");
  220.         const pcac = post_content_author.children;
  221.         const group1 = HTML("div", {class: "favatar-group-top"});
  222.         const group2 = HTML("div", {class: "favatar-group-bottom"});
  223.         while(!pcac.item(0).classList.contains("pil")) group1.append(pcac.item(0));
  224.         while(pcac.item(0)) group2.append(pcac.item(0));
  225.         post_content_author.append(group1, group2);
  226.     })
  227. }
  228. const OnMutation = (mulist, observer) => {
  229.     observer.disconnect();
  230.     //AddPreviewButton(...args);
  231.     ImproveForumPost();
  232.     observer.observe(document, {subtree: true, childList: true});
  233. };
  234. InsertStyleSheet(inj_style);
  235. new MutationObserver(OnMutation).observe(document, {subtree: true, childList: true});
复制代码


题外话,管理版没法编辑帖子真的是硬伤。

点评

忘了说了,漫画名字是《胡桃同学是人造人》。虽然只是普通的轻百合,但是作者的节奏和画风都相当出色,莫名其妙地就能投入进去,还是很神奇的  发表于 2023-12-11 20:20
由于论坛会自动把@替换为@链接,因此前几行的注释需要自行把多余的东西给删掉。  发表于 2023-12-11 20:19
您需要登录后才可以回帖 登录 | 成为会员

本版积分规则

Archiver|手机版|小黑屋|百合会 ( 苏公网安备 32030302000123号 )

GMT+8, 2024-11-22 17:08 , Processed in 0.083863 second(s), 29 queries , Gzip On.

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表