2

I wrote an angular.js directive which uses stellar.js for parallax effects. When I call the same directive multiple times one after another, stellar.js will only work on the first one. I read about $.stellar('refresh'); which should re-initialize stellar after injecting elements in the DOM, but this had no effect for me. Also calling stellar on the window had no effect.

Here is the code of my directive:

angular.module('ClientApp')
    .directive('parallaxModule', function () {
        return {
            templateUrl: 'views/parallax-module.html',
            restrict: 'E',
            scope: {
                data: '='
            },
            controller: function() {
               $(function(){
                   $(this).stellar({
                        horizontalScrolling: false,
                        verticalScrolling: true,
                        verticalOffset: 50
                   });
                });
            }
        };
    });

This is how I use the directive:

<parallax-module data="sectionData.para"></parallax-module>
<parallax-module data="sectionData.para"></parallax-module>

And here's my template:

<div class="parallax-module" data-stellar-background-ratio="0.2">
    <div class="wrapper" data-stellar-ratio="2.5">
        <div class="title">{{data.title}}</div>
        <div class="caption">{{data.caption}}</div>
    </div>
</div>

Here's a plunker: http://plnkr.co/edit/TJbPL3dhSsiitZQWm9Qe?p=preview

Felix Engelmann
  • 399
  • 5
  • 17

3 Answers3

1

$timeout works because the stellar plugin is supposed to be applied, not to each parallax element, but to the scrolling container. In the example in the docs it is applied to the window: $(window).stellar();

And so, with $timeout, the code "waits" the end of the digest queue and when it is executed, all the directives are loaded. Unfortunately, it is executed for each directive unnecessarily.

The solution is to apply the directive to the container. Then it becomes as simple as:

.directive("parallaxContainer", function(){
  return {
    link: function(scope, element){
      element.stellar({
          horizontalScrolling: false,
          verticalScrolling: true,
          verticalOffset: 0
        });
    }
  }
})

This solution relies on there being a stellar-compatible element (with data-stellar-background-ratio, for example) in that container:

<body parallax-container>
  <div data-stellar-background-ratio="0.2">
    ...
  </div>

  <div data-stellar-background-ratio="0.2">
    ...
  </div>
</body>

plunker

EDIT:

The above code would not work if the parallax elements are loaded dynamically. This complicates things a bit, but not much. In this arrangement, we'd need the container directive responsible for triggering the plugin, and the actual parallax elements directives that would register themselves with the container and notify the container when they have loaded.

The element directive could use ng-include to get a notification when content has loaded. This directives uses require: "^parallaxContainer".

app.directive('parallaxModule', function() {
    return {
      template: '<div ng-include="\'parallax-module.html\'" onload="notifyLoaded()"></div>',
      restrict: 'E',
      scope: {
        data: '='
      },
      require: "^parallaxContainer",
      link: {
        pre: function(scope, element, attrs, parallaxContainer){
          parallaxContainer.registerElement();
          scope.notifyLoaded = parallaxContainer.notifyElementLoaded;
        }
      }
    };
  })

The parallaxContainer directives adds a controller that exposes the methods to register and notify. And it triggers the plugin when all the children have loaded

app.directive("parallaxContainer", function() {
    var parallaxElementCount = 0;
    var parallaxElementLoaded = 0;
    return {
      controller: function($element){

        this.registerElement = function(){
          parallaxElementCount++;
        };

        this.notifyElementLoaded = function(){
          parallaxElementLoaded++;

          if (parallaxElementCount !== 0 && parallaxElementCount === parallaxElementLoaded){

            $element.stellar({
              horizontalScrolling: false,
              verticalScrolling: true,
              verticalOffset: 0
            });
          }
        }
      },
    }
  })

plunker

Note that stellar doesn't work (for some reason - perhaps there is some info on this in stellar docs) when it is activated more than once, so once triggered, loading more child elements won't change anything.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • I tested this in my project, but as I'm loading the parallaxModules dynamically, a parallaxContainer is no solution as stellar get's initialized before the modules are loaded and inserted in the DOM. But i think, one stellar call _after_ the modules are loaded should realize your idea of avoiding unnecessary calls in my specific context too. I'm not that far though, but I will let you know if it worked. Thanks. :] – Felix Engelmann Feb 02 '15 at 13:28
  • @dorthrithil, true, if you are loading dynamically, it complicates things. For simplicity, your solution might be better, which is why I suggested that you posted it as an answer. – New Dev Feb 02 '15 at 13:30
  • @dorthrithil, I edited the question for a dynamic case – New Dev Feb 02 '15 at 14:00
  • @NewDev Your dynamic solution works just perfect. I learned some new tricks there, thank you! – Felix Engelmann Feb 03 '15 at 09:35
  • Just for the record: I'm using [ScrollMagic](https://github.com/janpaepke/ScrollMagic) now. You can dynamically add and remove ScrollScenes (my parallaxContainers) to the ScrollMagic controller instance which makes everything so much easier. – Felix Engelmann Feb 12 '15 at 23:01
0

I found a solution: Because stellar is not part of the angular framework, I have to use $apply() to execute stellar expressions. But as $digest is already in progress in my directive, I have to use $timeout, to get the stellar() call in another digest cycle. $timeout implicitly calls $apply. I also have to call stellar() on angular.element($window) to get multiple directives running correctly.
So here's my working code:

angular.module('ClientApp')
    .directive('parallaxModule', function ($timeout, $window) {
        return {
            templateUrl: 'views/parallax-module.html',
            restrict: 'E',
            scope: {
                data: '='
            },
            link: function() {
                $timeout(function () {
                    angular.element($window).stellar({
                        horizontalScrolling: false,
                        verticalScrolling: true,
                        verticalOffset: 50
                    });
                });
            }
        };
    });
Felix Engelmann
  • 399
  • 5
  • 17
0

Use NG-PARALLAX directive for Angular2/Angular4 is very easy.

Link:https://www.npmjs.com/package/ng2-parallaxscroll

Git:https://github.com/tyrantwave/ng2-parallaxscroll

Example 1:

<div parallax [config]="{axis: x, speed: -.3}" style=" background-image: url('/parallax_img.jpg');"></div>

Example 2:

<ng-parallax img="img/img.jpg" [config]="{axis: x, speed: -.3}" class="some-class"></ng-parallax>
neobot
  • 1
  • 2