diff --git a/src/cdk/drag-drop/drag.spec.ts b/src/cdk/drag-drop/drag.spec.ts index 210f0783beb..320082448bb 100644 --- a/src/cdk/drag-drop/drag.spec.ts +++ b/src/cdk/drag-drop/drag.spec.ts @@ -144,6 +144,30 @@ describe('CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); })); + it('should preserve the previous `transform` value', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + dragElement.style.transform = 'translateX(-50%)'; + dragElementViaMouse(fixture, dragElement, 50, 100); + expect(dragElement.style.transform).toBe('translateX(-50%) translate3d(50px, 100px, 0px)'); + })); + + it('should not generate multiple own `translate3d` values', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + dragElement.style.transform = 'translateY(-50%)'; + + dragElementViaMouse(fixture, dragElement, 50, 100); + expect(dragElement.style.transform).toBe('translateY(-50%) translate3d(50px, 100px, 0px)'); + + dragElementViaMouse(fixture, dragElement, 100, 200); + expect(dragElement.style.transform).toBe('translateY(-50%) translate3d(150px, 300px, 0px)'); + })); + }); describe('touch dragging', () => { diff --git a/src/cdk/drag-drop/drag.ts b/src/cdk/drag-drop/drag.ts index 779b5c1d26d..36d2d5c9590 100644 --- a/src/cdk/drag-drop/drag.ts +++ b/src/cdk/drag-drop/drag.ts @@ -129,6 +129,9 @@ export class CdkDrag implements AfterViewInit, OnDestroy { /** CSS `transform` that is applied to the element while it's being dragged. */ private _activeTransform: Point = {x: 0, y: 0}; + /** Inline `transform` value that the element had before the first dragging sequence. */ + private _initialTransform?: string; + /** * Whether the dragging sequence has been started. Doesn't * necessarily mean that the element has been moved. @@ -325,6 +328,12 @@ export class CdkDrag implements AfterViewInit, OnDestroy { return; } + // Cache the previous transform amount only after the first drag sequence, because + // we don't want our own transforms to stack on top of each other. + if (this._initialTransform == null) { + this._initialTransform = this._rootElement.style.transform || ''; + } + this._hasStartedDragging = this._hasMoved = false; this._initialContainer = this.dropContainer; this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove); @@ -373,13 +382,12 @@ export class CdkDrag implements AfterViewInit, OnDestroy { if (!this._hasStartedDragging) { const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x); const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y); - const minimumDistance = this._config.dragStartThreshold; // Only start dragging after the user has moved more than the minimum distance in either // direction. Note that this is preferrable over doing something like `skip(minimumDistance)` // in the `pointerMove` subscription, because we're not guaranteed to have one move event // per pixel of movement (e.g. if the user moves their pointer quickly). - if (distanceX + distanceY >= minimumDistance) { + if (distanceX + distanceY >= this._config.dragStartThreshold) { this._hasStartedDragging = true; this._ngZone.run(() => this._startDragSequence()); } @@ -399,7 +407,11 @@ export class CdkDrag implements AfterViewInit, OnDestroy { pointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x; activeTransform.y = pointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y; - this._setTransform(this._rootElement, activeTransform.x, activeTransform.y); + const transform = getTransform(activeTransform.x, activeTransform.y); + + // Preserve the previous `transform` value, if there was one. + this._rootElement.style.transform = this._initialTransform ? + this._initialTransform + ' ' + transform : transform; } // Since this event gets fired for every pixel while dragging, we only @@ -507,9 +519,8 @@ export class CdkDrag implements AfterViewInit, OnDestroy { } this.dropContainer._sortItem(this, x, y, this._pointerDirectionDelta); - this._setTransform(this._preview, - x - this._pickupPositionInElement.x, - y - this._pickupPositionInElement.y); + this._preview.style.transform = + getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y); } /** @@ -525,7 +536,8 @@ export class CdkDrag implements AfterViewInit, OnDestroy { preview = viewRef.rootNodes[0]; this._previewRef = viewRef; - this._setTransform(preview, this._pickupPositionOnPage.x, this._pickupPositionOnPage.y); + preview.style.transform = + getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y); } else { const element = this._rootElement; const elementRect = element.getBoundingClientRect(); @@ -533,7 +545,7 @@ export class CdkDrag implements AfterViewInit, OnDestroy { preview = element.cloneNode(true) as HTMLElement; preview.style.width = `${elementRect.width}px`; preview.style.height = `${elementRect.height}px`; - this._setTransform(preview, elementRect.left, elementRect.top); + preview.style.transform = getTransform(elementRect.left, elementRect.top); } extendStyles(preview.style, { @@ -603,7 +615,7 @@ export class CdkDrag implements AfterViewInit, OnDestroy { this._preview.classList.add('cdk-drag-animating'); // Move the preview to the placeholder position. - this._setTransform(this._preview, placeholderRect.left, placeholderRect.top); + this._preview.style.transform = getTransform(placeholderRect.left, placeholderRect.top); // If the element doesn't have a `transition`, the `transitionend` event won't fire. Since // we need to trigger a style recalculation in order for the `cdk-drag-animating` class to @@ -634,16 +646,6 @@ export class CdkDrag implements AfterViewInit, OnDestroy { }); } - /** - * Sets the `transform` style on an element. - * @param element Element on which to set the transform. - * @param x Desired position of the element along the X axis. - * @param y Desired position of the element along the Y axis. - */ - private _setTransform(element: HTMLElement, x: number, y: number) { - element.style.transform = `translate3d(${x}px, ${y}px, 0)`; - } - /** * Helper to remove an element from the DOM and to do all the necessary null checks. * @param element Element to be removed. @@ -768,3 +770,12 @@ interface Point { x: number; y: number; } + +/** + * Gets a 3d `transform` that can be applied to an element. + * @param x Desired position of the element along the X axis. + * @param y Desired position of the element along the Y axis. + */ +function getTransform(x: number, y: number): string { + return `translate3d(${x}px, ${y}px, 0)`; +}