Skip to content

Commit cae16b0

Browse files
crisbetojosephperrott
authored andcommitted
fix(drag-drop): preserve previous inline transform (#13529)
1 parent 280b0d6 commit cae16b0

File tree

2 files changed

+54
-19
lines changed

2 files changed

+54
-19
lines changed

src/cdk/drag-drop/drag.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,30 @@ describe('CdkDrag', () => {
144144
expect(dragElement.style.transform).toBeFalsy();
145145
}));
146146

147+
it('should preserve the previous `transform` value', fakeAsync(() => {
148+
const fixture = createComponent(StandaloneDraggable);
149+
fixture.detectChanges();
150+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
151+
152+
dragElement.style.transform = 'translateX(-50%)';
153+
dragElementViaMouse(fixture, dragElement, 50, 100);
154+
expect(dragElement.style.transform).toBe('translateX(-50%) translate3d(50px, 100px, 0px)');
155+
}));
156+
157+
it('should not generate multiple own `translate3d` values', fakeAsync(() => {
158+
const fixture = createComponent(StandaloneDraggable);
159+
fixture.detectChanges();
160+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
161+
162+
dragElement.style.transform = 'translateY(-50%)';
163+
164+
dragElementViaMouse(fixture, dragElement, 50, 100);
165+
expect(dragElement.style.transform).toBe('translateY(-50%) translate3d(50px, 100px, 0px)');
166+
167+
dragElementViaMouse(fixture, dragElement, 100, 200);
168+
expect(dragElement.style.transform).toBe('translateY(-50%) translate3d(150px, 300px, 0px)');
169+
}));
170+
147171
});
148172

149173
describe('touch dragging', () => {

src/cdk/drag-drop/drag.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
129129
/** CSS `transform` that is applied to the element while it's being dragged. */
130130
private _activeTransform: Point = {x: 0, y: 0};
131131

132+
/** Inline `transform` value that the element had before the first dragging sequence. */
133+
private _initialTransform?: string;
134+
132135
/**
133136
* Whether the dragging sequence has been started. Doesn't
134137
* necessarily mean that the element has been moved.
@@ -325,6 +328,12 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
325328
return;
326329
}
327330

331+
// Cache the previous transform amount only after the first drag sequence, because
332+
// we don't want our own transforms to stack on top of each other.
333+
if (this._initialTransform == null) {
334+
this._initialTransform = this._rootElement.style.transform || '';
335+
}
336+
328337
this._hasStartedDragging = this._hasMoved = false;
329338
this._initialContainer = this.dropContainer;
330339
this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
@@ -373,13 +382,12 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
373382
if (!this._hasStartedDragging) {
374383
const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
375384
const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
376-
const minimumDistance = this._config.dragStartThreshold;
377385

378386
// Only start dragging after the user has moved more than the minimum distance in either
379387
// direction. Note that this is preferrable over doing something like `skip(minimumDistance)`
380388
// in the `pointerMove` subscription, because we're not guaranteed to have one move event
381389
// per pixel of movement (e.g. if the user moves their pointer quickly).
382-
if (distanceX + distanceY >= minimumDistance) {
390+
if (distanceX + distanceY >= this._config.dragStartThreshold) {
383391
this._hasStartedDragging = true;
384392
this._ngZone.run(() => this._startDragSequence());
385393
}
@@ -399,7 +407,11 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
399407
pointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x;
400408
activeTransform.y =
401409
pointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y;
402-
this._setTransform(this._rootElement, activeTransform.x, activeTransform.y);
410+
const transform = getTransform(activeTransform.x, activeTransform.y);
411+
412+
// Preserve the previous `transform` value, if there was one.
413+
this._rootElement.style.transform = this._initialTransform ?
414+
this._initialTransform + ' ' + transform : transform;
403415
}
404416

405417
// Since this event gets fired for every pixel while dragging, we only
@@ -507,9 +519,8 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
507519
}
508520

509521
this.dropContainer._sortItem(this, x, y, this._pointerDirectionDelta);
510-
this._setTransform(this._preview,
511-
x - this._pickupPositionInElement.x,
512-
y - this._pickupPositionInElement.y);
522+
this._preview.style.transform =
523+
getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
513524
}
514525

515526
/**
@@ -525,15 +536,16 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
525536

526537
preview = viewRef.rootNodes[0];
527538
this._previewRef = viewRef;
528-
this._setTransform(preview, this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
539+
preview.style.transform =
540+
getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
529541
} else {
530542
const element = this._rootElement;
531543
const elementRect = element.getBoundingClientRect();
532544

533545
preview = element.cloneNode(true) as HTMLElement;
534546
preview.style.width = `${elementRect.width}px`;
535547
preview.style.height = `${elementRect.height}px`;
536-
this._setTransform(preview, elementRect.left, elementRect.top);
548+
preview.style.transform = getTransform(elementRect.left, elementRect.top);
537549
}
538550

539551
extendStyles(preview.style, {
@@ -603,7 +615,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
603615
this._preview.classList.add('cdk-drag-animating');
604616

605617
// Move the preview to the placeholder position.
606-
this._setTransform(this._preview, placeholderRect.left, placeholderRect.top);
618+
this._preview.style.transform = getTransform(placeholderRect.left, placeholderRect.top);
607619

608620
// If the element doesn't have a `transition`, the `transitionend` event won't fire. Since
609621
// we need to trigger a style recalculation in order for the `cdk-drag-animating` class to
@@ -634,16 +646,6 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
634646
});
635647
}
636648

637-
/**
638-
* Sets the `transform` style on an element.
639-
* @param element Element on which to set the transform.
640-
* @param x Desired position of the element along the X axis.
641-
* @param y Desired position of the element along the Y axis.
642-
*/
643-
private _setTransform(element: HTMLElement, x: number, y: number) {
644-
element.style.transform = `translate3d(${x}px, ${y}px, 0)`;
645-
}
646-
647649
/**
648650
* Helper to remove an element from the DOM and to do all the necessary null checks.
649651
* @param element Element to be removed.
@@ -768,3 +770,12 @@ interface Point {
768770
x: number;
769771
y: number;
770772
}
773+
774+
/**
775+
* Gets a 3d `transform` that can be applied to an element.
776+
* @param x Desired position of the element along the X axis.
777+
* @param y Desired position of the element along the Y axis.
778+
*/
779+
function getTransform(x: number, y: number): string {
780+
return `translate3d(${x}px, ${y}px, 0)`;
781+
}

0 commit comments

Comments
 (0)