@@ -16,9 +16,14 @@ tmpl.innerHTML = `
1616 </div>
1717`
1818
19+ const startPositions : WeakMap < ImageCropElement , { startX : number , startY : number } > = new WeakMap ( )
20+ const dragStartPositions : WeakMap < ImageCropElement , { dragStartX : number , dragStartY : number } > = new WeakMap ( )
21+ const constructedElements : WeakMap < ImageCropElement , { image : HTMLImageElement , box : HTMLElement } > = new WeakMap ( )
22+
1923function moveCropArea ( event : MouseEvent | KeyboardEvent ) {
2024 const el = event . currentTarget
2125 if ( ! ( el instanceof ImageCropElement ) ) return
26+ const { box, image} = constructedElements . get ( el ) || { }
2227
2328 let deltaX = 0
2429 let deltaY = 0
@@ -32,23 +37,27 @@ function moveCropArea(event: MouseEvent | KeyboardEvent) {
3237 } else if ( event . key === 'ArrowRight' ) {
3338 deltaX = 1
3439 }
35- } else if ( el . dragStartX && el . dragStartY && event instanceof MouseEvent ) {
36- deltaX = event . pageX - el . dragStartX
37- deltaY = event . pageY - el . dragStartY
40+ } else if ( dragStartPositions . has ( el ) && event instanceof MouseEvent ) {
41+ const pos = dragStartPositions . get ( el )
42+ if ( ! pos ) return
43+ deltaX = event . pageX - pos . dragStartX
44+ deltaY = event . pageY - pos . dragStartY
3845 }
3946
4047 if ( deltaX !== 0 || deltaY !== 0 ) {
41- const x = Math . min ( Math . max ( 0 , el . box . offsetLeft + deltaX ) , el . image . width - el . box . offsetWidth )
42- const y = Math . min ( Math . max ( 0 , el . box . offsetTop + deltaY ) , el . image . height - el . box . offsetHeight )
43- el . box . style . left = `${ x } px`
44- el . box . style . top = `${ y } px`
48+ const x = Math . min ( Math . max ( 0 , box . offsetLeft + deltaX ) , image . width - box . offsetWidth )
49+ const y = Math . min ( Math . max ( 0 , box . offsetTop + deltaY ) , image . height - box . offsetHeight )
50+ box . style . left = `${ x } px`
51+ box . style . top = `${ y } px`
4552
46- fireChangeEvent ( el , { x, y, width : el . box . offsetWidth , height : el . box . offsetHeight } )
53+ fireChangeEvent ( el , { x, y, width : box . offsetWidth , height : box . offsetHeight } )
4754 }
4855
4956 if ( event instanceof MouseEvent ) {
50- el . dragStartX = event . pageX
51- el . dragStartY = event . pageY
57+ dragStartPositions . set ( el , {
58+ dragStartX : event . pageX ,
59+ dragStartY : event . pageY
60+ } )
5261 }
5362}
5463
@@ -58,6 +67,7 @@ function updateCropArea(event: MouseEvent | KeyboardEvent) {
5867
5968 const el = target . closest ( 'image-crop' )
6069 if ( ! ( el instanceof ImageCropElement ) ) return
70+ const { box} = constructedElements . get ( el ) || { }
6171
6272 const rect = el . getBoundingClientRect ( )
6373 let deltaX , deltaY , delta
@@ -66,13 +76,14 @@ function updateCropArea(event: MouseEvent | KeyboardEvent) {
6676 if ( event . key === '-' ) delta = - 10
6777 if ( event . key === '=' ) delta = + 10
6878 if ( ! delta ) return
69- deltaX = el . box . offsetWidth + delta
70- deltaY = el . box . offsetHeight + delta
71- el . startX = el . box . offsetLeft
72- el . startY = el . box . offsetTop
79+ deltaX = box . offsetWidth + delta
80+ deltaY = box . offsetHeight + delta
81+ startPositions . set ( el , { startX : box . offsetLeft , startY : box . offsetTop } )
7382 } else if ( event instanceof MouseEvent ) {
74- deltaX = event . pageX - el . startX - rect . left - window . pageXOffset
75- deltaY = event . pageY - el . startY - rect . top - window . pageYOffset
83+ const pos = startPositions . get ( el )
84+ if ( ! pos ) return
85+ deltaX = event . pageX - pos . startX - rect . left - window . pageXOffset
86+ deltaY = event . pageY - pos . startY - rect . top - window . pageYOffset
7687 }
7788
7889 if ( deltaX && deltaY ) updateDimensions ( el , deltaX , deltaY , ! ( event instanceof KeyboardEvent ) )
@@ -84,6 +95,7 @@ function startUpdate(event: MouseEvent) {
8495
8596 const el = currentTarget . closest ( 'image-crop' )
8697 if ( ! ( el instanceof ImageCropElement ) ) return
98+ const { box} = constructedElements . get ( el ) || { }
8799
88100 const target = event . target
89101 if ( ! ( target instanceof HTMLElement ) ) return
@@ -94,8 +106,10 @@ function startUpdate(event: MouseEvent) {
94106 el . addEventListener ( 'mousemove' , updateCropArea )
95107 if ( [ 'nw' , 'se' ] . indexOf ( direction ) >= 0 ) el . classList . add ( 'nwse' )
96108 if ( [ 'ne' , 'sw' ] . indexOf ( direction ) >= 0 ) el . classList . add ( 'nesw' )
97- el . startX = el . box . offsetLeft + ( [ 'se' , 'ne' ] . indexOf ( direction ) >= 0 ? 0 : el . box . offsetWidth )
98- el . startY = el . box . offsetTop + ( [ 'se' , 'sw' ] . indexOf ( direction ) >= 0 ? 0 : el . box . offsetHeight )
109+ startPositions . set ( el , {
110+ startX : box . offsetLeft + ( [ 'se' , 'ne' ] . indexOf ( direction ) >= 0 ? 0 : box . offsetWidth ) ,
111+ startY : box . offsetTop + ( [ 'se' , 'sw' ] . indexOf ( direction ) >= 0 ? 0 : box . offsetHeight )
112+ } )
99113 updateCropArea ( event )
100114 } else {
101115 // Move crop area
@@ -104,25 +118,24 @@ function startUpdate(event: MouseEvent) {
104118}
105119
106120function updateDimensions ( target , deltaX , deltaY , reposition = true ) {
107- let newSide = Math . max ( Math . abs ( deltaX ) , Math . abs ( deltaY ) , target . minWidth )
121+ let newSide = Math . max ( Math . abs ( deltaX ) , Math . abs ( deltaY ) , 10 )
122+ const pos = startPositions . get ( target )
123+ if ( ! pos ) return
124+ const { box, image} = constructedElements . get ( target ) || { }
108125 newSide = Math . min (
109126 newSide ,
110- deltaY > 0 ? target . image . height - target . startY : target . startY ,
111- deltaX > 0 ? target . image . width - target . startX : target . startX
127+ deltaY > 0 ? image . height - pos . startY : pos . startY ,
128+ deltaX > 0 ? image . width - pos . startX : pos . startX
112129 )
113130
114- const x = reposition
115- ? Math . round ( Math . max ( 0 , deltaX > 0 ? target . startX : target . startX - newSide ) )
116- : target . box . offsetLeft
117- const y = reposition
118- ? Math . round ( Math . max ( 0 , deltaY > 0 ? target . startY : target . startY - newSide ) )
119- : target . box . offsetTop
131+ const x = reposition ? Math . round ( Math . max ( 0 , deltaX > 0 ? pos . startX : pos . startX - newSide ) ) : box . offsetLeft
132+ const y = reposition ? Math . round ( Math . max ( 0 , deltaY > 0 ? pos . startY : pos . startY - newSide ) ) : box . offsetTop
120133
121- target . box . style . left = `${ x } px`
122- target . box . style . top = `${ y } px`
134+ box . style . left = `${ x } px`
135+ box . style . top = `${ y } px`
123136
124- target . box . style . width = `${ newSide } px`
125- target . box . style . height = `${ newSide } px`
137+ box . style . width = `${ newSide } px`
138+ box . style . height = `${ newSide } px`
126139 fireChangeEvent ( target , { x, y, width : newSide , height : newSide } )
127140}
128141
@@ -138,25 +151,28 @@ function imageReady(event: Event) {
138151}
139152
140153function setInitialPosition ( el ) {
141- const image = el . image
154+ const { image} = constructedElements . get ( el ) || { }
142155 const side = Math . round ( image . clientWidth > image . clientHeight ? image . clientHeight : image . clientWidth )
143- el . startX = ( image . clientWidth - side ) / 2
144- el . startY = ( image . clientHeight - side ) / 2
156+ startPositions . set ( el , {
157+ startX : ( image . clientWidth - side ) / 2 ,
158+ startY : ( image . clientHeight - side ) / 2
159+ } )
145160 updateDimensions ( el , side , side )
146161}
147162
148163function stopUpdate ( event : MouseEvent ) {
149164 const el = event . currentTarget
150165 if ( ! ( el instanceof ImageCropElement ) ) return
151166
152- el . dragStartX = el . dragStartY = null
167+ dragStartPositions . delete ( el )
153168 el . classList . remove ( 'nwse' , 'nesw' )
154169 el . removeEventListener ( 'mousemove' , updateCropArea )
155170 el . removeEventListener ( 'mousemove' , moveCropArea )
156171}
157172
158173function fireChangeEvent ( target : ImageCropElement , result : { x : number , y : number , width : number , height : number } ) {
159- const ratio = target . image . naturalWidth / target . image . width
174+ const { image} = constructedElements . get ( target ) || { }
175+ const ratio = image . naturalWidth / image . width
160176 for ( const key in result ) {
161177 const value = Math . round ( result [ key ] * ratio )
162178 result [ key ] = value
@@ -167,42 +183,24 @@ function fireChangeEvent(target: ImageCropElement, result: {x: number, y: number
167183 target . dispatchEvent ( new CustomEvent ( 'image-crop-change' , { bubbles : true , detail : result } ) )
168184}
169185
170- export class ImageCropElement extends HTMLElement {
171- image : HTMLImageElement
172- box : HTMLElement
173- constructed : boolean
174- minWidth : number
175- dragStartX : ?number
176- dragStartY : ?number
177- startX : number
178- startY : number
179-
180- constructor ( ) {
181- super ( )
182- this . minWidth = 10
183- }
184-
186+ class ImageCropElement extends HTMLElement {
185187 connectedCallback ( ) {
186- if ( this . constructed ) return
187- this . constructed = true
188-
188+ if ( constructedElements . has ( this ) ) return
189189 this . appendChild ( document . importNode ( tmpl . content , true ) )
190- const image = this . querySelector ( 'img' )
191- if ( ! ( image instanceof HTMLImageElement ) ) return
192- this . image = image
193-
194190 const box = this . querySelector ( '[data-crop-box]' )
195191 if ( ! ( box instanceof HTMLElement ) ) return
196- this . box = box
192+ const image = this . querySelector ( 'img' )
193+ if ( ! ( image instanceof HTMLImageElement ) ) return
194+ constructedElements . set ( this , { box, image} )
197195
198- this . image . addEventListener ( 'load' , imageReady )
196+ image . addEventListener ( 'load' , imageReady )
199197 this . addEventListener ( 'mouseleave' , stopUpdate )
200198 this . addEventListener ( 'mouseup' , stopUpdate )
201- this . box . addEventListener ( 'mousedown' , startUpdate )
199+ box . addEventListener ( 'mousedown' , startUpdate )
202200 this . addEventListener ( 'keydown' , moveCropArea )
203201 this . addEventListener ( 'keydown' , updateCropArea )
204202
205- if ( this . src ) this . image . src = this . src
203+ if ( this . src ) image . src = this . src
206204 }
207205
208206 static get observedAttributes ( ) {
@@ -234,9 +232,10 @@ export class ImageCropElement extends HTMLElement {
234232 }
235233
236234 attributeChangedCallback ( attribute : string , oldValue : string , newValue : string ) {
235+ const { image} = constructedElements . get ( this ) || { }
237236 if ( attribute === 'src' ) {
238237 this . loaded = false
239- if ( this . image ) this . image . src = newValue
238+ if ( image ) image . src = newValue
240239 }
241240 }
242241}
0 commit comments