Angular 2 Slide Up and Down Animation
Asked Answered
S

4

12

I recently built the following Angular 2 Read More component. What this component does is collapse and expand long blocks of text with "Read more" and "Read Less" links. Not on the basis of the character count but on the basis of the specified max height.

import { Component, Input, ElementRef, AfterViewInit } from '@angular/core';

@Component({
    selector: 'read-more',
    template: `
        <div [innerHTML]="text" [class.collapsed]="isCollapsed" [style.height]="isCollapsed ? maxHeight+'px' : 'auto'">
        </div>
            <a *ngIf="isCollapsable" (click)="isCollapsed =! isCollapsed">Read {{isCollapsed? 'more':'less'}}</a>
    `,
    styles: [`
        div.collapsed {
            overflow: hidden;
        }
    `]
})
export class ReadMoreComponent implements AfterViewInit {

    //the text that need to be put in the container
    @Input() text: string;

    //maximum height of the container
    @Input() maxHeight: number = 100;

    //set these to false to get the height of the expended container 
    public isCollapsed: boolean = false;
    public isCollapsable: boolean = false;

    constructor(private elementRef: ElementRef) {
    }

    ngAfterViewInit() {
        let currentHeight = this.elementRef.nativeElement.getElementsByTagName('div')[0].offsetHeight;
       //collapsable only if the contents make container exceed the max height
        if (currentHeight > this.maxHeight) {
            this.isCollapsed = true;
            this.isCollapsable = true;
        }
    }
}

And used like:

<read-more [text]="details" [maxHeight]="250"></read-more>

The component works well. Now I need to add some slide up/down animation to the component so that when Read More link is clicked the contents slide down and when Read less is clicked, the contents slide up to specified max height.

Can anyone please guide how to achieve this?

Snapback answered 15/6, 2016 at 20:7 Comment(4)
See my answer here. The example is pretty much exactly what you are trying to do.Puppetry
Thank you threeve, I actually tried adding the below style info to my above component, but this doesn't seem to work. styles: [` div.collapsed { overflow: hidden; }, div { transition: height 500ms ease; }`]Snapback
If it works for you, can you valid my solution please?Almandine
The solution seems to work only one way - slide up.Skutchan
S
19

My Solution with :enter, :leave and *ngIf:

@Component({
    selector: 'accordion',
    templateUrl: './accordion.component.html',
    animations: [
        trigger('slideInOut', [
            state('in', style({height: '*', opacity: 0})),
            transition(':leave', [
                style({height: '*', opacity: 1}),

                group([
                    animate(300, style({height: 0})),
                    animate('200ms ease-in-out', style({'opacity': '0'}))
                ])

            ]),
            transition(':enter', [
                style({height: '0', opacity: 0}),

                group([
                    animate(300, style({height: '*'})),
                    animate('400ms ease-in-out', style({'opacity': '1'}))
                ])

            ])
        ])
    ]
})
...

Template:

<div *ngIf="shown" [@slideInOut] >
    // ...content
</div>

Unfortunately I had to incorporate this fix also (for slideOut): https://github.com/angular/angular/issues/15798

Skutchan answered 6/4, 2018 at 12:37 Comment(0)
A
15

Automatic property calculation

Animation with automated height calculation

Sometimes you don't know the value of a dimensional style property until runtime. For example, elements often have widths and heights that depend on their content and the screen size. These properties are often tricky to animate with CSS.

In these cases, you can use a special * property value so that the value of the property is computed at runtime and then plugged into the animation.

In this example, the leave animation takes whatever height the element has before it leaves and animates from that height to zero :

animations: [
  trigger('shrinkOut', [
    state('in', style({height: '*'})),
    transition('* => void', [
      style({height: '*'}),
      animate(250, style({height: 0}))
    ])
  ])
]

from Angular official documentation (now as an archive) : https://v2.angular.io/docs/ts/latest/guide/animations.html#!#automatic-property-calculation

Almandine answered 7/3, 2017 at 9:56 Comment(2)
tried this but the text inside the element is not animated but when i put some background color it worked somehow for the container, care to tell why?Gunnery
Does this solution slide up (collapse / hide) only ? And not down (expand / show) ?Skutchan
R
10

This is what I use in Angular 8.1.2. The beauty of the code is that it supports unlimited height of the div that needs to be shown/collapsed and also makes smooth transitions.

TS FILE:

import {Component, OnInit} from '@angular/core';
import {trigger, transition, animate, style, state} from '@angular/animations';

@Component({
    selector: 'app-all-data',
    templateUrl: './all-data.page.html',
    styleUrls: ['./all-data.page.scss'],
    animations: [
        trigger('openClose', [
            state('open', style({
                height: '*',
                opacity: 1,
            })),
            state('closed', style({
                height: '0',
                opacity: 0
            })),
            transition('open => closed', [
                animate('0.35s')
            ]),
            transition('closed => open', [
                animate('0.35s')
            ]),
        ]),
    ]
})
export class AllDataPage implements OnInit {

    showCardBody = false;

    constructor() {
    }

    ngOnInit() {
    }

    /**
     * Toggle details on click
     */
    showDetails() {
        this.showCardBody = !this.showCardBody;
    }

}

HTML FILE:

<button type="button" (click)="showDetails()">
       Toggle Details
</button>

<div class="card-body" [@openClose]="showCardBody ? 'open' : 'closed'">
       <p>This is some content</p>
       <p>This is some content</p>
       <p>This is some content</p>       
       <p>This is some content</p>
       <p>This is some content</p>
</div>
Resee answered 24/8, 2019 at 8:55 Comment(3)
Great Solution mate, this is what I was looking for :)Latchstring
Exactly what I am looking for. Simply Superb...!Glasswork
i would use (click)="toggleDetails()" instead of showDetails()Eichmann
A
2

Threeve's answer is correct - the only problem is that the CSS transition will not work with 'auto'. So you need to capture the auto height in the ngAfterViewInit function and store it as a string. Notice also the use of the setTimeout function to stop the 'unidirectional-data-flow-violation error' that can occur.

import { Component, Input, ElementRef, AfterViewInit } from '@angular/core';

@Component({
selector: 'read-more',
template: `
    <div [style.height]="isCollapsed ? maxHeight+'px' : autoHeight">
        <ng-content></ng-content>
    </div> 

    <span *ngIf="isCollapsable" class="btn-link cpointer" (click)="isCollapsed =! isCollapsed">Read {{isCollapsed? 'more':'less'}} ...</span>

    `,
styles: [` div { overflow-y: hidden; 
          -moz-transition: height .5s;
          -ms-transition: height .5s;
          -o-transition: height .5s;
          -webkit-transition: height .5s;
          transition: height .5s; ease;}
         .cpointer {cursor:pointer; }
        `]
})
export class ReadMoreComponent implements AfterViewInit {

@Input()
maxHeight: number = 40; //two lines

////set these to false to get the height of the expended container 
isCollapsed: boolean = false;
isCollapsable: boolean = false;
autoHeight: string= "auto";

constructor(private elementRef: ElementRef) {}

ngAfterViewInit() {
    // Inportant !!
    // wait a tick to avoid one-time devMode
    // unidirectional-data-flow-violation error
    setTimeout(_ => {
            let currentHeight = this.elementRef.nativeElement.getElementsByTagName('div')[0].offsetHeight;

            this.autoHeight = currentHeight + "px";
            //collapsable only if the contents make container exceed the max height
            if (currentHeight >= this.maxHeight) {
                this.isCollapsed = true;
                this.isCollapsable = true;
            }
        }
    );
}

}
Anis answered 5/1, 2017 at 0:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.