feat: 增强 Mermaid 缩放功能
- 6.1 实现以鼠标为中心的缩放:滚轮缩放时自动调整滚动位置,保持鼠标指向的内容不变 - 6.2 优化缩放动画:按钮点击使用平滑过渡,滚轮缩放禁用过渡以获得更流畅的体验 - 6.3 实现智能缩放:双击图表时,默认大小放大到 2 倍,其他情况重置到 1 倍 - 6.4 优化缩放按钮状态:达到最大/最小缩放级别时自动禁用对应按钮 需求:12.1, 12.2, 12.3, 12.4, 12.5, 20.3
This commit is contained in:
@@ -5671,31 +5671,74 @@ void 0;
|
|||||||
const step = 0.25;
|
const step = 0.25;
|
||||||
|
|
||||||
// 更新缩放显示
|
// 更新缩放显示
|
||||||
const updateZoom = () => {
|
// 需求 12.2, 12.3: 使用 CSS transform 实现硬件加速和平滑过渡
|
||||||
|
// 需求 12.4, 20.3: 更新按钮状态
|
||||||
|
const updateZoom = (useTransition = true) => {
|
||||||
|
// 控制是否使用过渡动画
|
||||||
|
if (!useTransition) {
|
||||||
|
inner.style.transition = 'none';
|
||||||
|
} else {
|
||||||
|
inner.style.transition = 'transform 0.3s ease';
|
||||||
|
}
|
||||||
|
|
||||||
inner.style.transform = `scale(${scale})`;
|
inner.style.transform = `scale(${scale})`;
|
||||||
controls.querySelector('.mermaid-zoom-level').textContent = Math.round(scale * 100) + '%';
|
controls.querySelector('.mermaid-zoom-level').textContent = Math.round(scale * 100) + '%';
|
||||||
|
|
||||||
|
// 更新按钮状态
|
||||||
|
const zoomInBtn = controls.querySelector('[data-action="zoom-in"]');
|
||||||
|
const zoomOutBtn = controls.querySelector('[data-action="zoom-out"]');
|
||||||
|
|
||||||
|
// 禁用/启用放大按钮
|
||||||
|
if (scale >= maxScale) {
|
||||||
|
zoomInBtn.disabled = true;
|
||||||
|
zoomInBtn.style.opacity = '0.4';
|
||||||
|
zoomInBtn.style.cursor = 'not-allowed';
|
||||||
|
} else {
|
||||||
|
zoomInBtn.disabled = false;
|
||||||
|
zoomInBtn.style.opacity = '1';
|
||||||
|
zoomInBtn.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用/启用缩小按钮
|
||||||
|
if (scale <= minScale) {
|
||||||
|
zoomOutBtn.disabled = true;
|
||||||
|
zoomOutBtn.style.opacity = '0.4';
|
||||||
|
zoomOutBtn.style.cursor = 'not-allowed';
|
||||||
|
} else {
|
||||||
|
zoomOutBtn.disabled = false;
|
||||||
|
zoomOutBtn.style.opacity = '1';
|
||||||
|
zoomOutBtn.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复过渡(用于下次按钮点击)
|
||||||
|
if (!useTransition) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
inner.style.transition = 'transform 0.3s ease';
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 绑定按钮事件
|
// 绑定按钮事件
|
||||||
controls.addEventListener('click', (e) => {
|
controls.addEventListener('click', (e) => {
|
||||||
const btn = e.target.closest('.mermaid-zoom-btn');
|
const btn = e.target.closest('.mermaid-zoom-btn');
|
||||||
if (!btn) return;
|
if (!btn || btn.disabled) return; // 忽略禁用的按钮
|
||||||
|
|
||||||
const action = btn.dataset.action;
|
const action = btn.dataset.action;
|
||||||
|
|
||||||
if (action === 'zoom-in' && scale < maxScale) {
|
if (action === 'zoom-in' && scale < maxScale) {
|
||||||
scale = Math.min(scale + step, maxScale);
|
scale = Math.min(scale + step, maxScale);
|
||||||
updateZoom();
|
updateZoom(true); // 按钮点击使用平滑过渡
|
||||||
} else if (action === 'zoom-out' && scale > minScale) {
|
} else if (action === 'zoom-out' && scale > minScale) {
|
||||||
scale = Math.max(scale - step, minScale);
|
scale = Math.max(scale - step, minScale);
|
||||||
updateZoom();
|
updateZoom(true); // 按钮点击使用平滑过渡
|
||||||
} else if (action === 'zoom-reset') {
|
} else if (action === 'zoom-reset') {
|
||||||
scale = 1;
|
scale = 1;
|
||||||
updateZoom();
|
updateZoom(true); // 重置使用平滑过渡
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 鼠标滚轮缩放(按住 Ctrl)
|
// 鼠标滚轮缩放(按住 Ctrl)
|
||||||
|
// 需求 12.1: 以鼠标为中心进行缩放
|
||||||
container.addEventListener('wheel', (e) => {
|
container.addEventListener('wheel', (e) => {
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -5704,8 +5747,24 @@ void 0;
|
|||||||
const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
|
const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
|
||||||
|
|
||||||
if (newScale !== scale) {
|
if (newScale !== scale) {
|
||||||
|
// 获取鼠标相对于容器的位置
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const mouseX = e.clientX - rect.left;
|
||||||
|
const mouseY = e.clientY - rect.top;
|
||||||
|
|
||||||
|
// 计算鼠标相对于内容的位置(缩放前)
|
||||||
|
const scrollLeft = container.scrollLeft;
|
||||||
|
const scrollTop = container.scrollTop;
|
||||||
|
const offsetX = (scrollLeft + mouseX) / scale;
|
||||||
|
const offsetY = (scrollTop + mouseY) / scale;
|
||||||
|
|
||||||
|
// 更新缩放(滚轮缩放不使用过渡动画,更流畅)
|
||||||
scale = newScale;
|
scale = newScale;
|
||||||
updateZoom();
|
updateZoom(false);
|
||||||
|
|
||||||
|
// 调整滚动位置,使鼠标位置保持不变
|
||||||
|
container.scrollLeft = offsetX * scale - mouseX;
|
||||||
|
container.scrollTop = offsetY * scale - mouseY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
@@ -5757,6 +5816,34 @@ void 0;
|
|||||||
inner.addEventListener('dragstart', (e) => {
|
inner.addEventListener('dragstart', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 需求 12.5: 双击智能缩放
|
||||||
|
// 双击图表时,如果当前是默认大小则放大到 2 倍,否则重置到 1 倍
|
||||||
|
inner.addEventListener('dblclick', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 获取双击位置
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const mouseX = e.clientX - rect.left;
|
||||||
|
const mouseY = e.clientY - rect.top;
|
||||||
|
|
||||||
|
// 计算鼠标相对于内容的位置(缩放前)
|
||||||
|
const scrollLeft = container.scrollLeft;
|
||||||
|
const scrollTop = container.scrollTop;
|
||||||
|
const offsetX = (scrollLeft + mouseX) / scale;
|
||||||
|
const offsetY = (scrollTop + mouseY) / scale;
|
||||||
|
|
||||||
|
// 智能缩放:如果接近默认大小则放大,否则重置
|
||||||
|
const targetScale = Math.abs(scale - 1) < 0.1 ? 2 : 1;
|
||||||
|
scale = targetScale;
|
||||||
|
updateZoom(true); // 使用平滑过渡
|
||||||
|
|
||||||
|
// 调整滚动位置,使双击位置保持在视野中心
|
||||||
|
setTimeout(() => {
|
||||||
|
container.scrollLeft = offsetX * scale - mouseX;
|
||||||
|
container.scrollTop = offsetY * scale - mouseY;
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---------- 主题切换监听 ----------
|
// ---------- 主题切换监听 ----------
|
||||||
|
|||||||
35
style.css
35
style.css
@@ -921,7 +921,7 @@ article .wp-block-separator {
|
|||||||
animation: mermaidFadeIn 0.3s ease-in-out forwards;
|
animation: mermaidFadeIn 0.3s ease-in-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 需求 18.2: Mermaid 加载动画容器 */
|
/* 需<EFBFBD>?18.2: Mermaid 加载动画容器 */
|
||||||
.mermaid-loading {
|
.mermaid-loading {
|
||||||
background: var(--color-foreground);
|
background: var(--color-foreground);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -1034,6 +1034,12 @@ html.darkmode .mermaid-zoom-controls {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mermaid-zoom-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mermaid-zoom-level {
|
.mermaid-zoom-level {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1047,6 +1053,7 @@ html.darkmode .mermaid-zoom-controls {
|
|||||||
html.darkmode .mermaid-zoom-level {
|
html.darkmode .mermaid-zoom-level {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 按钮 tooltip */
|
/* 按钮 tooltip */
|
||||||
.mermaid-zoom-btn[title] {
|
.mermaid-zoom-btn[title] {
|
||||||
@@ -1134,7 +1141,7 @@ article.card .mermaid-container {
|
|||||||
margin: 20px -20px;
|
margin: 20px -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式调整 */
|
/* 响应式调<EFBFBD>?*/
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.mermaid-container {
|
.mermaid-container {
|
||||||
margin: 15px -15px;
|
margin: 15px -15px;
|
||||||
@@ -2503,7 +2510,7 @@ html:not(.is-home) .cover-scroll-down {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 左侧栏 */
|
/* 左侧<EFBFBD>?*/
|
||||||
|
|
||||||
.leftbar-banner {
|
.leftbar-banner {
|
||||||
|
|
||||||
@@ -2863,7 +2870,7 @@ html.navbar-absolute #leftbar_part2.sticky {
|
|||||||
max-height: calc(100vh - 110px - var(--leftbar-part3-height, 0px));
|
max-height: calc(100vh - 110px - var(--leftbar-part3-height, 0px));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 只有文章目录标签需要滚动 */
|
/* 只有文章目录标签需要滚<EFBFBD>?*/
|
||||||
#leftbar_part2.sticky #leftbar_tab_catalog {
|
#leftbar_part2.sticky #leftbar_tab_catalog {
|
||||||
max-height: calc(100vh - 200px - var(--leftbar-part3-height, 0px));
|
max-height: calc(100vh - 200px - var(--leftbar-part3-height, 0px));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -7717,7 +7724,7 @@ html.darkmode #post_comment.post-comment-force-privatemode-off .comment-post-pri
|
|||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*短代码 Github*/
|
/*短代<EFBFBD>?Github*/
|
||||||
|
|
||||||
.github-info-card {
|
.github-info-card {
|
||||||
|
|
||||||
@@ -11197,7 +11204,7 @@ html.darkmode.amoled-dark #content:after {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端已完成任务分隔栏 */
|
/* 移动端已完成任务分隔<EFBFBD>?*/
|
||||||
.mobile-todo-completed-divider {
|
.mobile-todo-completed-divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -12816,7 +12823,7 @@ html.navbar-absolute #leftbar_part3.sticky {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 折叠已完成按钮 */
|
/* 折叠已完成按<EFBFBD>?*/
|
||||||
.todo-collapse-btn {
|
.todo-collapse-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -14416,7 +14423,7 @@ article.post.card {
|
|||||||
transition: all var(--m3-motion-duration-short) var(--m3-motion-standard);
|
transition: all var(--m3-motion-duration-short) var(--m3-motion-standard);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* M3 代码块 */
|
/* M3 代码<EFBFBD>?*/
|
||||||
article pre:not(.hljs-codeblock) {
|
article pre:not(.hljs-codeblock) {
|
||||||
border-radius: var(--m3-shape-md) !important;
|
border-radius: var(--m3-shape-md) !important;
|
||||||
}
|
}
|
||||||
@@ -14965,7 +14972,7 @@ html.darkmode #leftbar_part2 {
|
|||||||
border-color: var(--themecolor);
|
border-color: var(--themecolor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 评论项悬浮效果 */
|
/* 评论项悬浮效<EFBFBD>?*/
|
||||||
.comment-body {
|
.comment-body {
|
||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
border-radius: var(--m3-shape-md);
|
border-radius: var(--m3-shape-md);
|
||||||
@@ -16923,6 +16930,12 @@ html.darkmode .mermaid-zoom-controls {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mermaid-zoom-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mermaid-zoom-level {
|
.mermaid-zoom-level {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -16983,7 +16996,7 @@ html.darkmode .mermaid-container {
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 响应式设计 ---------- */
|
/* ---------- 响应式设<EFBFBD>?---------- */
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.mermaid-container {
|
.mermaid-container {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -17149,7 +17162,7 @@ html.darkmode .mermaid-error-code pre {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 滚动条样式(Webkit) ---------- */
|
/* ---------- 滚动条样式(Webkit<EFBFBD>?---------- */
|
||||||
.mermaid-container::-webkit-scrollbar {
|
.mermaid-container::-webkit-scrollbar {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user