11import { positiveSubtract , positiveAdd } from '@tdesign/common-js/input-number/number' ;
22import { ref , watch } from 'vue' ;
33import { ImageScale } from '../type' ;
4- import { throttle } from 'lodash-es' ;
54
65interface InitTransform {
76 translateX : number ;
87 translateY : number ;
98}
109
11- export function useDrag ( initTransform : InitTransform ) {
10+ interface DragOptions {
11+ maxTranslateX ?: number ;
12+ maxTranslateY ?: number ;
13+ }
14+
15+ export function useDrag (
16+ initTransform : InitTransform ,
17+ onDragStart ?: ( ) => void ,
18+ onDragEnd ?: ( distance : number ) => void ,
19+ options ?: DragOptions ,
20+ ) {
1221 const transform = ref ( initTransform ) ;
1322
1423 const mouseDownHandler = ( e : MouseEvent ) => {
@@ -17,18 +26,41 @@ export function useDrag(initTransform: InitTransform) {
1726
1827 const { pageX : startX , pageY : startY } = e ;
1928 const { translateX, translateY } = transform . value ;
29+ let totalDistance = 0 ;
30+
31+ onDragStart ?.( ) ;
32+
2033 const mouseMoveHandler = ( e : MouseEvent ) => {
2134 const { pageX, pageY } = e ;
35+ const deltaX = pageX - startX ;
36+ const deltaY = pageY - startY ;
37+
38+ // 计算移动距离
39+ const distance = Math . sqrt ( deltaX * deltaX + deltaY * deltaY ) ;
40+ totalDistance = distance ;
41+
42+ let newTranslateX = translateX + deltaX ;
43+ let newTranslateY = translateY + deltaY ;
44+
45+ // 应用边界限制
46+ if ( options ?. maxTranslateX !== undefined ) {
47+ newTranslateX = Math . max ( - options . maxTranslateX , Math . min ( options . maxTranslateX , newTranslateX ) ) ;
48+ }
49+ if ( options ?. maxTranslateY !== undefined ) {
50+ newTranslateY = Math . max ( - options . maxTranslateY , Math . min ( options . maxTranslateY , newTranslateY ) ) ;
51+ }
52+
2253 transform . value = {
23- translateX : translateX + pageX - startX ,
24- translateY : translateY + pageY - startY ,
54+ translateX : newTranslateX ,
55+ translateY : newTranslateY ,
2556 } ;
2657 } ;
2758
2859 const removeHandler = ( ) => {
2960 document . removeEventListener ( 'mousemove' , mouseMoveHandler ) ;
3061 document . removeEventListener ( 'mouseup' , mouseUpHandler ) ;
3162 document . removeEventListener ( 'mouseleave' , mouseLeaveHandler ) ;
63+ onDragEnd ?.( totalDistance ) ;
3264 } ;
3365
3466 const mouseUpHandler = ( ) => removeHandler ( ) ;
@@ -58,34 +90,79 @@ export function useMirror() {
5890 return { mirror, onMirror, resetMirror } ;
5991}
6092
93+ export interface ZoomOptions {
94+ /** 鼠标相对于容器中心的 X 偏移 */
95+ mouseOffsetX ?: number ;
96+ /** 鼠标相对于容器中心的 Y 偏移 */
97+ mouseOffsetY ?: number ;
98+ /** 当前的位移值 */
99+ currentTranslate ?: { translateX : number ; translateY : number } ;
100+ }
101+
102+ export interface ZoomResult {
103+ /** 新的位移值(用于替换,而不是累加) */
104+ newTranslate ?: { translateX : number ; translateY : number } ;
105+ }
106+
61107export function useScale ( imageScale : ImageScale ) {
62108 const params = { max : 2 , min : 0.5 , step : 0.2 , defaultScale : 1 , ...imageScale } ;
63109 const { max, min, step, defaultScale } = params ;
64110 const scale = ref ( defaultScale ) ;
65111
66- const onZoomIn = throttle ( ( ) => {
67- const result = positiveAdd ( scale . value , step ) ;
68- setScale ( result ) ;
69- } , 50 ) ;
112+ /**
113+ * 计算缩放后的位移补偿,保持鼠标指向的图片内容在屏幕上的位置不变
114+ *
115+ * 公式推导:设 Z = 缩放中心(鼠标位置),T = 当前位移,λ'/λ = scaleRatio
116+ * newTranslate = scaleRatio * T + (1 - scaleRatio) * Z
117+ */
118+ const calculateTranslateOffset = (
119+ oldScale : number ,
120+ newScale : number ,
121+ options ?: ZoomOptions ,
122+ ) : { translateX : number ; translateY : number } | undefined => {
123+ // 缺少鼠标位置信息时,不计算位移补偿
124+ if ( options ?. mouseOffsetX == null || options ?. mouseOffsetY == null ) {
125+ return undefined ;
126+ }
127+
128+ const scaleRatio = newScale / oldScale ;
129+ const { translateX = 0 , translateY = 0 } = options ?. currentTranslate ?? { } ;
130+ const { mouseOffsetX, mouseOffsetY } = options ;
131+
132+ return {
133+ translateX : scaleRatio * translateX + ( 1 - scaleRatio ) * mouseOffsetX ,
134+ translateY : scaleRatio * translateY + ( 1 - scaleRatio ) * mouseOffsetY ,
135+ } ;
136+ } ;
70137
71- const onZoomOut = throttle ( ( ) => {
72- const result = positiveSubtract ( scale . value , step ) ;
73- setScale ( result ) ;
74- } , 50 ) ;
138+ const onZoomIn = ( options ?: ZoomOptions ) : ZoomResult => {
139+ const oldScale = scale . value ;
140+ const result = positiveAdd ( oldScale , step ) ;
141+ const newScale = Math . min ( result , max ) ;
142+ setScale ( newScale ) ;
143+
144+ return {
145+ newTranslate : calculateTranslateOffset ( oldScale , newScale , options ) ,
146+ } ;
147+ } ;
148+
149+ const onZoomOut = ( options ?: ZoomOptions ) : ZoomResult => {
150+ const oldScale = scale . value ;
151+ const result = positiveSubtract ( oldScale , step ) ;
152+ const newScale = Math . max ( result , min ) ;
153+ setScale ( newScale ) ;
154+
155+ return {
156+ newTranslate : calculateTranslateOffset ( oldScale , newScale , options ) ,
157+ } ;
158+ } ;
75159
76160 const resetScale = ( ) => {
77161 scale . value = defaultScale ;
78162 } ;
79163
80164 const setScale = ( newScale : number ) => {
81- let value = newScale ;
82- if ( newScale < min ) {
83- value = min ;
84- }
85- if ( newScale > max ) {
86- value = max ;
87- }
88- scale . value = value ;
165+ scale . value = Math . max ( min , Math . min ( max , newScale ) ) ;
89166 } ;
90167
91168 watch (
0 commit comments