Fix family map fullscreen jitter and long recording playback

This commit is contained in:
Codex
2026-10-06 22:46:50 +08:00
parent 7d4329f773
commit 12556bf10f
13 changed files with 92 additions and 14 deletions
+21 -6
View File
@@ -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) {