اضافه شدن قابلیت مقایسه شماتیک
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { Camera } from './camera.js';
|
||||
|
||||
/** Pointer/touch camera adapter shared by both comparison panes. */
|
||||
export function bindCamera(surface, svg, bounds, onChange = () => {}) {
|
||||
const camera = new Camera(bounds, surface.clientWidth, surface.clientHeight);
|
||||
let frame = 0;
|
||||
const pointers = new Map();
|
||||
function paint(notify = true) {
|
||||
if (!frame) frame = requestAnimationFrame(() => { frame = 0; svg.setAttribute('viewBox', camera.viewBox); });
|
||||
if (notify) onChange(view);
|
||||
}
|
||||
const view = { camera, paint,
|
||||
pose() { return { center: camera.world(camera.width/2,camera.height/2), scale: camera.scale }; },
|
||||
apply({center,scale}) { camera.scale=scale; camera.x=camera.width/2-center.x*scale; camera.y=camera.height/2-center.y*scale; paint(false); },
|
||||
fit() { camera.fit(); paint(); },
|
||||
focus(box) {
|
||||
const scale=Math.min(camera.width/Math.max(20,box.width+30),camera.height/Math.max(20,box.height+30),camera.fitScale*100);
|
||||
view.apply({center:{x:box.x+box.width/2,y:box.y+box.height/2},scale}); onChange(view);
|
||||
},
|
||||
destroy() { cancelAnimationFrame(frame); observer.disconnect(); events.abort(); }
|
||||
};
|
||||
const events = new AbortController(); const options={ signal:events.signal };
|
||||
surface.addEventListener('wheel',e => {
|
||||
e.preventDefault();const rect=surface.getBoundingClientRect();
|
||||
const delta=e.deltaY*(e.deltaMode===1?16:e.deltaMode===2?camera.height:1);
|
||||
camera.zoom(Math.exp(-Math.max(-400,Math.min(400,delta))*.002),e.clientX-rect.left,e.clientY-rect.top);paint();
|
||||
},{...options,passive:false});
|
||||
surface.addEventListener('pointerdown',e => {
|
||||
if(e.button!==0)return;surface.focus();surface.setPointerCapture(e.pointerId);
|
||||
pointers.set(e.pointerId,{x:e.clientX,y:e.clientY});
|
||||
},options);
|
||||
function metrics() {
|
||||
const p=[...pointers.values()], rect=surface.getBoundingClientRect();
|
||||
return { x:p.reduce((s,p)=>s+p.x,0)/p.length-rect.left,y:p.reduce((s,p)=>s+p.y,0)/p.length-rect.top,
|
||||
distance:p.length>1?Math.hypot(p[0].x-p[1].x,p[0].y-p[1].y):0 };
|
||||
}
|
||||
surface.addEventListener('pointermove',e => {
|
||||
if(!pointers.has(e.pointerId))return;
|
||||
const before=metrics();pointers.set(e.pointerId,{x:e.clientX,y:e.clientY});const after=metrics();
|
||||
if(before.distance&&after.distance)camera.zoom(after.distance/before.distance,before.x,before.y);
|
||||
camera.pan(after.x-before.x,after.y-before.y);paint();
|
||||
},options);
|
||||
for(const type of ['pointerup','pointercancel','lostpointercapture'])surface.addEventListener(type,e=>pointers.delete(e.pointerId),options);
|
||||
surface.addEventListener('dblclick',()=>view.fit(),options);
|
||||
surface.addEventListener('keydown',e=>{
|
||||
if(e.key==='Home'||e.key==='0')camera.fit();
|
||||
else if(e.key==='+'||e.key==='=')camera.zoom(1.25,camera.width/2,camera.height/2);
|
||||
else if(e.key==='-')camera.zoom(.8,camera.width/2,camera.height/2);
|
||||
else if(e.key.startsWith('Arrow'))camera.pan(e.key==='ArrowLeft'?40:e.key==='ArrowRight'?-40:0,e.key==='ArrowUp'?40:e.key==='ArrowDown'?-40:0);
|
||||
else return;e.preventDefault();paint();
|
||||
},options);
|
||||
const observer=new ResizeObserver(()=>{
|
||||
const pose=view.pose();camera.resize(surface.clientWidth,surface.clientHeight);
|
||||
camera.fitScale=Math.min(camera.width/bounds.width,camera.height/bounds.height);view.apply(pose);
|
||||
});observer.observe(surface);paint(false);return view;
|
||||
}
|
||||
مرجع در شماره جدید
مسدود کردن کاربر