// ==UserScript==
// @name 知乎++
// @description 清理标题括号数字,替换消息/私信按钮,收藏夹可配置,解除复制限制,隐藏侧边栏,并增强过滤干扰内容、调整布局,重新对阅读页面卡片化设计,美化界面极大提升阅读体验,新增护眼色开关(全局持久化)
// @namespace http://tampermonkey.net/
// @icon https://www.zhihu.com/favicon.ico
// @license MIT
// @version 4.0
// @author ddrwin
// @match *://*/*
// @match https://*.zhihu.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @note 2026.03.22 V1.0 全局清理网页标题开头的未读数字提示(如 (3条消息))
// @note 2026.03.23 V1.2 将首页消息/私信按钮替换为收藏夹、最近浏览
// @note 2026.03.24 V1.3 隐藏右侧边栏及创作中心,清理消息/私信按钮上的通知数字
// @note 2026.03.25 V2.1 创作中心改为可配置的收藏设置,允许清空地址,解除复制版权限制,仅保留纯净文本
// @note 2026.03.25 V2.2 增加文章页居中并自适应宽度,自动点击“查看全部回答”,隐藏首页“热榜”和“视频”选项卡,只保留“推荐”
// @note 2026.03.25 V3.0 首页、搜索结果页、问题页答案卡片美化,热榜条目卡片美化;过滤推荐页中的视频、文章、链接卡片、教育卡片、专栏、电商等干扰内容,过滤答案页中的视频答案,调整推荐页、问题页、搜索页主列宽度为900px,在问题页标题下方显示修改时间, 隐藏搜索框占位符,加大问题标题字体
// @note 2026.03.26 V3.1 统一卡片样式为 Baidu++ 风格(浅绿色背景、蓝色边框、阴影)
// @note 2026.03.27 V3.5 动态更新页面类型类,改用 CSS 变量方案实现护眼色,彻底消除 SPA 路由切换后类残留导致的样式错乱失效的问题
// @note 2026.03.28 V3.9 彻底解决 SPA 路由切换后类残留导致的样式错乱;新增圈子页面(/ring)完整样式支持;优化专栏主页及个人主页卡片布局。
// @note 2026.03.28 V4.0 设置面板增加动画开关,新增 ANIM_KEY 存储键,默认值 true(默认开启动画),与护眼色独立存储、独立控制。
// @downloadURL https://update.greasyfork.icu/scripts/571059/%E7%9F%A5%E4%B9%8E%2B%2B.user.js
// @updateURL https://update.greasyfork.icu/scripts/571059/%E7%9F%A5%E4%B9%8E%2B%2B.meta.js
// ==/UserScript==
(function() {
'use strict';
// ============================================================
// 1. 颜色配置:两套色板,面板切换时只改 CSS 变量,样式规则不变
// ============================================================
/**
* 在这里集中管理两套颜色。
* - softEye:护眼绿色系
* - white:默认白色系
*
* 新增/修改颜色时只需改这里,CSS 规则无需动。
*/
const COLOR_PALETTES = {
softEye: {
cardBg: '#F5FCFA',
cardBgHover: '#E9F7F3',
cardBorder: '#3D7CD4',
cardBorderHover: '#2c6fc7',
cardShadow: '0 8px 20px rgba(0,0,0,0.15)',
cardShadowHover: '0 12px 28px rgba(0,0,0,0.2)',
},
white: {
cardBg: '#ffffff',
cardBgHover: '#ffffff',
cardBorder: '#e1e8ed',
cardBorderHover: '#cbd5e0',
cardShadow: '0 2px 8px rgba(0,0,0,0.08)',
cardShadowHover: '0 8px 20px rgba(0,0,0,0.12)',
},
};
// ============================================================
// 2. 配置存储
// ============================================================
const STORAGE_KEY = 'zhihu_collection_url';
const SOFT_EYE_KEY = 'zhihu_soft_eye';
const ANIM_KEY = 'zhihu_anim';
const getCollectionUrl = () => GM_getValue(STORAGE_KEY, '');
const saveCollectionUrl = (url) => {
if (url === null || url === undefined) return false;
GM_setValue(STORAGE_KEY, url.trim());
return true;
};
const getSoftEyeEnabled = () => GM_getValue(SOFT_EYE_KEY, true);
const setSoftEyeEnabled = (enabled) => {
GM_setValue(SOFT_EYE_KEY, enabled);
applyTheme();
};
const getAnimEnabled = () => GM_getValue(ANIM_KEY, true);
const setAnimEnabled = (enabled) => {
GM_setValue(ANIM_KEY, enabled);
applyTheme();
};
// ============================================================
// 3. 核心:用 CSS 变量驱动主题切换
//
// CSS 变量写在 document.documentElement(即 )上,
// 不依赖 body class,SPA 路由切换、框架重渲染都不会丢失,
// 无需任何 MutationObserver 来"防御"。
// ============================================================
/**
* 把对应色板的值写入 CSS 自定义属性。
* 调用一次即永久生效,直到页面关闭或再次调用。
*/
const applyTheme = () => {
const softEye = getSoftEyeEnabled();
const anim = getAnimEnabled();
const palette = COLOR_PALETTES[softEye ? 'softEye' : 'white'];
const root = document.documentElement;
root.style.setProperty('--zh-card-bg', palette.cardBg);
root.style.setProperty('--zh-card-border', palette.cardBorder);
root.style.setProperty('--zh-card-shadow', palette.cardShadow);
if (anim) {
root.style.setProperty('--zh-card-bg-hover', palette.cardBgHover);
root.style.setProperty('--zh-card-border-hover', palette.cardBorderHover);
root.style.setProperty('--zh-card-shadow-hover', palette.cardShadowHover);
root.style.setProperty('--zh-card-transform-hover', 'translateY(-2px)');
root.style.setProperty('--zh-transition', 'all 0.3s ease');
} else {
root.style.setProperty('--zh-card-bg-hover', palette.cardBg);
root.style.setProperty('--zh-card-border-hover', palette.cardBorder);
root.style.setProperty('--zh-card-shadow-hover', palette.cardShadow);
root.style.setProperty('--zh-card-transform-hover', 'translateY(0)');
root.style.setProperty('--zh-transition', 'none');
}
};
const applySoftEye = () => applyTheme();
// ============================================================
// 4. 设置面板
// ============================================================
const showSettingsPanel = () => {
if (document.getElementById('zh-settings-panel')) return;
const currentUrl = getCollectionUrl();
const overlay = document.createElement('div');
overlay.id = 'zh-settings-overlay';
overlay.style.cssText = `
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.4);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
`;
const panel = document.createElement('div');
panel.id = 'zh-settings-panel';
panel.style.cssText = `
background: #fff;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
width: 360px;
padding: 24px 20px;
text-align: left;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
`;
panel.innerHTML = `
知乎++ 设置
`;
overlay.appendChild(panel);
document.body.appendChild(overlay);
const input = document.getElementById('collection-url-input');
const checkbox = document.getElementById('soft-eye-checkbox');
const animCheckbox = document.getElementById('anim-checkbox');
const confirmBtn = document.getElementById('confirm-settings-btn');
const cancelBtn = document.getElementById('close-settings-btn');
checkbox.checked = getSoftEyeEnabled();
animCheckbox.checked = getAnimEnabled();
confirmBtn.addEventListener('click', () => {
saveCollectionUrl(input.value.trim());
GM_setValue(SOFT_EYE_KEY, checkbox.checked);
GM_setValue(ANIM_KEY, animCheckbox.checked);
applyTheme();
overlay.remove();
});
cancelBtn.addEventListener('click', () => overlay.remove());
};
// ============================================================
// 5. 全局标题清理
// ============================================================
const cleanTitle = () => {
const titleElem = document.getElementsByTagName('title')[0];
if (!titleElem) return;
const newTitle = titleElem.innerText.replace(/^\(\d+(?:[^)]*)?\)\s*/, '');
if (newTitle !== titleElem.innerText) titleElem.innerText = newTitle;
};
cleanTitle();
const titleElem = document.querySelector('title');
if (titleElem) {
const titleObserver = new MutationObserver(() => {
titleObserver.disconnect();
cleanTitle();
titleObserver.observe(titleElem, { childList: true });
});
titleObserver.observe(titleElem, { childList: true });
}
// ============================================================
// 6. 知乎专属功能
// ============================================================
if (window.location.hostname.includes('zhihu.com')) {
// ---------- 6.1 解除复制版权限制 ----------
document.addEventListener('copy', (e) => {
const selection = window.getSelection();
if (selection && selection.toString().trim() !== '') {
const plainText = selection.toString();
e.clipboardData.setData('text/plain', plainText);
e.clipboardData.setData('text/html', plainText.replace(/\n/g, '
'));
e.preventDefault();
e.stopPropagation();
console.log('[知乎定制] 已拦截复制,仅保留纯净文本');
}
}, { capture: true });
// ---------- 6.2 样式注入 ----------
//
// 所有卡片颜色属性统一引用 CSS 变量(--zh-card-*)。
// 切换护眼色时只需改变量值,这段 CSS 无需修改。
// 新增颜色属性:在 COLOR_PALETTES 里加字段,
// 在 applySoftEye 里加一行 setProperty,在这里加一处 var() 引用即可。
//
const style = document.createElement('style');
style.textContent = `
/* 隐藏消息/私信弹窗 */
.PushNotifications-menuContainer,
.Messages-menuContainer,
[id^="Popover"][class*="PushNotifications"],
[id^="Popover"][class*="Messages"] {
display: none !important;
}
/* 隐藏右侧边栏 */
.Post-Row-Content-right,
.css-1qyytj7,
div[data-za-detail-view-path-module="RightSideBar"] {
display: none !important;
}
/* 隐藏顶部导航栏的"付费咨询"和"知学堂"按钮 */
a[href="https://www.zhihu.com/consult"],
a[href="https://www.zhihu.com/education/learning"] {
display: none !important;
}
/* 内容宽度自适应 */
.Post-Row-Content-left {
max-width: 100% !important;
width: 90% !important;
margin-left: 0 !important;
margin-right: auto !important;
}
.Post-Row-Content-left-article {
max-width: 100% !important;
}
/* 首页:只保留"推荐"选项卡 */
a[aria-controls="Topstory-hot"],
a[aria-controls="Topstory-zvideo"],
ul[class~="AppHeader-Tabs"] li:not(:first-child) {
display: none !important;
}
/* 隐藏搜索框占位符 */
::placeholder {
color: transparent !important;
}
/* 推荐页过滤干扰内容 */
.TopstoryItem--advertCard,
.TopstoryItem-isRecommend:has(.VideoAnswerPlayer-video),
.TopstoryItem-isRecommend:has(.ZVideoItem-video),
.TopstoryItem-isRecommend:has(.RichText-video),
.TopstoryItem-isRecommend:has(.CopyrightRichText-richTex),
.TopstoryItem-isRecommend:has(.RichText-LinkCardContainer),
.TopstoryItem-isRecommend:has(.RichText-EduCardContainer),
.TopstoryItem-isRecommend:has(div[data-za-extra-module*="Post"]),
.TopstoryItem-isRecommend:has(.RichText-Ecommerce),
.TopstoryItem-isRecommend:has(.ecommerce-ad-box) {
display: none !important;
}
/* 问题页隐藏视频答案 */
.AnswerItem:has(.VideoCard-video-content),
.AnswerItem:has(.VideoAnswerPlayer) {
display: none !important;
}
/* 主列宽度调整 */
.Topstory-container,
.Topstory-mainColumn,
.Question-main,
.Question-mainColumn,
.Search-container,
.SearchMain {
width: 900px !important;
}
/* 问题页显示修改时间 */
meta[itemprop="dateModified"] {
display: block;
height: 20px;
padding: 10px 0;
margin-top: 10px;
}
meta[itemprop="dateModified"]::after {
content: "修改时间: " attr(content);
color: #8590a6;
font-size: 14px;
}
/* 问题页标题字体加大 */
.ContentItem-title {
font-size: x-large !important;
}
/* ===== 卡片样式:全部使用 CSS 变量,护眼/白色均由变量决定 ===== */
.TopstoryItem:not(.TopstoryItem-feedList),
.SearchResult-Card,
.Question-mainColumn .AnswerItem,
.RelevantQuery,
.HotItem,
.css-i83kfi,
.TopicFeedItem,
.Post-Main.Post-NormalMain,
.CollectionDetailPageItem {
background: var(--zh-card-bg) !important;
border: 1px solid var(--zh-card-border) !important;
border-radius: 12px !important;
box-shadow: var(--zh-card-shadow) !important;
margin-bottom: 20px !important;
padding: 20px 24px !important;
transition: var(--zh-transition) !important;
}
.TopstoryItem:not(.TopstoryItem-feedList):hover,
.SearchResult-Card:hover,
.Question-mainColumn .AnswerItem:hover,
.RelevantQuery:hover,
.HotItem:hover,
.css-i83kfi:hover,
.TopicFeedItem:hover,
.Post-Main.Post-NormalMain:hover,
.CollectionDetailPageItem:hover {
background: var(--zh-card-bg-hover) !important;
border-color: var(--zh-card-border-hover) !important;
box-shadow: var(--zh-card-shadow-hover) !important;
transform: var(--zh-card-transform-hover) !important;
}
/* 问题页答案卡片特殊处理 */
.Question-mainColumn .AnswerItem {
margin-bottom: 0px !important;
}
/* 专栏页已更内容特殊处理 */
.css-1a2rdla,
.css-oarhve {
margin-bottom: 20px !important;
}
/* 拼接答案卡片:与现有卡片共享 CSS 变量,hover 效果一致 */
.zh-injected-answer-card {
background: var(--zh-card-bg) !important;
border: 1px solid var(--zh-card-border) !important;
border-radius: 12px !important;
box-shadow: var(--zh-card-shadow) !important;
margin-bottom: 20px !important;
padding: 20px 24px !important;
transition: var(--zh-transition) !important;
}
.zh-injected-answer-card:hover {
background: var(--zh-card-bg-hover) !important;
border-color: var(--zh-card-border-hover) !important;
box-shadow: var(--zh-card-shadow-hover) !important;
transform: var(--zh-card-transform-hover) !important;
}
/* 相关搜索模块内部样式 */
.RelevantQuery h2 {
font-size: 18px !important;
font-weight: 600 !important;
margin-bottom: 16px !important;
}
.RelevantQuery ul {
display: flex !important;
flex-wrap: wrap !important;
gap: 12px !important;
list-style: none !important;
padding-left: 0 !important;
}
.RelevantQuery li { margin: 0 !important; }
.RelevantQuery li a {
display: inline-block !important;
padding: 6px 14px !important;
background: #ffffff !important;
border-radius: 20px !important;
color: #3476d2 !important;
text-decoration: none !important;
font-size: 14px !important;
transition: all 0.2s ease !important;
}
.RelevantQuery li a:hover {
background: #eef2f6 !important;
color: #0f4c81 !important;
transform: scale(1.02) !important;
}
/* 隐藏搜索结果页右侧边栏 */
.css-knqde { display: none !important; }
/* 浏览历史页主内容区域加宽至900px */
.css-9511cm {
width: 900px !important;
max-width: 900px !important;
margin: 0 auto !important;
}
/* 收藏页主内容区域加宽至900px */
.CollectionsDetailPage-mainColumn {
width: 900px !important;
max-width: 900px !important;
margin: 0 auto !important;
}
/* 专栏主页内容区域加宽至900px(仅 html.zh-column 生效) */
html.zh-column .css-1pariuy,
html.zh-column .css-jqhguc,
html.zh-column .css-44kk6u,
html.zh-column .css-1u9sxdg{
width: 900px !important;
max-width: 900px !important;
}
/* 专栏主页文章卡片样式(仅 html.zh-column 生效,不影响其他页面) */
html.zh-column .css-9w3zhd {
width: 860px !important;
background: var(--zh-card-bg) !important;
border: 1px solid var(--zh-card-border) !important;
border-radius: 12px !important;
box-shadow: var(--zh-card-shadow) !important;
margin-bottom: 20px !important;
margin-left: 20px !important;
padding: 20px 24px !important;
transition: var(--zh-transition) !important;
}
html.zh-column .css-9w3zhd:hover {
background: var(--zh-card-bg-hover) !important;
border-color: var(--zh-card-border-hover) !important;
box-shadow: var(--zh-card-shadow-hover) !important;
transform: var(--zh-card-transform-hover) !important;
}
/* 专题页文章外壳容器:仅保留上下间距,不加卡片装饰 */
html.zh-column .ContentItem.ArticleItem {
margin-bottom: 20px !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
/* 个人主页:页头宽度 900px */
html.zh-people .ProfileHeader,
html.zh-people .Profile-main,
html.zh-people .css-16mzn5c,
html.zh-people .Profile-mainColumn {
max-width: 900px !important;
width: 900px !important;
margin: 0 auto !important;
}
/* 个人主页:回答卡片样式 */
html.zh-people .ContentItem.AnswerItem,
html.zh-people .ContentItem.ArticleItem,
html.zh-people .ContentItem.PinItem {
background: var(--zh-card-bg) !important;
border: 1px solid var(--zh-card-border) !important;
border-radius: 12px !important;
box-shadow: var(--zh-card-shadow) !important;
margin-bottom: 0px !important;
padding: 20px 24px !important;
transition: var(--zh-transition) !important;
}
html.zh-people .ContentItem.AnswerItem:hover,
html.zh-people .ContentItem.ArticleItem:hover,
html.zh-people .ContentItem.PinItem:hover {
background: var(--zh-card-bg-hover) !important;
border-color: var(--zh-card-border-hover) !important;
box-shadow: var(--zh-card-shadow-hover) !important;
transform: var(--zh-card-transform-hover) !important;
}
/* 圈子页面:页头宽度 900px */
html.zh-ring .css-1g878q7 {
max-width: 900px !important;
width: 900px !important;
margin: 0 auto !important;
}
/* 圈子页面(/ring-feeds):PinItem 卡片样式 */
html.zh-ring .ContentItem.PinItem {
background: var(--zh-card-bg) !important;
border: 1px solid var(--zh-card-border) !important;
border-radius: 12px !important;
box-shadow: var(--zh-card-shadow) !important;
margin-bottom: 0px !important;
padding: 20px 24px !important;
transition: var(--zh-transition) !important;
}
html.zh-ring .ContentItem.PinItem:hover {
background: var(--zh-card-bg-hover) !important;
border-color: var(--zh-card-border-hover) !important;
box-shadow: var(--zh-card-shadow-hover) !important;
transform: var(--zh-card-transform-hover) !important;
}
`;
document.head.appendChild(style);
// 页面加载时立即把对应色板写入 CSS 变量,一次搞定
applyTheme();
// ---------- 6.2.1 动态更新页面类型类(解决 SPA 路由切换后类残留问题) ----------
/**
* 根据当前 URL 给 添加对应的类(zh-column / zh-people / zh-ring),
* 并移除旧的类,确保样式只作用于正确页面。
*/
const updatePageClass = () => {
const host = window.location.hostname;
const path = window.location.pathname;
// 先移除所有可能已有的类(避免累积)
document.documentElement.classList.remove('zh-column', 'zh-people', 'zh-ring');
// zh-column:专栏/专题/文章详情相关页面
if (
// zhuanlan.zhihu.com/c_xxx(专栏主页)、/p/xxx(文章详情)
(host === 'zhuanlan.zhihu.com' && /^\/(c_|p\/)/.test(path))
// www.zhihu.com/column/c_xxx(专题主页)、/column-square(专栏广场)
|| (host === 'www.zhihu.com' && /^\/(column\/|column-square)/.test(path))
) {
document.documentElement.classList.add('zh-column');
}
// zh-people:个人主页
if (host === 'www.zhihu.com' && /^\/people\//.test(path)) {
document.documentElement.classList.add('zh-people');
}
// zh-ring:圈子页面(/ring-feeds 和 /ring/host/xxx)
if (host === 'www.zhihu.com' && /^\/ring/.test(path)) {
document.documentElement.classList.add('zh-ring');
}
};
// 初始调用一次
updatePageClass();
// 劫持 history.pushState 和 replaceState,以便在 SPA 路由切换时更新类
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function(...args) {
originalPushState.apply(this, args);
// 延迟更新,等待 DOM 变化完成
setTimeout(updatePageClass, 100);
};
history.replaceState = function(...args) {
originalReplaceState.apply(this, args);
setTimeout(updatePageClass, 100);
};
// 监听浏览器前进/后退事件
window.addEventListener('popstate', () => setTimeout(updatePageClass, 100));
// 补充:监听 title 变化作为"页面已渲染"信号(比 setTimeout 更准确)
// 知乎每次 SPA 路由完成后都会更新 ,此时 DOM 已稳定
const titleElemForRoute = document.querySelector('title');
if (titleElemForRoute) {
new MutationObserver(() => updatePageClass()).observe(titleElemForRoute, { childList: true });
}
// ---------- 6.3 答案文章页:原地注入全部回答 + 无限滚动 ----------
//
// 仅在单答案页 (/question/xxx/answer/xxx) 生效。
// - 拦截"查看全部回答"按钮,阻止跳转,改为在当前页尾部拼接答案列表。
// - 利用知乎 /api/v4/questions/{id}/feeds 接口分批加载,
// 用 paging.next 游标驱动无限滚动,行为与问题页一致。
//
const answerPageMatch = window.location.pathname.match(/\/question\/(\d+)\/answer\/\d+/);
if (answerPageMatch) {
const questionId = answerPageMatch[1];
let injected = false;
let nextFeedUrl = null;
let loadingMore = false;
// 当前答案 ID,用于过滤拼接列表中的重复项
const currentAnswerId = String(window.location.pathname.match(/\/answer\/(\d+)/)[1]);
// 修复懒加载图片:把 data-actualsrc 替换为真实 src
const fixLazyImages = (container) => {
// 方式一:处理
container.querySelectorAll('img[data-actualsrc]').forEach(img => {
img.src = img.dataset.actualsrc;
img.removeAttribute('data-actualsrc');
});
// 方式二:处理被