Как сделан zoom в редакторе блок-схем dgrm.net.
Zoom-ить можно:
колесиком мышки,
touchpad-ом
и двумя пальцами на телефонах и планшетах.
Готовая функция zoom-а SVG для ваших проектов прилагается. Для HTML можно переделать.
Алгоритм zoom-а
В SVG и HTML есть масштабирование. Масштабирование в HTML.
Только менять масштаб не достаточно. Изображение будет уезжать.
При увеличении круг в центре экрана съезжает вниз вправо.
Черный прямоугольник это экран. Синий это увеличенное изображение. Увеличенный круг находится в центре увеличенного изображения, но съехал относительно экрана.
Нужно увеличивать и сдвигать, тогда центр изображения не уедет.
На рисунке 4 центр изображения не съезжает. Но карты работают не так. Карты зумятся не в центр. Карты зумятся относительно курсора. Место, куда указывает курсор, не сдвигается относительно экрана.
Здание в центре карты уезжает вниз. Оно выделено красным. Здание под курсором остается на месте. Выделено синим.
Функция зума должна сдвигать изображение так, чтобы точка под курсором оставалась на месте.
/**
* @param {SVGGraphicsElement} svgEl
* @param {Point} fixedPoint this point will not change position while scale
* @param {number} scale
* @param {number} nextScale
*/
export function svgScale(svgEl, fixedPoint, scale, nextScale) {
const position = svgPositionGet(svgEl);
svgPositionSet(svgEl, {
x: nextScale / scale * (position.x - fixedPoint.x) + fixedPoint.x,
y: nextScale / scale * (position.y - fixedPoint.y) + fixedPoint.y
});
ensureTransform(svgEl, SVGTransform.SVG_TRANSFORM_SCALE)
.setScale(nextScale, nextScale);
}
Листинг 1. Функция зума. Сдвигает изображение так, что fixedPoint остается на месте.
Вспомогательные функции svgPositionGet, svgPositionSet, ensureTransform смотрите на GitHub.
Zoom колесиком мышки и touchpad-ом
Подписываемся на событие колесика мышки “wheel”. Для щипка двумя пальцами на touchpad отдельного события нет. Щипок использует это же событие “wheel”.
Для колесика масштаб изменяется с шагом 0.25, а для touchpad 0.05. Значения подобраны так чтобы:
колесико мышки не нужно было долго крутить,
а на touchpad изображение не скакало.
// 'svg' is type of {SVGSVGElement}
let scale = 1;
// mouse wheel, trackpad pitch
svg.addEventListener('wheel', /** @param {WheelEvent} evt */ evt => {
evt.preventDefault();
// calc nextScale
const delta = evt.deltaY || evt.deltaX;
const scaleStep = Math.abs(delta) < 50
? 0.05 // touchpad pitch
: 0.25; // mouse wheel
const scaleDelta = delta < 0 ? scaleStep : -scaleStep;
const nextScale = scale + scaleDelta; // 'scale' is previous scale
// calc fixedPoint
const fixedPoint = { x: evt.clientX, y: evt.clientY };
// scale
// 'svgEl' is element to scale
svgScale(svgEl, fixedPoint, scale, nextScale);
scale = nextScale;
});
Листинг 2. Подписываемся на событие колесика мышки. Щипок touchpad запускает это же событие. Полный код смотрите на GitHub.
Zoom двумя пальцам на телефонах и планшетах
Для zoom-а пальцами фиксированная точка - это точка посередине между пальцами. Изменение масштаба зависит от изменения расстояния между пальцами.
Еще нужно учитывать что изображение можно одновременно зумить и двигать.
// calc nextScale
// distance between fingers
const distanceNew = Math.hypot(
firstFinger.x - secondFinger.x,
firstFinger.y - secondFinger.y);
// 'distance' is previous distance between fingers
// 'scale' is previous scale
const nextScale = scale / distance * distanceNew;
// calc fixedPoint
const fixedPoint = {
x: (firstFinger.x + secondFinger.x) / 2,
y: (firstFinger.y + secondFinger.y) / 2
};
// scale
// 'svgEl' is element to scale
svgScale(svgEl, fixedPoint, scale, nextScale);
// don't forget to also move the canvas behind your fingers
Листинг 3. При zoom-е пальцами фиксированная точка это точка посередине между пальцами. Изменение масштаба зависит от изменения расстояния между пальцами. Полный код смотрите на GitHub.
Другие статьи про dgrm.net
Как поддержать проект
Комментарии (7)
mayorovp
30.10.2022 11:07+2Кстати, держите функцию которая сразу считает масштабирование, перемещение и поворот по двум пальцам:
/** * @param {Point} p1 старая позиция первого пальца * @param {Point} p2 старая позиция второго пальца * @param {Point} q1 новая позиция первого пальца * @param {Point} q2 новая позиция второго пальца */ function getMatrix(p1, p2, q1, q2) { const // межпальцевые вектора px = p1.x - p2.x, py = p1.y - p2.y, qx = q1.x - q2.x, qy = q1.2 - q2.y; const // коэффициенты масштаба и поворота a = (px * qx + py * qy) / (px * px + py * py), b = (qx * py - px * qy) / (px * px + py * py); const // коэффициенты перемещения e = q1.x - a * p1.x - b * p1.y, f = q1.y + b * p1.x - a * p1.y; return new DOMMatrix([a, b, -b, a, e, f]); }
Этот код возвращает матрицу перехода от старого масштаба к новому, чтобы её применить надо взять текущую матрицу преобразования и домножить на вычисленную.
Но чтобы такой способ работал — надо все преобразования делать только матрицами, никаких svgPositionSet, только ensureTransform.
DWM
01.11.2022 18:33qy = q1.2 + q2.y ?
может быть так:
qy = q1.y + q2.y ?
mayorovp
01.11.2022 19:06Разумеется, только всё-таки минус:
const // межпальцевые вектора px = p1.x - p2.x, py = p1.y - p2.y, qx = q1.x - q2.x, qy = q1.y - q2.y;
Если что, код я проверял, но я его проверял на C#, а не на Javascript. Вот при переносе с одного языка на другой опечатка и вылезла...
dom1n1k
31.10.2022 01:57Не очень хорошая идея называть переменную fixedPoint — в контексте информатики сразу лезет посторонний смысл. Тем более, что есть специальный устоявшийся термин — origin.
BooooBka
31.10.2022 10:46Спасибо!
Решал похожую задачу, необходимо было сделать просматривалку планов здания. В итоге уперся в размер svg, они могли быть 50-100мб, возникали тормоза.
Как решение написал сервис на SharpJS, резал картинку на тайлы, для просмотра использовал любые карты - leaflet/mapbox/etc.
mayorovp
Что это вообще за формулы? В них есть хоть какой-то математический смысл, или они подбирались пока результат не стал хоть немного напоминать то что нужно?
Вот правильная формула, дающая наиболее интуитивное управление масштабом:
Alex_BBB Автор
Да, так лучше. Спасибо.