volta 发表于 2023-12-11 20:18

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

上次由于考虑不周,擅自发布了破坏性脚本,于是这次想着痛改前非,来搞一个好脚本。

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


如果想要进一步放大,就会变成这个样子:


另外,如果我们想要缩小页面,或者显示器本来就相当巨大,或缩放比例过低,那么页面会变成这样:


如果说上面的都只是有些难受的话,那么看小说的时候,如果页面过窄或缩放比例过高,那么看到的将会是:


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

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

但是,总之还是花了点时间写了这个脚本。实话实说,是因为我当时没有想到这一点。先展示一下做了什么改动吧:


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

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

小说的体验也和这个差不多:


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

// ==UserScript==
// @name         Yamibo Script
// @namespace    http://tampermonkey.net/
// @version      0.0
// @description小幅增强百合会网页版观看体验
// @author       VoltaXTY
// @Match      https://bbs.yamibo.com/*
// @icon         http://yamibo.com/favicon.ico
// @grant      none
// @run-at       document-end
// ==/UserScript==
let ID = 1;
const GenerateID = () => {
    return ID++;
};
const HTML = (tagname, attrs, ...children) => {
    if(attrs === undefined) return document.createTextNode(tagname);
    const ele = document.createElement(tagname);
    if(attrs) for(const of Object.entries(attrs)){
      if(value === null || value === undefined) continue;
      if(key.charAt(0) === "_"){
            const type = key.slice(1);
            ele.addEventListener(type, value);
      }
      else if(key.charAt(0) === "#" && ele.getAttribute("id") !== null){
            const type = key.slice(1);
            persistentEventListeners.set(ele.getAttribute("id"), {type: type, value: value});
            ele.addEventListener(type, value);
      }
      else if(key === "eventListener"){
            for(const listener of value){
                ele.addEventListener(listener.type, listener.listener, listener.options);
            }
      }
      else ele.setAttribute(key, value);
    }
    for(const child of children) if(child) ele.append(child);
    return ele;
};
const ParseHTML = (html) => {
    const t = document.createElement("template");
    t.innerHTML = html;
    return t.content;
}
const ThreadIDToURL = (tid, page = 1) => {
    return `https://bbs.yamibo.com/thread-${tid}-${page}-1.html`;
}
const FetchThread = async (url) => {
    let retry = false;
    do{
      retry = false;
      let res = await fetch(url, {
            credentials: "include",
      });
      let text = await res.text();
      if(text.startsWith("<script")){
            let jumploc = "";
            let location = {
                get href(){
                  return jumploc;
                },
                set href(_){
                  if(!jumploc) jumploc = _;
                },
                assign(_){
                  if(!jumploc) jumploc = _;
                },
                replace(_){
                  if(!jumploc) jumploc = _;
                }
            };
            let window = {
                get href(){
                  return jumploc;
                },
                set href(_){
                  if(!jumploc) jumploc = _;
                }
            };
            const t = document.createElement("template");
            t.innerHTML = text;
            const s = t.content.firstElementChild;
            if(s.tagName === "SCRIPT"){
                const _ = s.innerHTML;
                try{ eval(_); } catch (error){
                  console.error(_);
                  console.error(error);
                  jumploc = location = "";
                  retry = true;
                }
            }
            if(typeof location === "string" && location.length > jumploc.length) jumploc = location;
            if(jumploc !== ""){
                let res2 = await fetch(`https://bbs.yamibo.com${jumploc}`, {
                  credentials: "include",
                });
                let text = await res2.text();
                return text;
            }
      }
      else return text;
    }while(retry);
    return "Should not reach here.";
}
const inj_style =
`
.zoom{
    margin: 0px auto;
    width: auto;
    height: auto;
    max-width: 100%;
    min-width: 50px;
}
.zoom{
    width: auto;
    height: auto;
    max-width: 100%;
    min-width: 900px;
}
img.zoom{
    display: block;
    counter-increment: img-counter 1;
}
img.zoom::before{
    display: block;
    text-align: center;
}
td.plc{
    counter-reset: img-counter;
}

body#nv_forum{
    min-width: 0px;
}
@media(min-width: 900px){
    .author-id__added{
      display: none;
    }
}
@media(max-width: 900px){
    #hd .wp, #wp{
      min-width: calc(100% - 20px);
    }
    div > table > tbody > tr{
      display: grid;
      grid-template-columns: 1fr;
      width: 100%;
      max-width: 9999px;
    }
    div > table > tbody > tr:nth-child(1) > td.pls{
      grid-column: 1/1;
      grid-row: 1/1;
      margin-top: 37px;
      width: 100%;
      z-index: 1;
    }
    div > table > tbody > tr:nth-child(1) > td.pls > div > div.pi{
      display: none;
    }
    div > table > tbody > tr:nth-child(1) > td.plc{
      grid-column: 1/1;
      grid-row: 1/1;
      z-index: 0;
    }
    .author-id__added{
      margin-left: 8px;
    }
    div.pls.cl.favatar{
      height: auto;
      width: auto;
      display: none;
    }
    .favatar-group-top .pi{
      display: none;
    }
    td.pls{
      visibility: hidden;
    }
    .pls.ptn.pbn{
      visibility: visible;
    }
}
`
const InsertStyleSheet = (style) => {
    const s = new CSSStyleSheet();
    s.replaceSync(style);
    document.adoptedStyleSheets = [...document.adoptedStyleSheets, s];
};
const IsForumPost = () => {
    const forumview_url = /https:\/\/bbs\.yamibo\.com\/thread-+-+-+\.html/;
    if(forumview_url.test(location.toString())) return true;
    if((new URLSearchParams(window.location.search)).get("mod") === "viewthread") return true;
    else return false;
}
const AddPreviewButton = () => {
    // TODO
    document.querySelectorAll("#threadlisttableid tbody").forEach((tbody) => {
      if(tbody.hasAttribute("preview-button-added")) return;
      else tbody.setAttribute("preview-button-added", "");
      const id_pattern = /+_(+)/;
      if(!id_pattern.test(tbody.id)) return;
      const title_ele = tbody.querySelector(":scope tr th");
      title_ele.insertAdjacentElement("afterbegin",
            HTML("td", {class: "preview-button"}, "test")
      );
    })
};
const ImproveForumPost = () => {
    if(!IsForumPost()) return;
    document.querySelectorAll("div").forEach((div) => {
      if(!(/post_+/.test(div.id)) || div.hasAttribute("modified")) return;
      div.setAttribute("modified", "");
      const tok = GenerateID();
      const author_id = div.querySelector(":scope .pi .authi .xw1").textContent;
      const post_content = div.querySelector(":scope .plc");
      const post_content_header = post_content.querySelector(":scope .authicn");
      post_content_header.insertAdjacentElement("afterend",
            HTML("strong", {class: "author-id__added"}, author_id),
      );
      const post_content_author = div.querySelector(":scope div.pls");
      const pcac = post_content_author.children;
      const group1 = HTML("div", {class: "favatar-group-top"});
      const group2 = HTML("div", {class: "favatar-group-bottom"});
      while(!pcac.item(0).classList.contains("pil")) group1.append(pcac.item(0));
      while(pcac.item(0)) group2.append(pcac.item(0));
      post_content_author.append(group1, group2);
    })
}
const OnMutation = (mulist, observer) => {
    observer.disconnect();
    //AddPreviewButton(...args);
    ImproveForumPost();
    observer.observe(document, {subtree: true, childList: true});
};
InsertStyleSheet(inj_style);
new MutationObserver(OnMutation).observe(document, {subtree: true, childList: true});


题外话,管理版没法编辑帖子真的是硬伤。
页: [1]
查看完整版本: 论坛网页版增强脚本:适配任意倍率缩放页面,大幅提升窄屏阅读体验(Tampermonkey脚本)