Search code examples
javascriptangulartypescriptdraggableangular-animations

Angular 6 Transform Animation doesn't change styles on Element


Gist

I am creating an image slider with Angular 6, that can be moved by dragging. The position is changed via transform: translateX(percentage).When the user doesn't click anymore I am animating the x-position.

Issue(s)

Problem is showcased in this video.

(1) The animation works but isn't changing the transform style of the element. (so the translateX value is wrong after the animation finished)

(2) After animating once, the dragging does not work anymore (only when destroying the animation, which results in weird behavior)

Code

Html:

<div
    class="slider"
    (mousedown)="onMouseDown($event)"
    (touchstart)="onTouchStart($event)"
    (mouseup)="onMouseUp($event)"
    (touchend)="onTouchEnd($event)"
    (mousemove)="onMouseMove($event)"
    (touchmove)="onTouchMove($event)"
    (mouseleave)="onMouseLeave($event)"
    #slider
>

    <div
        class="vis-container"
        [style.transform]="translateXValue"
        [class.grabbing]="grabbing"
        [class.grab]="!grabbing"
        #container
    >
        <div
            class="item"
            *ngFor="let i of images; let j = index"
            [style.left]="j * 100 + '%'"
        >
            <img
                dyImagePositioning
                src="{{i}}"
            >
        </div>
    </div>
    <div>{{translateXValue}}</div>
</div>

Typescript:

images: string[] = [
        'https://images.unsplash.com/photo-1533147722845-4078740ae714?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=c8e866ca41f36ad560c435c1bb51e0fd&auto=format&fit=crop&w=1350&q=80',
        'https://images.unsplash.com/photo-1504415724575-56cef5e3da72?ixlib=rb-0.3.5&s=89c39a767a764693bcc2e2c2ecad59ce&auto=format&fit=crop&w=1350&q=80',
        'https://s3.eu-central-1.amazonaws.com/drakery-4/product-files/5b5c8d96823eba53e45ba5ac/5b5c8d9a823eba53e45ba5ad-normal.webp',
    ];

    grabbing: boolean = false;
    startX: number;
    startXPerc: number;
    translateX: number = 0;
    animationDurance: number = 300;

    @ViewChild('slider') slider;
    @ViewChild('container') container;

    constructor(
        private builder: AnimationBuilder,
    ) { }

    ngOnInit(): void { }

    start(pageX: number): void {
        this.grabbing = true;
        this.startX = pageX - this.slider.nativeElement.offsetLeft;
        this.startXPerc = this.translateX;
    }
    onMouseDown(event: MouseEvent): void {
        this.start(event.pageX);
    }
    onTouchStart(event: TouchEvent): void {
        this.start(event.changedTouches[0].pageX);
    }

    end(): void {
        this.grabbing = false;
        const endX = Math.round(this.translateX / 100) * 100;
        const animationFactory = this.builder.build(this.translate(
            this.translateX,
            endX,
        ));
        const animationPlayer: AnimationPlayer = animationFactory.create(this.container.nativeElement);
        animationPlayer.play();
        // Destroying the animation makes it able
        // to drag again, but results in weird behaviour
        /* animationPlayer.onDone(() => {
            animationPlayer.destroy();
        }) */
    }
    onMouseUp(event: MouseEvent): void {
        this.end();
    }
    onTouchEnd(event: TouchEvent): void {
        this.end();
    }

    move(pageX: number): void {
        if (!this.grabbing) return;
        event.preventDefault();
        const x = pageX - this.slider.nativeElement.offsetLeft;
        const diff = this.startX - x;
        const diffPerc = 100 * diff / this.slider.nativeElement.offsetWidth;
        this.translateX = this.startXPerc - diffPerc;
    }
    onMouseMove(event: MouseEvent): void {
        this.move(event.pageX);
    }
    onTouchMove(event: TouchEvent): void {
        this.move(event.changedTouches[0].pageX);
    }

    onMouseLeave(event: MouseEvent): void {
        if (!this.grabbing) return;
        this.grabbing = false;
        this.end();
    }

    get translateXValue(): string {
        return `translateX(${this.translateX}%)`
    }

    private translate(startX: number, endX: number): AnimationMetadata[] {
        return [
            style({ transform: `translateX(${startX}%)` }),
            animate(`${this.animationDurance}ms ease-out`, style({ transform: `translateX(${endX}%)` })),
        ];
    }

Solution

  • In your case, I would dump the angular animation and simply use a css animation instead. So in your case it would something like this:

    .slider
        ...
        transition-duration: 500ms
        transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1)