📜 ⬆️ ⬇️

Adaptive carousel on AngularJS

Probably every novice web developer should write a curve, with a bunch of crutches, but his own carousel. This material will help to deal with some of the subtleties of the framework and will show the reader how to make a carousel "angular way".
Carousel - a web-interface element that alternately shows the user in advance prepared slides with information.

carousel example

The finished project can be viewed here .

The idea of ​​our carousel is: it is nice to build cards on the screen in two levels using the css z-index property and alternately changing the css position of the cards with animation changes using the transition property.
The CSS file will look like this:
view CSS
el-carousel { width: 100%; margin: 0; position: relative; z-index: 20; } .el-carousel .el-card { position: absolute; background: rgba(141, 141, 141, 0.5); border:1px #e0e0e0 solid; border-radius:1px; box-shadow: 0 0 0 4px rgba(107, 108, 40, 0.25), 0 0 0 5px rgba(183, 183, 183, 0.6); -webkit-transition: all 1s ease-in-out; -moz-transition: all 1s ease-in-out; -o-transition: all 1s ease-in-out; transition: all 1s ease-in-out; z-index: 5; opacity: 0.2; cursor: pointer; } .el-carousel .sm-el-card-1 { height: 65%; width: 23%; left: 6%; bottom: 1%; z-index: 20; } .el-carousel .sm-el-card-2 { height: 70%; width: 25%; left: 2%; bottom: 7%; opacity: 0; } .el-carousel .sm-el-card-3 { height: 70%; width: 25%; left: 2%; bottom: 7%; z-index: 20; opacity: 0.8; } .el-carousel .sm-el-card-4 { height: 84%; width: 30%; left: 35%; bottom: 14%; z-index: 20; opacity: 1; background: rgba(141, 141, 141, 0.7); } .el-carousel .sm-el-card-5 { height: 70%; width: 25%; left: 73%; bottom: 7%; z-index: 20; opacity: 0.8; } .el-carousel .sm-el-card-6 { height: 70%; width: 25%; left: 73%; bottom: 7%; opacity: 0; } .el-carousel .sm-el-card-7 { height: 65%; width: 23%; left: 70.5%; bottom: 1%; } .el-carousel .sm-el-card-8 { height: 77%; width: 27%; left: 33%; bottom: 8%; } .el-carousel .sm-el-card-hide { height: 77%; width: 27%; left: 33%; bottom: 8%; opacity: 0; } .el-carousel .md-el-card-4 { height: 57%; width: 14%; left: 2%; bottom: 7%; z-index: 20; opacity: 0.6; } .el-carousel .md-el-card-5 { height: 71%; width: 16%; left: 20.5%; bottom: 11%; z-index: 20; opacity: 0.8; } .el-carousel .md-el-card-6 { height: 84%; width: 19%; left: 40.5%; bottom: 14%; z-index: 20; opacity: 1; background: rgba(141, 141, 141, 0.7); } .el-carousel .md-el-card-7 { height: 71%; width: 16%; left: 63.75%; bottom: 11%; z-index: 20; opacity: 0.8; } .el-carousel .md-el-card-8 { height: 57%; width: 14%; left: 84%; bottom: 7%; z-index: 20; opacity: 0.6; } .el-carousel .md-el-card-9 { height: 57%; width: 14%; left: 84%; bottom: 7%; opacity: 0; } .el-carousel .md-el-card-10 { height: 67%; width: 15%; left: 62%; bottom: 7%; } .el-carousel .md-el-card-1 { height: 78%; width: 18%; left: 38.7%; bottom: 9.5%; } .el-carousel .md-el-card-2 { height: 67%; width: 15%; left: 18.7%; bottom: 7%; } .el-carousel .md-el-card-3 { height: 57%; width: 14%; left: 2%; bottom: 7%; opacity: 0; } .el-carousel .md-el-card-hide { height: 78%; width: 18%; left: 38.7%; bottom: 9.5%; opacity: 0; } .el-carousel .lg-el-card-1 { height: 78.7%; width: 15%; left: 40.5%; bottom: 10%; } .el-carousel .lg-el-card-2 { height: 65%; width: 13%; left: 23.5%; bottom: 7%; } .el-carousel .lg-el-card-3 { height: 52%; width: 11%; left: 9%; bottom: 3%; } .el-carousel .lg-el-card-4 { height: 33%; width: 7%; left: 1%; bottom: 4%; z-index: 10; opacity: 0.4; } .el-carousel .lg-el-card-5 { height: 57%; width: 12%; left: 10%; bottom: 7%; z-index: 20; opacity: 0.6; } .el-carousel .lg-el-card-6 { height: 71%; width: 14%; left: 25%; bottom: 11%; z-index: 20; opacity: 0.8; } .el-carousel .lg-el-card-7 { height: 84%; width: 16%; left: 42%; bottom: 14%; z-index: 20; opacity: 1; background: rgba(141, 141, 141, 0.7); } .el-carousel .lg-el-card-8 { height: 71%; width: 14%; left: 61%; bottom: 11%; z-index: 20; opacity: 0.8; } .el-carousel .lg-el-card-9 { height: 57%; width: 12%; left: 78%; bottom: 7%; z-index: 20; opacity: 0.6; } .el-carousel .lg-el-card-10 { height: 33%; width: 7%; left: 91.32%; bottom: 4%; z-index: 10; opacity: 0.4; } .el-carousel .lg-el-card-11 { height: 52%; width: 11%; left: 77%; bottom: 3%; } .el-carousel .lg-el-card-12 { height: 65%; width: 13%; left: 59.5%; bottom: 7%; } .el-carousel .lg-el-card-hide { height: 78.7%; width: 15%; left: 40.5%; bottom: 10%; opacity: 0; } 


Since the carousel is adaptive - all parameters of size and position are defined as a percentage.
So, let's try to implement our angular-way carousel. In order for each carousel card to have a unique pattern and action, we will use the directive and the factory.
')
Consider a factory
In the closure, we will store two arrays:

The factory provides two methods addCard and addAction , which add to the arrays of the html-template and the function that should be executed when clicked, respectively. The addCard function accepts a second, optional parameter as input, which, when the logical value is true, allows promis. This is a kind of crutch, which I will discuss a little later.
Separately, I note a secure connection dependencies. Without going into details, the names of the connected dependencies cannot be changed, since this completely breaks the code when it is minified. Therefore, a secure connection of dependencies is provided in Angular - instead of the factory / controller / directive function, an array is transmitted, the first elements of which specify the string names of the dependencies, and the last function itself with the dependencies connected. In this case, the minification of the code is not terrible for us, however, we must not forget that the order of listing the string dependencies in the array must coincide with the order of connecting the dependencies to the function.
view factory code
 'use strict'; (function () { angular.module('carousel') .factory('card', ['$q', function ($q) { var list = [], action = [], done = $q.defer(); //    function addCard(card, last) { if(typeof card === 'object' && card.length > 0) { list.push(card); if(last) { done.resolve(); } } } //       function addAction(foo) { action.push(foo); } //      ,   return { addCard: addCard, addAction: addAction, list: list, action: action, done: done.promise }; }]); })(); 


The directive of the html-card template
It's simple. For the directive, an isolated scop is set with three parameters forwarded through the attributes of the element on which the directive is installed.
I note that for convenience in the directives, you can change the names of the parameters passed. The key in the scop object indicates a convenient name to use, and in its field, after specifying the type of forwarding (=, @, &), the name of the element attribute. If an attribute of an element is double, for example last-card, then in the name of the element it is indicated in the form of camel notation lastCard.
The action parameter is forwarded through & - this means that the expression is accepted as input, in our case, the function of clicking on the card.
The item and last parameters are forwarded through = , i.e. a certain value is taken as input, which can be specified both explicitly in the attribute value, and defined by the variable of the loop in which the directive is located and passed to the attribute of the variable element.
These parameters are necessary in the case when we do not want to create a separate template for each card, but make one template and clone it using the ng-repeat directive. The item parameter is passed the current data object obtained using ng-repeat . The last parameter is passed a special value of $ last , to which the ng-repeat directive will be set to true if the object from the array under consideration for cloning is the last one.
To get the html template (actually the internal contents of the directive), you need to set the transclude parameter to true . In this case, the fifth parameter appears in the linking function of the directive ( link ), which is the transclusion function. I note that the purpose of this function is wider, but in this case it is used only to get the internal contents of the directive.
It is time to substantiate the use of a crutch with the explicit definition of the last cloning element of $ last . The fact is that a user directive with transclude paired with ng-repeat works specifically. This is due to the sequence of operations: first, angular clones the element template, then the other directives with lower priority are executed, including custom ones, and only after that the cloned templates are filled with values ​​from the scopa. Therefore, unless you explicitly indicate that ng-repeat has done its job - the carousel will not display cards with the contents of the cloned elements.
view directive code
 'use strict'; (function () { angular.module('carousel') .directive('elCard', ['card', function(card) { return { scope: { action: '&cardAction', item: '=elCard', last: '=lastCard' }, restrict: 'A', transclude: true, link: function(scope, elem, attr, ctrl, transclude) { //         card.addAction(scope.action); //     transclude(scope, function(item) { card.addCard(item, scope.last); elem.remove(); }); } }; }]); })(); 


Carousel Directive
The directive builds our carousel on the available templates. The carousel has three versions: 3, 5 or 7 cards in the first row. By default, 7 cards are selected, but the elements parameter is forwarded to determine the number of maps manually. Depending on the number of cards, the coefficient of recalculation of the heihtCoeff roundabout element height is determined .
The directive is built on three functions:

Auxiliary functions runCarousel and stopCarousel are provided for triggering and stopping , which periodically execute moveCards using $ interval . When you switch to another tab, $ interval continues its work, but the css transition property does not, which leads to malfunctions in the carousel. Therefore, the start and stop of the carousel are tied to the events of changing the active window. At the end of the directive, we do not forget to untie all the listeners.
view directive code
 'use strict'; (function () { angular.module('carousel') .directive('elCarousel', ['$window', '$compile', '$interval', 'card', function($window, $compile, $interval, card) { return { scope: { elements: '=elCarousel' }, restrict: 'A', link: function($scope, elem) { var cards = [], action = [], heightCoeff = ($scope.elements === 3) ? 2 : ($scope.elements === 5) ? 3.3 : 3.96, cardAmount = ($scope.elements === 3) ? 8 : ($scope.elements === 5) ? 10 : 12; $scope.card = []; //       elem.addClass('el-carousel'); //           ng-repeat function preStartActions() { if(card.list.length < 1) { return; } cards = card.list; action = card.action; makeCards(); } preStartActions(); card.done.then(preStartActions); //        function changeHeight() { var carouselWidth = elem.width(), carouselHeight = carouselWidth / heightCoeff; elem.css('height', carouselHeight); } angular.element($window).bind('resize', changeHeight); changeHeight(); //  DOM     function makeCards() { elem.empty(); var k = 0, cardNumber = (cards.length > cardAmount) ? cards.length : cardAmount, numClass = (cardAmount === 8) ? 'sm-' : (cardAmount === 10) ? 'md-' : 'lg-'; for(var i = 0; i < cardNumber; i++) { var div = angular.element('<div ng-click="cardAction' + i + '()" class="el-card" ng-class="card[' + i + ']"></div>'); if(i < cards.length) { div.append(cards[i].clone()); $scope['cardAction' + i] = action[i]; } else { div.append(cards[k].clone()); $scope['cardAction' + i] = action[k]; k = (k > cards.length - 2) ? 0 : k + 1; } $scope.card[i] = (i < cardAmount) ? numClass + 'el-card-' + (i + 1) : numClass + 'el-card-hide'; $compile(div)($scope); elem.append(div); } } //      function moveCards() { var lastElem = $scope.card.shift(); $scope.card.push(lastElem); } // /       var moveInterval; runCarousel(); angular.element($window).bind('blur', stopCarousel); function stopCarousel() { $interval.cancel(moveInterval); } angular.element($window).bind('focus', runCarousel); function runCarousel() { moveInterval = $interval(moveCards, 2000); } elem.bind('$destroy', function () { $interval.cancel(moveInterval); angular.element($window).unbind('blur', stopCarousel); angular.element($window).unbind('focus', runCarousel); angular.element($window).unbind('resize', changeHeight); }); } }; }]); })(); 


As a result, our html carousel code might look like this.
  <div el-carousel="7"> <div ng-repeat="card in cardList" el-card="card" card-action="someAction(someParam)" last-card="$last"> <span>{{card.name}}</span> <img class="image image-1" ng-src={{card.img}} alt={{card.alt}}/> </div> <div el-card card-action="otherAction(otherParam)"> <img class="image image-2" src="/app/ru/main/img/js.png" alt="javascript"/> </div> </div> 

You can use either individually written templates, or cloned ones using ng-repeat , and also combine them together.

Thank you for your attention, good luck to all.

Source: https://habr.com/ru/post/261847/


All Articles