持续更新…
施工计划:
- 加载动画;
- 适配黑暗模式;
- 自定义(宽度 + 高度 /加载动画相关…);
- 预览窗口的 锁定(防误触) 功能;
- 预览窗口的 “台前调度” 功能;
—分割线—
太长不看
碎碎念:
由于不喜欢在新标签页中打开网页,我一直在寻找优雅的预览方法:
(这也是我用arc浏览器的主要原因)
桌面端可以使用的预览插件有很多,但是想在移动设备上实现预览,基本没有可用的方法;
(注:仅折腾过iOS/iPadOS的浏览器);
在Discourse论坛点击帖子后,默认是改变直接改变当前页面的链接;
那么在回退后,原页面有可能会刷新;
(注:仅个人观察结果,未排除其他插件等因素)
某日,我在Greasy Fork上,发现了一个链接预览的脚本,
试了一下,移动端也可以正常使用;
太长不看
碎碎念:
使用一段时间后,发现预览窗口与原页面之间的边界比较模糊;
于是我就在这个脚本的基础上,修修改改;
目前为止还没有折腾出啥好结果,bug有点多,留着自用了 ;
(具体折腾过程略)
下面分享的代码,我仅做了小小的修改:
- 匹配了linux.do 和meta.appinn.net(小众软件);
- 外观/功能 上:修改了iframe样式,效果类似Arc浏览器;
- 加载动画;
- 适配黑暗模式;
- 未完待续…欢迎留言建议
点击查看 脚本
// ==UserScript==
// @name [Discourse] 论坛内链接预览 "Arc"版 -20240803.3
// @version 20240803.3
// @description 点击论坛内链接 -> 在像Arc浏览器的窗口中预览 (阻止默认的页面跳转)
// @match https://linux.do/*
// @match https://meta.appinn.net/*
// @grant none
//
// @icon https://www.svgrepo.com/show/330308/discourse.svg
// ==/UserScript==
(function() {
'use strict';
console.log("脚本初始化...");
// 添加样式到页面头部
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = `
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.iframe-container {
width: 90%;
height: 90%;
position: relative;
}
iframe {
width: calc(100% - 16px);
height: calc(100% - 16px);
border: 8px solid #e0e0e0;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
display: none;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 7px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* 黑暗模式 */
@media (prefers-color-scheme: dark) {
iframe {
border: 8px solid #333333;
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.05);
}
::-webkit-scrollbar-track {
background: #333333;
}
::-webkit-scrollbar-thumb {
background: #666666;
}
::-webkit-scrollbar-thumb:hover {
background: #888888;
}
.loader {
border: 5px solid #333;
border-top: 5px solid #3498db;
}
}
`;
document.head.appendChild(style);
// 创建模态窗口、iframe容器和加载动画
const modal = document.createElement('div');
modal.className = 'modal';
const iframeContainer = document.createElement('div');
iframeContainer.className = 'iframe-container';
const iframe = document.createElement('iframe');
const loader = document.createElement('div');
loader.className = 'loader';
iframeContainer.appendChild(iframe);
iframeContainer.appendChild(loader);
modal.appendChild(iframeContainer);
document.body.appendChild(modal);
// 打开模态窗口的函数
function openModal(url) {
modal.style.display = 'flex';
loader.style.display = 'block';
iframe.style.display = 'none';
iframe.src = url;
// 监听 iframe 的 onload 事件
iframe.onload = function() {
// 当 iframe 内容加载完成后,隐藏加载动画,显示iframe
loader.style.display = 'none';
iframe.style.display = 'block';
};
}
// 点击模态窗口以外的区域关闭模态窗口
modal.addEventListener('click', function(event) {
if (event.target === modal) {
iframe.src = ''; // 清除iframe内容
modal.style.display = 'none';
loader.style.display = 'none';
iframe.style.display = 'none';
}
});
// 检查链接是否为内部话题链接
function isInternalTopicLink(url) {
const currentDomain = window.location.hostname;
const urlObject = new URL(url, window.location.origin);
return (urlObject.hostname === currentDomain) && urlObject.pathname.includes('/t/');
}
// 处理链接点击的函数
function handleLinkClick(e) {
console.log("Link clicked:", e.target); // 调试日志
let target = e.target.closest('a'); // 找到最近的 <a> 标签
if (!target) return; // 如果点击的不是链接或其子元素,直接返回
// 检查是否是我们想要处理的链接类型
if (target.classList.contains('raw-link') ||
(target.closest('.fps-topic') && target.classList.contains('search-link')) ||
isInternalTopicLink(target.href)) {
// 检查链接是否在导航栏或其他功能区域
if (target.closest('header') || target.closest('nav') || target.closest('.d-header')) {
return; // 如果在这些区域,不处理链接
}
e.preventDefault();
e.stopPropagation(); // 防止事件冒泡
let url = target.href;
if (url.startsWith('/')) {
url = window.location.origin + url;
}
console.log("Opening URL:", url); // 调试日志
openModal(url);
}
}
// 使用事件委托来监听整个文档的点击事件
document.addEventListener('click', handleLinkClick, true);
// 添加 MutationObserver 来处理动态加载的内容
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const links = node.querySelectorAll('a');
links.forEach(link => {
if (link.classList.contains('raw-link') ||
(link.closest('.fps-topic') && link.classList.contains('search-link')) ||
isInternalTopicLink(link.href)) {
link.addEventListener('click', handleLinkClick);
}
});
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
console.log("脚本加载完成"); // 调试日志
})();
点击查看 测试结果
经个人测试:
浏览器 | 运行 | 功能 |
---|---|---|
桌面端 | ☐ 是否正常? | ☐ 可嵌套不止1层? |
Chrome + Violentmonkey | ||
Firefox + Violentmonkey | ||
Safari + Stay | ||
Orion + Violentmonkey | … | |
移动端 | ☐ 是否正常? | ☐ 可嵌套不止1层? |
Orion + Violentmonkey | ||
Safari + Stay | ||
Safari + Makeover | … | … |
未完待续… | … | … |
—分割线—
致谢
感谢论坛里的佬们,
你们经常分享自己编写的脚本,质量普遍很高
—分割线—
其他:
如果有对 链接预览 感兴趣的佬,可以一起交流;
----20240803.3