Fix family map fullscreen jitter and long recording playback
This commit is contained in:
@@ -7,6 +7,18 @@
|
||||
|
||||
.family-workspace.family-expanded{position:fixed;inset:0;z-index:8;}
|
||||
|
||||
/* Keep scaled descendants and fractional fullscreen sizes out of the
|
||||
scrollable overflow calculation. Reserve one gutter instead of alternating. */
|
||||
.family-map-viewport{scrollbar-gutter:stable;overflow-anchor:none}
|
||||
.family-map-space{min-width:0;min-height:0;overflow:clip}
|
||||
.family-workspace:is(:fullscreen,.family-expanded){min-height:0;grid-template-rows:minmax(0,1fr)}
|
||||
.family-workspace:is(:fullscreen,.family-expanded) .family-canvas-panel{min-height:0}
|
||||
.family-workspace:is(:fullscreen,.family-expanded) .family-map-heading,
|
||||
.family-workspace:is(:fullscreen,.family-expanded) .family-map-controls,
|
||||
.family-workspace:is(:fullscreen,.family-expanded) .family-map-footer{flex-shrink:0}
|
||||
.family-workspace.family-expanded{z-index:1100}
|
||||
@media(max-width:1000px){.family-workspace:is(:fullscreen,.family-expanded) .family-canvas-panel{min-height:65vh}}
|
||||
|
||||
/* Versions remain readable on a small screen, including long family narratives. */
|
||||
#history-dialog{width:min(1040px,94vw)}
|
||||
#history-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;max-height:260px;overflow:auto;margin:16px 0}
|
||||
|
||||
+21
-6
@@ -10,9 +10,24 @@ class FamilyMap {
|
||||
viewport.addEventListener('pointermove',e=>{if(!this.drag)return;viewport.scrollLeft=this.drag.left+this.drag.x-e.clientX;viewport.scrollTop=this.drag.top+this.drag.y-e.clientY;});
|
||||
const release=()=>{this.drag=null;viewport.classList.remove('panning');};
|
||||
viewport.addEventListener('pointerup',release);viewport.addEventListener('pointercancel',release);viewport.addEventListener('lostpointercapture',release);
|
||||
this.resize=new ResizeObserver(()=>{if(this.autoFit)this.fit();else this.applyScale();});this.resize.observe(viewport);
|
||||
this.resizeFrame=0;this.resizeSize='';
|
||||
this.resize=new ResizeObserver(()=>this.scheduleResize());this.resize.observe(viewport,{box:'border-box'});
|
||||
}
|
||||
reset(){cancelAnimationFrame(this.resizeFrame);this.resizeFrame=0;this.resizeSize='';this.key='';this.data=null;this.graph=null;this.viewport.replaceChildren();this.autoFit=true;this.drag=null;$('#family-zoom-value').textContent='100%';}
|
||||
viewportSize(){
|
||||
const v=this.viewport,r=v.getBoundingClientRect();
|
||||
// clientHeight rounds fractional pixels up; using it as a child height can
|
||||
// create a scrollbar, remove it on the next fit, and repeat indefinitely.
|
||||
return {width:Math.floor(Math.min(v.clientWidth,r.width)),height:Math.floor(Math.min(v.clientHeight,r.height))};
|
||||
}
|
||||
scheduleResize(){
|
||||
if(this.resizeFrame)return;
|
||||
this.resizeFrame=requestAnimationFrame(()=>{
|
||||
this.resizeFrame=0;const {width,height}=this.viewportSize(),key=width+':'+height;
|
||||
if(!width||!height||key===this.resizeSize)return;
|
||||
this.resizeSize=key;if(this.autoFit)this.fit();else this.applyScale();
|
||||
});
|
||||
}
|
||||
reset(){this.key='';this.data=null;this.viewport.replaceChildren();this.autoFit=true;this.drag=null;$('#family-zoom-value').textContent='100%';}
|
||||
render(data) {
|
||||
this.data=data;
|
||||
const key=JSON.stringify([data.people,data.links,data.showArchived,data.selfId]);
|
||||
@@ -46,16 +61,16 @@ class FamilyMap {
|
||||
}
|
||||
applyScale() {
|
||||
const v=this.viewport,g=this.graph,space=v.querySelector('.family-map-space'),surface=v.querySelector('.family-map-surface');
|
||||
if(!g||!space||!v.clientWidth)return;
|
||||
const w=Math.max(v.clientWidth,g.width*this.scale+32),h=Math.max(v.clientHeight,g.height*this.scale+32);
|
||||
const {width,height}=this.viewportSize();if(!g||!space||!width||!height)return;
|
||||
const w=Math.max(width,Math.ceil(g.width*this.scale+32)),h=Math.max(height,Math.ceil(g.height*this.scale+32));
|
||||
space.style.width=w+'px';space.style.height=h+'px';
|
||||
this.left=(w-g.width*this.scale)/2;this.top=(h-g.height*this.scale)/2;
|
||||
surface.style.transform=`translate(${this.left}px,${this.top}px) scale(${this.scale})`;
|
||||
$('#family-zoom-value').textContent=Math.round(this.scale*100)+'%';
|
||||
}
|
||||
fit() {
|
||||
const v=this.viewport,g=this.graph;if(!g||!v.clientWidth||!v.clientHeight)return;
|
||||
this.autoFit=true;this.scale=Math.max(.001,Math.min(1,(v.clientWidth-40)/g.width,(v.clientHeight-40)/g.height));
|
||||
const v=this.viewport,g=this.graph,{width,height}=this.viewportSize();if(!g||!width||!height)return;
|
||||
this.autoFit=true;this.scale=Math.max(.001,Math.min(1,(width-40)/g.width,(height-40)/g.height));
|
||||
this.applyScale();v.scrollTo(0,0);
|
||||
}
|
||||
zoom(factor) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,3 +1,3 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="theme-color" content="#244d43"><link rel="icon" href="/zhao-icon.png"><link rel="apple-touch-icon" href="/zhao-icon.png"><title>赵府 · 智能数字管家</title> <script type="module" crossorigin src="/ui/assets/index-CWMAn71z.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/ui/assets/index-B5ZdIQcb.css">
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="theme-color" content="#244d43"><link rel="icon" href="/zhao-icon.png"><link rel="apple-touch-icon" href="/zhao-icon.png"><title>赵府 · 智能数字管家</title> <script type="module" crossorigin src="/ui/assets/index-BN-llATb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/ui/assets/index-B2d9n-y0.css">
|
||||
</head><body><div id="app"></div></body></html>
|
||||
|
||||
Reference in New Issue
Block a user