From bae14f02780a13651ea0184842e8d3744955e538 Mon Sep 17 00:00:00 2001 From: twarped Date: Thu, 25 Jan 2024 22:10:46 -0700 Subject: [PATCH] Tried out some zoom stuff --- formatpedigree3.0.js | 150 +++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 55 deletions(-) diff --git a/formatpedigree3.0.js b/formatpedigree3.0.js index 32f378a..81d399c 100644 --- a/formatpedigree3.0.js +++ b/formatpedigree3.0.js @@ -1,75 +1,115 @@ const content = document.getElementById('content'); const contentWrapper = document.getElementById('contentWrapper'); -const clamp = (num, min, max) => { +/** + * + * @param {number} num The number to clamp + * @param {number} min The lowest value the number can be + * @param {number} max The highest value the number can be + * @returns {number} The clamped number + */ +function clamp(num, min, max) { return Math.min(Math.max(num, min), max) } -let lastMousePosition = new MouseEvent('mousedown'); -let isMouseDown = false; -let contentTranslate = { - x: content.offsetWidth / -2, - y: content.offsetHeight / -2, +/** + * + * @param {HTMLElement=} el The element we are getting the translate data from. Defaults to content + * @returns {{ x: number, y: number }} The x and y translations of the element + */ +function getElementTranslate(el = content) { + return el.style.translate == '' ? { x: el.offsetWidth / -2, y: el.offsetHeight / -2} : Object.fromEntries(el.style.translate.split(' ').map((v, i) => [i - 1 ? 'x' : 'y', parseInt(v.split('px')[0])])); } -let scale = 1; -let zoomDelta = 0; +/** + * Uses lastMouseEvent to calculate a smooth translate for panning an element + * @param {MouseEvent} ev The MouseEvent to convert to a smooth scroll/smooth translate + * @param {HTMLElement} el Defaults to the content element, but can be changed. However, I haven't done that yet + * + * @example window.addEventListener('mousemove', + ev => { + if (isMouseDown) smoothTranslate(ev); + lastMouseEvent = ev; + } + ); + */ +function smoothTranslate(ev, el = content) { + let [translateX, translateY] = el.style.translate == '' ? [el.offsetWidth / -2, el.offsetHeight / -2] : el.style.translate.split(' ').map(v => parseInt(v.split('px')[0])); + let x = ev.type == "wheel" ? ev.deltaX : lastMouseEvent.clientX - ev.clientX; + let y = ev.type == "wheel" ? ev.deltaY : lastMouseEvent.clientY - ev.clientY; + translateX -= isNaN(x) ? 0 : x; + translateY -= isNaN(y) ? 0 : y; -let contentWidth = content.offsetWidth * scale; -let contentHeight = content.offsetHeight * scale; + /** + * Clamp values for [min, max]. Had to do it this way because sometimes the values will flip which is higher and lower. + */ + let clampsX = [(el.offsetWidth - innerWidth + el.offsetWidth * scale) / -2, (el.offsetWidth + innerWidth - content.offsetWidth * scale) / -2].sort((a, b) => a - b); + let clampsY = [(el.offsetHeight - innerHeight + el.offsetHeight * scale) / -2, (el.offsetHeight + innerHeight - content.offsetHeight * scale) / -2].sort((a, b) => a - b); + el.style.translate = `${clamp(translateX, ...clampsX)}px ${clamp(translateY, ...clampsY)}px`; // The juicy part. +}; -let minXTranslation = (content.offsetWidth - innerWidth + contentWidth) / -2; -let minYTranslation = (content.offsetHeight - innerHeight + contentHeight) / -2; -let maxXTranslation = (-content.offsetWidth - innerWidth + contentWidth) / 2; -let maxYTranslation = (-content.offsetHeight - innerHeight + contentHeight) / 2; +/** + * Returns the visibleWidth and the visibleHeight of a scaled element + * @param {number} scale The scale factor given to the element through it's style property + * @param {HTMLElement} el The element to which the sides will be scaled + * + * @returns {{visibleWidth: number, visibleHeight: number}} The visibleWidth and the visibleHeight in an object + */ +function scalePerimeter(scale, el) { + const sideScale = scale; // Simple equation to convert the scale property which affects area to a scale factor for the sides of the element. + return { + visibleWidth: el.offsetWidth * sideScale, + visibleHeight: el.offsetHeight * sideScale + }; +} -let smoothScroll = e => { - let x = e.type == "wheel" ? e.deltaX : lastMousePosition.clientX - e.clientX; - let y = e.type == "wheel" ? e.deltaY : lastMousePosition.clientY - e.clientY; - x = isNaN(x) ? 0 : x; - y = isNaN(y) ? 0 : y; - +let lastMouseEvent = new MouseEvent('mousedown'); +let isMouseDown = false; - contentTranslate.x -= x; - contentTranslate.y -= y; - console.log(contentTranslate); - // contentTranslate.x = clamp(contentTranslate.x, (-content.offsetWidth - innerWidth + content.offsetWidth * scale) / 2, (content.offsetWidth - innerWidth + content.offsetWidth * scale) / -2) - console.log(contentTranslate.x, clamp(contentTranslate.y, minYTranslation, maxYTranslation)); - content.style.translate = `${contentTranslate.x}px ${contentTranslate.y}px` - lastMousePosition = e; -}; +let scale = 1; +let zoomDelta = 0; -window.addEventListener('mousemove', (e = new MouseEvent('mousemove')) => { - if (isMouseDown) smoothScroll(e); - lastMousePosition = e; -}); -window.addEventListener('mousedown', () => isMouseDown = true); -window.addEventListener('mouseup', () => isMouseDown = false); +let { visibleWidth, visibleHeight } = scalePerimeter(scale, content); // Quickly assign visibleWidth and visibleHeight initially -window.addEventListener('wheel', (e = new WheelEvent('wheel')) => { - e.preventDefault(); - - if (!e.ctrlKey) { - smoothScroll(e); - return; - }; +window.addEventListener('mousemove', + /** + * + * @param {MouseEvent} ev mousemove event to pass into smoothTranslate and lastMouseEvent + */ + ev => { + if (isMouseDown) smoothTranslate(ev); + lastMouseEvent = ev; + if (ev.shiftKey) + console.log(ev); + } +); - minXTranslation = (-content.offsetWidth - innerWidth + content.offsetHeight * scale) / 2; - minYTranslation = (-content.offsetHeight - innerHeight + content.offsetHeight * scale) / 2; - maxXTranslation = (content.offsetWidth - innerWidth + content.offsetWidth * scale) / 2; - maxYTranslation = (content.offsetHeight - innerHeight + content.offsetHeight * scale) / 2; - - contentWidth = content.offsetWidth * scale; - contentHeight = content.offsetHeight * scale; +window.addEventListener('mousedown', () => isMouseDown = true); +window.addEventListener('mouseup', () => isMouseDown = false); - zoomDelta += e.deltaY; - scale = clamp(scale - zoomDelta * 0.001, 0.01, 1.2); - - console.log(scale) +window.addEventListener('wheel', + /** + * This is the zoom + * @param {WheelEvent} ev The wheel event to use in the zoom calculation + */ + ev => { + ev.preventDefault(); + + if (!ev.ctrlKey) { + smoothTranslate(ev); + return; + }; + + content.style.transformOrigin = `${content.offsetWidth - ev.offsetX}px ${content.offsetHeight - ev.offsetY}px`; + scale = clamp(scale - ev.deltaY * 0.001, 0.01, 1.2); // 0.001 was a multiplier that I found was easy to use when zooming in and out + content.style.scale = scale; + ({ visibleWidth, visibleHeight } = scalePerimeter(scale, content)); // Reassign visibleWidth and visibleHeight to the rescaled content element - content.style.scale = scale; - scale = scale; -}, { passive: false }); + if (ev.shiftKey) console.log(ev); + let {x, y} = getElementTranslate(); + // content.style.translate = `${x - ev.deltaY}px ${y - ev.deltaY}px`; + }, +{ passive: false }); for (let i = 0; i < 100; i++) { const box = document.createElement('div');