Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate dynamic height with AngularJS

So, I have been trying to animate kind of like a Angular Accordion, but with no success. I figured it out with fixed heights, but not with dynamic. height: auto; does not work. :(

Maybe some of you have had a similar problem?

My code:

html:

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.js"></script>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <section ng-app="myApp">
    <div ng-controller="myCtrl as vm">
        <ul ng-init="vm.tab=1">
            <li ng-repeat="item in vm.data">
              <a href ng-click="vm.tab = item.thingy">{{item.name}}</a>
              <div ng-show="vm.tab === item.thingy">
                <img ng-src="{{item.img}}" width="50px"><br>
                <div class="longDiv">{{item.description}}</div>
              </div>
            </li>
        </ul>
    </div>
  </section>
</body>
</html>

js:

var app = angular.module('myApp', []);

app.controller('myCtrl', ['$scope',
  function($scope) {
    var vm = this;

    vm.data = [{
      name: "First",
      title: "oneTitle",
      description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur.",
      year: "2013",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42735.jpg",
      thingy: 1
    }, {
      name: "third",
      title: "twoTitle",
      description: "Quisque pulvinar libero sed eros ornare",
      year: "2014",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/1/8519.jpg",
      thingy: 2
    }, {
      name: "Second",
      title: "threeTitle",
      description: "Cras accumsan ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur massa vitae tortor vehicula .",
      year: "2015",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/5/43326.jpg",
      thingy: 3
    }, {
      name: "fourth",
      title: "FourTitle",
      description: "Suspendisse ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur vitae mattis magna.",
      year: "2011",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42413.jpg",
  thingy: 4
    }];

  }
]);

Thanks in advance! Plnkr here

like image 623
Kristine Avatar asked Jun 24 '15 12:06

Kristine


4 Answers

I've solved this problem by creating a "collapse" directive:

ngModule.directive('collapse', [function () {
    return {
        restrict: 'A',

        link: function ($scope, ngElement, attributes) {
            var element = ngElement[0];

            $scope.$watch(attributes.collapse, function (collapse) {
                var newHeight = collapse ? 0 : getElementAutoHeight();

                element.style.height = newHeight +"px";
                ngElement.toggleClass('collapsed', collapse);
            });

            function getElementAutoHeight() {
                var currentHeight = getElementCurrentHeight();

                element.style.height = 'auto';
                var autoHeight = getElementCurrentHeight();

                element.style.height = currentHeight +"px";
                getElementCurrentHeight(); // Force the browser to recalc height after moving it back to normal

                return autoHeight;
            }

            function getElementCurrentHeight() {
                return element.offsetHeight
            }
        }
    };
}]);

To use the directive, you just throw it on your element and set it to the scope variable that says whether or not to collapse it:

<div collapse="isCollapsed">
    This will be collapsed
</div>

Now you just need to apply a CSS transition on the height and it will smoothly collapse & expand. Since this directive also adds a class, you can apply other transitions like opacity.

Here's a Codepen with an example: http://codepen.io/KaidenR/pen/GoRJLx

like image 91
Kaiden Avatar answered Nov 15 '22 03:11

Kaiden


Modified @kaiden answer, if using angular directive can use

require('./').directive('collapse', collapseDirective);

/**
* @ngInject
*/
function collapseDirective() {

   return {
    restrict: 'A',
    link: link
   };

   function link($scope, ngElement, attributes) {
     var element = ngElement[0];

     // set the height as a data attr so we can use it again later
     element.dataset.heightOld = angular.element(element).prop('offsetHeight');

     $scope.$watch(attributes.collapse, function (collapse) {
       var newHeight = !collapse ? 0 : element.dataset.heightOld;
       element.style.height = newHeight + 'px';
       ngElement.toggleClass('collapsed', collapse);
     }); 
   }
 }

As the link function then just add CSS transitions for height. This sets a data attribute to store the height then uses it when toggling. In my application I used some markup like

<ion-item class="item-accordion" collapse="vm.isActive($index)">

Note this is inside an ng-repeat and calls a function on view model. This is also an ionic project hence the ion item tag.

like image 22
Gareth Fuller Avatar answered Nov 15 '22 03:11

Gareth Fuller


You can try setting max-height to 0 and then remove the style/class as suggested here:

http://css3.bradshawenterprises.com/animating_height/

like image 22
Icycool Avatar answered Nov 15 '22 04:11

Icycool


So, after long hours of trying to figure out what is the easiest and shortest way to make jQuery accordion with angular content, I figured one easy, but messy way.

Basically I made a new variable in my $scope- accordion, and put in there all accordion html:

vm.data = [{
  name     : "First",
  accordion: ["<h3>Title1</h3><div>Content 1</div><h3>Title2</h3><div>Content 1</div>"]};

For me this works, because now I can use this variable in ng-repeat:

<div ng-repeat="item in vm.data>
    <div ng-repeat="acc in item.accordion" ng-bind-html="acc | normalHtml"></div>

And as long there is only one string in this variable, I get one accordion div. After that I called accordion function inside a setTimout function and everything works fine.

BUT this is messy and kind of "with a hammer where I could've used a feather" situation.

So, for now this is fine for me, but I am still looking for a prettier solution.

Thank you all for helping me out. If some of you have any idea how to make this better, I welcome every answer :)

like image 37
Kristine Avatar answered Nov 15 '22 04:11

Kristine