Сделать постоянную скорость перемещения содержимого div при его масштабирование с помощью transform: translate() scale();
Прочитал статью https://habr.com/ru/articles/722964/
Сделать рабочую область получилось, добавил возможность двигать <div class="node">
, но при изменении зума, скорость (изменение положения div
) перемещения уменьшается. Я хочу чтобы скорость оставалась постоянной.
Как это сделать? Помогите, пожалуйста???
Что сделал я, компонент App
и MovingComponent
, он вместо <div class="node">
:
import { useEffect, useRef, useState } from 'react';
import { TextInput, Button } from './components';
import "./App.css"
export interface IBlock {
x: number;
y: number;
}
interface IMovingComponentProps {
block: IBlock;
onMove: (newX: number, newY: number) => void;
children?: React.ReactNode;
}
const MovingComponent = (props: IMovingComponentProps) => {
const [isDragging, setIsDragging] = useState(false);
let offsetX = 0;
let offsetY = 0;
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
e.preventDefault();
e.stopPropagation();
// как я понял, нужно учитывать scale родительского div
// но я не понимаю где и как это нужно делать, здесь и в методе handleMouseMove?
offsetX = e.clientX - props.block.x;
offsetY = e.clientY - props.block.y;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseMove = (e: MouseEvent) => {
e.stopPropagation();
props.onMove(e.clientX - offsetX, e.clientY - offsetY)
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
setIsDragging(false);
};
return (
<div
className={`interactive-block ${isDragging ? 'selected' : ''} `}
style={{
top: props.block.y,
left: props.block.x,
border: 'solid',
zIndex: 1
}}
onMouseDown={handleMouseDown}
>
{props.children}
</div>
);
};
function App() {
const [block, setBlock] = useState<IBlock>({ x: 100, y: 100 });
const handleMove = (newX: number, newY: number) => {
setBlock({ x: newX, y: newY });
}
const layerRef = useRef<HTMLDivElement>(null);
const [viewport, setViewport] = useState({
offset: {
x: 0.0,
y: 0.0
},
zoom: 1
});
useEffect(() => {
if (!layerRef.current) {
return;
}
layerRef.current.onwheel = (e: WheelEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.ctrlKey) {
const speedFactor =
(e.deltaMode === 1 ? 0.05 : e.deltaMode ? 1 : 0.002) * 10;
setViewport((prev) => {
const pinchDelta = -e.deltaY * speedFactor;
return {
...prev,
zoom: Math.min(
1.3,
Math.max(0.1, prev.zoom * Math.pow(2, pinchDelta))
)
};
});
}
};
}, [setViewport]);
return (
<div className="app"
ref={layerRef}>
<div className="container">
<div
className="nodes-container"
style={{
transform: `
translate(
${viewport.offset.x * viewport.zoom}px,
${viewport.offset.y * viewport.zoom}px
)
scale(${viewport.zoom})
`
}}
>
<MovingComponent block={block} onMove={handleMove}>Что-то</MovingComponent>
</div>
</div>
</div>
);
}
export default App;
файл App.css
.interactive-block {
position: absolute;
border: solid black 2px;
padding: 10px;
background-color: #fff;
}
html,
body {
width: 100%;
height: 100%;
}
.app {
overflow: hidden;
width: 100%;
height: 100%;
position: relative;
}
.container,
.nodes-container {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
}
.container {
overflow: hidden;
}
Инструкция как запустить код:
- создайте стандартное приложение
react
вvs.code
- скопируйте код с компонентами
App
иMovingComponent
в файлApp.tsx
- создай файл
App.css
в той же папке , где лежитApp.tsx
и скопируйте кодcss
туда - в файле index.html дополните
<div id="root" style="width: 100%; height: 100%;"></div>
- запустите приложение
react
Ответы (1 шт):
Автор решения: Кирилл Серебренников
→ Ссылка
Вот исправленный код компонентов, файл CSS менять не надо. Всего-то нужно было понять, где и как учитывать новый масштаб.
Вроде бы работает:
import { useEffect, useRef, useState } from 'react';
import "./App.css"
export interface IBlock {
x: number;
y: number;
}
interface IMovingComponentProps {
block: IBlock;
i: number;
port: {
offset: {
x: number,
y: number
},
zoom: number
}
onMove: (newX: number, newY: number, i: number) => void;
children?: React.ReactNode;
}
const MovingComponent = (props: IMovingComponentProps) => {
const [isDragging, setIsDragging] = useState(false);
let offsetX = 0;
let offsetY = 0;
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
e.preventDefault();
e.stopPropagation();
// нужно учитывать масштаб здесь
offsetX = e.clientX / props.port.zoom - props.block.x;
offsetY = e.clientY / props.port.zoom - props.block.y;
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
const handleMouseMove = (e: MouseEvent) => {
e.stopPropagation();
// и нужно учитывать масштаб здесь
props.onMove((e.clientX / props.port.zoom - offsetX), (e.clientY / props.port.zoom - offsetY), props.i);
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
setIsDragging(false);
};
return (
<div
className={`interactive-block ${isDragging ? 'selected' : ''} `}
style={{
top: props.block.y,
left: props.block.x,
border: 'solid',
zIndex: 1,
}}
onMouseDown={handleMouseDown}
>
{props.children}
</div>
);
};
function App() {
const [block, setBlock] = useState<IBlock[]>([{ x: 100, y: 100 }, { x: 300, y: 100 }]);
const handleMove = (newX: number, newY: number, index: number) => {
let newBlocks = block.filter((b, i) => i != index)
let newBlock = block[index];
newBlock.x = newX;
newBlock.y = newY;
newBlocks.push(newBlock);
setBlock(newBlocks);
//setBlock({ x: newX, y: newY });
}
const layerRef = useRef<HTMLDivElement>(null);
const [viewport, setViewport] = useState({
offset: {
x: 0.0,
y: 0.0
},
zoom: 1
});
useEffect(() => {
if (!layerRef.current) {
return;
}
layerRef.current.onwheel = (e: WheelEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.ctrlKey) {
const speedFactor = 0.002;
setViewport((prev) => {
const pinchDelta = -e.deltaY * speedFactor;
return {
...prev,
zoom: Math.min(
1.3,
Math.max(0.1, prev.zoom * Math.pow(2, pinchDelta))
)
};
});
}
};
}, [setViewport]);
return (
<div className="app"
ref={layerRef}>
<div className="container">
<div
className="nodes-container"
style={{
transform: `
translate(
${viewport.offset.x * viewport.zoom}px,
${viewport.offset.y * viewport.zoom}px
)
scale(${viewport.zoom})
`
}}
>
{block.map((b, i) =>
<MovingComponent
key={i}
block={b}
onMove={handleMove}
port={viewport}
i={i}
>
Что-то {i}
</MovingComponent>
)}
</div>
</div>
</div>
);
}
export default App;