This commit is contained in:
Daniel Vazome 2025-11-22 12:26:16 +07:00 committed by GitHub
commit c6c5273d53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 57 additions and 28 deletions

View File

@ -12,6 +12,7 @@ class DiagramPanZoom {
private scale = 1 private scale = 1
private readonly MIN_SCALE = 0.5 private readonly MIN_SCALE = 0.5
private readonly MAX_SCALE = 3 private readonly MAX_SCALE = 3
private resizeTimeout?: number
cleanups: (() => void)[] = [] cleanups: (() => void)[] = []
@ -29,7 +30,7 @@ class DiagramPanZoom {
const mouseDownHandler = this.onMouseDown.bind(this) const mouseDownHandler = this.onMouseDown.bind(this)
const mouseMoveHandler = this.onMouseMove.bind(this) const mouseMoveHandler = this.onMouseMove.bind(this)
const mouseUpHandler = this.onMouseUp.bind(this) const mouseUpHandler = this.onMouseUp.bind(this)
const resizeHandler = this.resetTransform.bind(this) const resizeHandler = this.onResize.bind(this)
this.container.addEventListener("mousedown", mouseDownHandler) this.container.addEventListener("mousedown", mouseDownHandler)
document.addEventListener("mousemove", mouseMoveHandler) document.addEventListener("mousemove", mouseMoveHandler)
@ -44,10 +45,20 @@ class DiagramPanZoom {
) )
} }
private onResize() {
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout)
}
this.resizeTimeout = window.setTimeout(() => this.resetTransform(), 50)
}
cleanup() { cleanup() {
for (const cleanup of this.cleanups) { for (const cleanup of this.cleanups) {
cleanup() cleanup()
} }
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout)
}
} }
private setupNavigationControls() { private setupNavigationControls() {
@ -102,16 +113,22 @@ class DiagramPanZoom {
private zoom(delta: number) { private zoom(delta: number) {
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE) const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
// Zoom around center // Zoom around the center of the container viewport, not the content
const rect = this.content.getBoundingClientRect() const containerRect = this.container.getBoundingClientRect()
const centerX = rect.width / 2 const centerX = containerRect.width / 2
const centerY = rect.height / 2 const centerY = containerRect.height / 2
const scaleDiff = newScale - this.scale // Calculate the point we're zooming around relative to current transform
this.currentPan.x -= centerX * scaleDiff const zoomPointX = (centerX - this.currentPan.x) / this.scale
this.currentPan.y -= centerY * scaleDiff const zoomPointY = (centerY - this.currentPan.y) / this.scale
// Update scale
this.scale = newScale this.scale = newScale
// Adjust pan to keep the zoom point in the same screen position
this.currentPan.x = centerX - zoomPointX * this.scale
this.currentPan.y = centerY - zoomPointY * this.scale
this.updateTransform() this.updateTransform()
} }
@ -120,12 +137,23 @@ class DiagramPanZoom {
} }
private resetTransform() { private resetTransform() {
this.scale = 1 // Get container dimensions
const containerRect = this.container.getBoundingClientRect()
const svg = this.content.querySelector("svg")! const svg = this.content.querySelector("svg")!
const svgRect = svg.getBoundingClientRect()
// svgRect is scaled by this.scale. We want the unscaled size.
const svgWidth = svgRect.width / this.scale
const svgHeight = svgRect.height / this.scale
this.scale = 1
// Calculate center position relative to container
this.currentPan = { this.currentPan = {
x: svg.getBoundingClientRect().width / 2, x: (containerRect.width - svgWidth) / 2,
y: svg.getBoundingClientRect().height / 2, y: (containerRect.height - svgHeight) / 2,
} }
this.updateTransform() this.updateTransform()
} }
} }
@ -237,8 +265,10 @@ document.addEventListener("nav", async () => {
popupContainer.classList.add("active") popupContainer.classList.add("active")
container.style.cursor = "grab" container.style.cursor = "grab"
// Initialize pan-zoom after showing the popup // Initialize pan-zoom after showing the popup and let the layout settle
panZoom = new DiagramPanZoom(container, content) requestAnimationFrame(() => {
panZoom = new DiagramPanZoom(container, content)
})
} }
function hideMermaid() { function hideMermaid() {

View File

@ -53,7 +53,7 @@ pre {
} }
& > #mermaid-space { & > #mermaid-space {
border: 1px solid var(--lightgray); border: 0.2vh solid var(--lightgray);
background-color: var(--light); background-color: var(--light);
border-radius: 5px; border-radius: 5px;
position: fixed; position: fixed;
@ -86,14 +86,14 @@ pre {
& > .mermaid-controls { & > .mermaid-controls {
position: absolute; position: absolute;
bottom: 20px; bottom: 2.5vh;
right: 20px; right: 2.5vw;
display: flex; display: flex;
gap: 8px; gap: 0.5vw;
padding: 8px; padding: 1vh 0.5vw;
background: var(--light); background: var(--light);
border: 1px solid var(--lightgray); border: 0.2vh solid var(--lightgray);
border-radius: 6px; border-radius: 0.8vh;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 2; z-index: 2;
@ -101,15 +101,15 @@ pre {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 32px; width: 4.5vh;
height: 32px; height: 4.5vh;
padding: 0; padding: 0;
border: 1px solid var(--lightgray); border: 0.2vh solid var(--lightgray);
background: var(--light); background: var(--light);
color: var(--dark); color: var(--dark);
border-radius: 4px; border-radius: 0.5vh;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 2vh;
font-family: var(--bodyFont); font-family: var(--bodyFont);
transition: all 0.2s ease; transition: all 0.2s ease;
@ -121,11 +121,10 @@ pre {
transform: translateY(1px); transform: translateY(1px);
} }
// Style the reset button differently
&:nth-child(2) { &:nth-child(2) {
width: auto; width: auto;
padding: 0 12px; padding: 0 1vw;
font-size: 14px; font-size: 2vh;
} }
} }
} }