revert: image editor ux

This commit is contained in:
jialin
2025-01-09 17:53:26 +08:00
parent b37ec98e17
commit bd0747ff6f
5 changed files with 83 additions and 119 deletions
+53 -97
View File
@@ -34,9 +34,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
onSave, onSave,
uploadButton uploadButton
}) => { }) => {
const MIN_SCALE = 0.4;
const MAX_SCALE = 10;
const zoomFactor = 1.1;
const intl = useIntl(); const intl = useIntl();
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const overlayCanvasRef = useRef<HTMLCanvasElement>(null); const overlayCanvasRef = useRef<HTMLCanvasElement>(null);
@@ -50,20 +47,25 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
const autoScale = useRef<number>(1); const autoScale = useRef<number>(1);
const cursorRef = useRef<HTMLDivElement>(null); const cursorRef = useRef<HTMLDivElement>(null);
const [imgLoaded, setImgLoaded] = useState(false); const [imgLoaded, setImgLoaded] = useState(false);
const offsetRef = useRef({ x: 0, y: 0 });
const originPosition = useRef({ x: 0, y: 0 }); let scale = 1;
let offsetX = 0;
let offsetY = 0;
const MIN_SCALE = 0.5;
const MAX_SCALE = 5;
const getTransformedPoint = (event: React.MouseEvent<HTMLCanvasElement>) => { const getTransformedPoint = (event: React.MouseEvent<HTMLCanvasElement>) => {
const overlayCanvas = overlayCanvasRef.current!; const overlayCanvas = overlayCanvasRef.current!;
const rect = overlayCanvas.getBoundingClientRect(); const rect = overlayCanvas.getBoundingClientRect();
// 获取鼠标在画布上的原始坐标
const x = event.clientX - rect.left; const x = event.clientX - rect.left;
const y = event.clientY - rect.top; const y = event.clientY - rect.top;
// 考虑缩放比例和偏移量 const transformedX = x - overlayCanvas.width / 2;
const transformedX = (x - offsetRef.current.x) / autoScale.current; const transformedY = y - overlayCanvas.height / 2;
const transformedY = (y - offsetRef.current.y) / autoScale.current;
console.log('Mouse Coordinates (Transformed):', transformedX, transformedY);
return { x: transformedX, y: transformedY }; return { x: transformedX, y: transformedY };
}; };
@@ -111,10 +113,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
overlayCtx!.translate(ctx!.canvas.width / 2, ctx!.canvas.height / 2); overlayCtx!.translate(ctx!.canvas.width / 2, ctx!.canvas.height / 2);
ctx!.translate(ctx!.canvas.width / 2, ctx!.canvas.height / 2); ctx!.translate(ctx!.canvas.width / 2, ctx!.canvas.height / 2);
offscreenCtx!.translate(ctx!.canvas.width / 2, ctx!.canvas.height / 2); offscreenCtx!.translate(ctx!.canvas.width / 2, ctx!.canvas.height / 2);
originPosition.current = {
x: ctx!.canvas.width / 2,
y: ctx!.canvas.height / 2
};
}, [canvasRef.current, overlayCanvasRef.current]); }, [canvasRef.current, overlayCanvasRef.current]);
const scaleCanvasSize = useCallback(() => { const scaleCanvasSize = useCallback(() => {
@@ -441,8 +439,8 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
1 1
); );
canvas!.width = img.width; canvas!.width = img.width * scale;
canvas!.height = img.height; canvas!.height = img.height * scale;
autoScale.current = scale / autoScale.current; autoScale.current = scale / autoScale.current;
@@ -459,7 +457,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
canvas!.width, canvas!.width,
canvas!.height canvas!.height
); );
canvas!.style.transform = `scale(${scale})`;
resolve(); resolve();
}; };
}); });
@@ -505,102 +502,61 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = ({
const y = event.clientY - rect.top; const y = event.clientY - rect.top;
// 考虑缩放比例和偏移量 // 考虑缩放比例和偏移量
const transformedX = (x - offsetRef.current.x) / autoScale.current; const transformedX = (x - offsetX) / scale;
const transformedY = (y - offsetRef.current.y) / autoScale.current; const transformedY = (y - offsetY) / scale;
return { x: transformedX, y: transformedY }; return { x: transformedX, y: transformedY };
}; };
const getMousePosition = (event: any) => {
const canvas = overlayCanvasRef.current!;
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
const { x: originX, y: originY } = originPosition.current;
const canvasX = (mouseX - originX) / autoScale.current;
const canvasY = (mouseY - originY) / autoScale.current;
return { x: canvasX, y: canvasY };
};
const updateCavasTransform = (event: any) => {
const canvas = overlayCanvasRef.current!;
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
// 鼠标在中心坐标系中的位置
const { x: originX, y: originY } = originPosition.current;
const canvasX = (mouseX - originX) / autoScale.current;
const canvasY = (mouseY - originY) / autoScale.current;
// 更新缩放比例
// max scale: 2, min scale: 0.3
const zoomIn = event.deltaY < 0;
let newScale = autoScale.current;
if (zoomIn) {
newScale = Math.min(MAX_SCALE, autoScale.current * zoomFactor);
} else {
newScale = Math.max(MIN_SCALE, autoScale.current / zoomFactor);
}
console.log('New Scale:', newScale);
autoScale.current = newScale;
// 更新原点
const neworiginX = mouseX - canvasX * newScale;
const neworiginY = mouseY - canvasY * newScale;
originPosition.current = { x: neworiginX, y: neworiginY };
const overlayCtx = overlayCanvasRef.current!.getContext('2d')!;
const canvasCtx = canvasRef.current!.getContext('2d')!;
// 更新 Canvas 的变换
overlayCtx.setTransform(
autoScale.current,
0,
0,
autoScale.current,
neworiginX,
neworiginY
);
canvasCtx.setTransform(
autoScale.current,
0,
0,
autoScale.current,
neworiginX,
neworiginY
);
};
const handleOnWheel = (event: WheelEvent) => { const handleOnWheel = (event: WheelEvent) => {
event.preventDefault(); event.preventDefault();
updateCavasTransform(event); const zoomFactor = event.deltaY < 0 ? 1.1 : 0.9;
const newScale = Math.min(
MAX_SCALE,
Math.max(MIN_SCALE, scale * zoomFactor)
);
overlayCanvasRef.current!.style.transform = `scale(${autoScale.current})`; const rect = canvasRef.current!.getBoundingClientRect();
canvasRef.current!.style.transform = `scale(${autoScale.current})`; const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
// 计算新的偏移量
offsetX = mouseX - (mouseX - offsetX) * (newScale / scale);
offsetY = mouseY - (mouseY - offsetY) * (newScale / scale);
// 更新缩放比例
scale = newScale;
// 设置画布的变换
const overlayCtx = overlayCanvasRef.current!.getContext('2d')!;
overlayCtx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
const canvasCtx = canvasRef.current!.getContext('2d')!;
canvasCtx.setTransform(scale, 0, 0, scale, offsetX, offsetY);
overlayCanvasRef.current!.style.transform = `scale(${scale})`;
canvasRef.current!.style.transform = `scale(${scale})`;
console.log('Zoom:', scale, offsetX, offsetY);
}; };
useEffect(() => { useEffect(() => {
initializeImage(); initializeImage();
}, [initializeImage]); }, [initializeImage]);
useEffect(() => { // useEffect(() => {
const container = containerRef.current; // const container = containerRef.current;
if (!container) return; // if (!container) return;
if (container) { // if (container) {
resizeObserver.current = new ResizeObserver( // resizeObserver.current = new ResizeObserver(
_.throttle(handleResize, 100) // _.throttle(handleResize, 100)
); // );
resizeObserver.current.observe(container); // resizeObserver.current.observe(container);
} // }
return () => { // return () => {
resizeObserver.current?.disconnect(); // resizeObserver.current?.disconnect();
}; // };
}, [handleResize, containerRef.current]); // }, [handleResize, containerRef.current]);
useEffect(() => { useEffect(() => {
createOffscreenCanvas(); createOffscreenCanvas();
-1
View File
@@ -184,7 +184,6 @@ body {
font-family: 'noto sans', sans-serif; font-family: 'noto sans', sans-serif;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
overflow-y: initial !important;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 0; width: 0;
+1
View File
@@ -42,6 +42,7 @@ export default function useOverlayScroller(options?: any) {
clickScroll: 'instant' clickScroll: 'instant'
} }
}, },
defer: true defer: true
}); });
@@ -4,29 +4,36 @@ import 'simplebar-react/dist/simplebar.min.css';
import '../style/column-wrapper.less'; import '../style/column-wrapper.less';
const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => { const ColumnWrapper: React.FC<any> = ({ children, footer, height }) => {
if (footer) { const handleOnWheel = (e: React.WheelEvent) => {
return ( console.log('handleOnWheel', e);
<div className="column-wrapper-footer"> e.preventDefault();
e.stopPropagation();
};
return (
<>
{footer ? (
<div className="column-wrapper-footer">
<div className="column-wrapper">
<SimpleBar
style={{
maxHeight: height || 'calc(100vh - 89px)',
paddingBottom: '50px'
}}
>
{children}
</SimpleBar>
</div>
{<div className="footer">{footer}</div>}
</div>
) : (
<div className="column-wrapper"> <div className="column-wrapper">
<SimpleBar <SimpleBar style={{ maxHeight: height || 'calc(100vh - 89px)' }}>
style={{
maxHeight: height || 'calc(100vh - 89px)',
paddingBottom: '50px'
}}
>
{children} {children}
</SimpleBar> </SimpleBar>
</div> </div>
{<div className="footer">{footer}</div>} )}
</div> </>
);
}
return (
<div className="column-wrapper">
<SimpleBar style={{ maxHeight: height || 'calc(100vh - 89px)' }}>
{children}
</SimpleBar>
</div>
); );
}; };
@@ -189,8 +189,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
value: item.quantization value: item.quantization
}; };
}); });
setQuantizationOptions(quantizationList); const result = _.uniqBy(quantizationList, 'value');
return quantizationList; setQuantizationOptions(result);
return result;
}; };
const handleSetBackendOptions = () => { const handleSetBackendOptions = () => {