加载Ng-Repeat元素后运行的jQuery Swiper脚本

时间:2022-06-28 01:24:04

I'm making a web app using AngularJS, jQuery, HTML, CSS and Bootstrap, and I would like to pick some image links from my JSON that is located in an Apache2 server and use them in order to render those images in my main page. I would also like to swipe them like in a carousel. To make that work, I'm trying to use iDangero.us Swiper.

我正在使用AngularJS,jQuery,HTML,CSS和Bootstrap创建一个Web应用程序,我想从我的JSON中选择一些位于Apache2服务器中的图像链接并使用它们来在我的主页面中呈现这些图像。我也想像旋转木马一样刷它们。为了做到这一点,我正在尝试使用iDangero.us Swiper。

When I pick my images with 3 separated divs, I have no problems. I get my images and then I can normally swipe them as I want. I do it like shown below:

当我用3个分开的div选择我的图像时,我没有问题。我得到了我的图像,然后我可以按照我想要的方式刷它们。我这样做如下所示:

Main.html:

main.html中:

<div ng-init="initGetRequestMain()">

  <div class="swiper-slide" ng-click="swipers()" 
              style="{{data.background1}}"></div>
  <div class="swiper-slide" ng-click="swipers()" 
              style="{{data.background2}}"></div>
  <div class="swiper-slide" ng-click="swipers()" 
              style="{{data.background3}}"></div>

   <script src="scripts/custom/main/swipers.js"></script>
</div>

I use Swiper to swipe from one image to another, and it seems to work as it should. It's a jQuery plugin, and you can see some demos at this link.

我使用Swiper从一个图像滑动到另一个图像,它似乎应该工作。这是一个jQuery插件,你可以在这个链接上看到一些演示。

Swipers.js:

Swipers.js:

angular.module('swipers', [])
       .controller('',[ 
        $(document).ready(function (){
           var swiper = new Swiper('.swiper-container',{
           direction: 'horizontal',
           pagination: '.swiper-pagination',
           paginationClickable: true
       })
})]);

Json:

JSON:

"background1":"background-image: url(images/img1.jpg)",
"background2":"background-image: url(images/img2.jpg)",
"background3":"background-image: url(images/img3.jpg)"

mainController.js:

mainController.js:

  myApp.controller('MainController', ["$scope","$http",                
                  function($scope,$http){

      $scope.initGetRequestMain = function(){

       $http.get('http://localhost/main.json').success(function(data){

         $scope.data=data;
       })
      }
  }]);

The problem is that when I try to use ng-repeat instead of 3 separated divs, I can't see them anymore and my Swiper script triggers before they are fully loaded. I have no errors in my console or in my JSON (validated with JSONLint). Below, I added 2 screenshots of my output in both situations.

问题是,当我尝试使用ng-repeat而不是3个单独的div时,我再也看不到它们了,我的Swiper脚本在完全加载之前就会触发。我的控制台或JSON中没有错误(使用JSONLint验证)。下面,我在两种情况下都添加了2个输出屏幕截图。

Working with 3 separated divs:

使用3个独立的div:

加载Ng-Repeat元素后运行的jQuery Swiper脚本

Not working with ng-repeat:

不使用ng-repeat:

加载Ng-Repeat元素后运行的jQuery Swiper脚本

This is the code where I try to make ng-repeat work keeping the same controller and the same Swiper script as before:

这是我尝试使ng-repeat工作保持与以前相同的控制器和相同的Swiper脚本的代码:

Main.html:

main.html中:

  <div ng-init="initGetRequestMain()">

       <div ng-repeat="slide in data.slides" isLoaded="">
           <div class="swiper-slide" style="{{slide.background}}" 
                       ng-click="swipers()"></div>
       </div>

    <script type="text/javascript-lazy" src="scripts/custom/main/swipers.js"></script>
  </div>

mainJson.json:

mainJson.json:

"slides":[
            {"background":"background-image:url('images/img1.jpg')"},
            {"background":"background-image:url('images/img2.jpg')"},
            {"background":"background-image: url('images/img3.jpg')"}
],

In order to get my images loaded before triggering the script, I'm trying to use 2 custom directives.

为了在触发脚本之前加载我的图像,我正在尝试使用2个自定义指令。

isLoaded tells me when the last ng-repeat element is loaded and sets pageIsLoaded = true;:

isLoaded告诉我何时加载了最后一个ng-repeat元素并设置了pageIsLoaded = true;:

myApp.directive('isLoaded', function (){

   return{
     scope:true,
     restrict: 'A', //Attribute type
     link: function (scope, elements, arguments){ 

        if (scope.$last === true) {
            scope.pageIsReady = true;
            console.log('page Is Ready!');
         }
     }   
   }
})

gettingTheScript waits for pageIsLoaded = true; and loads the script:

gettingTheScript等待pageIsLoaded = true;并加载脚本:

myApp.directive('src', function (){

     return{
       scope:true,
       restrict: 'A', //Attribute type
       link: function (scope, elements, arguments){

            scope.$on(pageIsReady===true, function(){
                   if (attr.type === 'text/javascript-lazy'){

                       scope.scriptLink = arguments.src;
                    }
             })
         },
         replace: true, //replaces our element 
         template: '{{scriptLink}}'
       }
  })   

They do not seem to fix my problem. I also can't see console.log('page Is Ready!'); when making the first one.

他们似乎没有解决我的问题。我也看不到console.log('page Is Ready!');在制作第一个时。

I'm just having some troubles when I have to trigger a script like Swiper after the page is loaded in order to avoid these kind of problems. My images seem to have no height. I think that the problem is caused by ng-repeat not fully loading before triggering my script.

当我加载页面后必须触发像Swiper这样的脚本以避免这些问题时,我只是遇到了一些麻烦。我的图像似乎没有高度。我认为问题是由于在触发我的脚本之前ng-repeat没有完全加载。

What am I doing wrong? Are there better solutions?

我究竟做错了什么?有更好的解决方案吗?

3 个解决方案

#1


11  

Before getting into how to make this work, most of these types of jQuery plugins don't work well with angular since they make modifications to the DOM that angular doesn't know about.

在开始如何使这项工作成功之前,大多数这些类型的jQuery插件都不能很好地处理角度,因为它们会修改角度不知道的DOM。

That being said, the trick is deferring the invocation of the jQuery plugin until after Angular has rendered the DOM.

话虽这么说,诀窍是推迟jQuery插件的调用,直到Angular渲染DOM。

First, put your swiper plugin into a directive:

首先,将swiper插件放入指令中:

.directive('swiper', function($timeout) {
    return {
        link: function(scope, element, attr) {
            //Option 1 - on ng-repeat change notification
            scope.$on('content-changed', function() {
                new Swiper(element , {
                    direction: 'horizontal',
                    pagination: '.swiper-pagination',
                    paginationClickable: true);
            }
            //Option 2 - using $timeout
            $timeout(function(){
                new Swiper(element , {
                    direction: 'horizontal',
                    pagination: '.swiper-pagination',
                    paginationClickable: true);
            });

        }
    };
})

For Option 1, you need a modified isLoaded directive

对于选项1,您需要修改的isLoaded指令

myApp.directive('isLoaded', function (){

   return{
     scope:false, //don't need a new scope
     restrict: 'A', //Attribute type
     link: function (scope, elements, arguments){ 

        if (scope.$last) {
            scope.$emit('content-changed');
            console.log('page Is Ready!');
        }
     }   
   }
})

Finally, your markup (note the change from isLoaded to is-loaded....this is probably the reason you weren't seeing the notification).

最后,你的标记(注意从isLoaded到is-loaded的变化....这可能是你没有看到通知的原因)。

<div ng-init="initGetRequestMain()" swiper>

   <div ng-repeat="slide in data.slides" is-loaded>
       <div class="swiper-slide" style="{{slide.background}}" 
                   ng-click="swipers()"></div>
   </div>
</div>

As mentioned, most jQuery plugins that do carousel functionality don't handle changes to the DOM (i.e new elements) very well. Even with both options, you may see unexpected things if you change your model after the ng-repeat is first rendered. However, if your model is static, this should work for you. If your model changes, then you might want to search for a more "angular" carousel directive.

如上所述,大多数执行轮播功能的jQuery插件都不能很好地处理对DOM(即新元素)的更改。即使有这两个选项,如果在首次渲染ng-repeat后更改模型,也可能会看到意外情况。但是,如果您的模型是静态的,这应该适合您。如果您的模型发生变化,那么您可能希望搜索更“有角度”的轮播指令。

#2


3  

There's an even easier way you can use that's baked into Swiper.js. When you're initializing the swiper, just set the observer property to true. This will make Swiper watch for changes to your slides, so you won't have to worry about whether it runs before all the slides are added through ng-repeat.

你可以使用更简单的方法将其融入到Swiper.js中。初始化swiper时,只需将observer属性设置为true即可。这将使Swiper监视幻灯片的更改,因此您无需担心是否在通过ng-repeat添加所有幻灯片之前运行它。

Example:

例:

var galleryTop = new Swiper('.gallery-top', { observer: true });

var galleryTop = new Swiper('。gallery-top',{observer:true});

And from the Swiper documentation on the observer property:

从观察者属性的Swiper文档中:

Set to true to enable Mutation Observer on Swiper and its elements. In this case Swiper will be updated (reinitialized) each time if you change its style (like hide/show) or modify its child elements (like adding/removing slides)

设置为true以在Swiper及其元素上启用Mutation Observer。在这种情况下,如果您更改其样式(如隐藏/显示)或修改其子元素(如添加/删除幻灯片),Swiper将每次更新(重新初始化)

#3


0  

I'm answering to this old question because some guys asked me to do it and because I want to help other developers with this "funny" problem.

我正在回答这个老问题,因为有些人要我这样做,因为我想帮助其他开发人员解决这个“有趣”的问题。

After more than one year, I managed to solve this issue using Promises and Factories.

一年多之后,我设法使用Promises和Factories解决了这个问题。

Essentially, I made a Factory, ItemFactory ensuring to get all the items needed (in this case slides), then, I made another Factory, SwiperFactory, which uses a Promise to trigger that funny guy, swipers(). I called all those factory methods into a MainController, which, after getting the slides and triggering the script, does $scope.apply(); to reflect the changes in the main view.

基本上,我创建了一个Factory,ItemFactory确保获得所需的所有项目(在本例中为幻灯片),然后,我创建了另一个Factory,SwiperFactory,它使用Promise来触发那个有趣的家伙,swipers()。我将所有这些工厂方法调用到MainController中,在获取幻灯片并触发脚本之后,它会执行$ scope.apply();反映主视图中的更改。

Main.html:

main.html中:

//MainController known as mainCtrl
<div ng-repeat="slide in mainCtrl.slides">
  <div class="swiper-slide" ng-click="mainCtrl.swipers()"></div>
</div>

MainController.js:

MainController.js:

angular
.module('myApp')
.controller('MainController', MainController);

function MainController($scope, ItemFactory, SwiperFactory) {

   var vm    = this;
   //Initializing slides as an empty array
   vm.slides = [];

   /** Gets called as soon as the Controller is called **/
   getData('main.json');

   function getData(path){
      ItemFactory.getItems(path).then( function(response){

        //If response is not empty
        if (response) {
           //Assigning fetched items to vm.items
           vm.slides = response.data;
        }

        //Calling triggerSwiper after we ensured fetching item data
        SwiperFactory.triggerSwiper();

        //Callig $scope.$apply() to reflect correctly the changes in the view
        $scope.$apply();
      })
   };

}

ItemFactory.js:

ItemFactory.js:

angular
.module('myApp')
.factory('ItemFactory', function ($http) {

    /** Using this referring as ItemFactory Controller **/
    var vm     = this;
    vm.API_URL = 'http://localhost/';

    /** Gets items from API based on jsonPath **/
    function getItems(jsonPath) {
        return $http.get(vm.API_URL + jsonPath
        ).then(function (response) {
            return (response);
        });
    }

    //Exposing getItems method
    return {
        getItems : getItems
    }
});

SwiperFactory.js:

SwiperFactory.js:

angular
.module('myApp')
.factory('SwiperFactory', function () {

    /** Using this referring as SwiperFactory Controller **/
    var vm     = this;

    /** Triggers Swiper Script 
     *  Resolving only after swiper has been triggered **/
    function triggerSwiper(){
      return new Promise( function(resolve, reject){
        resolve({
            $(document).ready(function (){
                var swiper = new Swiper('.swiper-container',{
                direction           : 'horizontal',
                pagination          : '.swiper-pagination',
                paginationClickable : true
            })
        });

      }, function(error){
         console.log('Error while triggering Swiper');
      })

    }

    //Exposing triggerSwiper method
    return {
        triggerSwiper : triggerSwiper
    }
});
  • $scope.$apply(); can be removed, it is not really essential
  • $范围$适用()。可以删除,这不是必不可少的
  • In general, this kind of approach works well with asynchronous tasks
  • 通常,这种方法适用于异步任务

The code above does the work, but, please, pay attention to what I'm saying below

上面的代码完成了工作,但请注意我在下面说的内容

Try avoiding using jQuery libraries such as iDangero.us Swiper in an AngularJS project. Instead, look for an Angular library which makes the same work. If back then I was skilled like I'm right now, I would have never used it, expecially for the $(document).ready stuff. Instead, study Promises or AngularJS way of doing that. I was totally new to AngularJS and Web Development in general, so I took wrong decisions.

尝试避免在AngularJS项目中使用jQuery库,例如iDangero.us Swiper。相反,寻找一个Angular库来做同样的工作。如果回来的话我就像现在这样熟练,我从来没有用过它,尤其是$(document).ready的东西。相反,研究Promises或AngularJS这样做的方式。我对AngularJS和Web Development一般都是新手,所以我做出了错误的决定。

I hope I've been helpful.

我希望我一直很有帮助。

#1


11  

Before getting into how to make this work, most of these types of jQuery plugins don't work well with angular since they make modifications to the DOM that angular doesn't know about.

在开始如何使这项工作成功之前,大多数这些类型的jQuery插件都不能很好地处理角度,因为它们会修改角度不知道的DOM。

That being said, the trick is deferring the invocation of the jQuery plugin until after Angular has rendered the DOM.

话虽这么说,诀窍是推迟jQuery插件的调用,直到Angular渲染DOM。

First, put your swiper plugin into a directive:

首先,将swiper插件放入指令中:

.directive('swiper', function($timeout) {
    return {
        link: function(scope, element, attr) {
            //Option 1 - on ng-repeat change notification
            scope.$on('content-changed', function() {
                new Swiper(element , {
                    direction: 'horizontal',
                    pagination: '.swiper-pagination',
                    paginationClickable: true);
            }
            //Option 2 - using $timeout
            $timeout(function(){
                new Swiper(element , {
                    direction: 'horizontal',
                    pagination: '.swiper-pagination',
                    paginationClickable: true);
            });

        }
    };
})

For Option 1, you need a modified isLoaded directive

对于选项1,您需要修改的isLoaded指令

myApp.directive('isLoaded', function (){

   return{
     scope:false, //don't need a new scope
     restrict: 'A', //Attribute type
     link: function (scope, elements, arguments){ 

        if (scope.$last) {
            scope.$emit('content-changed');
            console.log('page Is Ready!');
        }
     }   
   }
})

Finally, your markup (note the change from isLoaded to is-loaded....this is probably the reason you weren't seeing the notification).

最后,你的标记(注意从isLoaded到is-loaded的变化....这可能是你没有看到通知的原因)。

<div ng-init="initGetRequestMain()" swiper>

   <div ng-repeat="slide in data.slides" is-loaded>
       <div class="swiper-slide" style="{{slide.background}}" 
                   ng-click="swipers()"></div>
   </div>
</div>

As mentioned, most jQuery plugins that do carousel functionality don't handle changes to the DOM (i.e new elements) very well. Even with both options, you may see unexpected things if you change your model after the ng-repeat is first rendered. However, if your model is static, this should work for you. If your model changes, then you might want to search for a more "angular" carousel directive.

如上所述,大多数执行轮播功能的jQuery插件都不能很好地处理对DOM(即新元素)的更改。即使有这两个选项,如果在首次渲染ng-repeat后更改模型,也可能会看到意外情况。但是,如果您的模型是静态的,这应该适合您。如果您的模型发生变化,那么您可能希望搜索更“有角度”的轮播指令。

#2


3  

There's an even easier way you can use that's baked into Swiper.js. When you're initializing the swiper, just set the observer property to true. This will make Swiper watch for changes to your slides, so you won't have to worry about whether it runs before all the slides are added through ng-repeat.

你可以使用更简单的方法将其融入到Swiper.js中。初始化swiper时,只需将observer属性设置为true即可。这将使Swiper监视幻灯片的更改,因此您无需担心是否在通过ng-repeat添加所有幻灯片之前运行它。

Example:

例:

var galleryTop = new Swiper('.gallery-top', { observer: true });

var galleryTop = new Swiper('。gallery-top',{observer:true});

And from the Swiper documentation on the observer property:

从观察者属性的Swiper文档中:

Set to true to enable Mutation Observer on Swiper and its elements. In this case Swiper will be updated (reinitialized) each time if you change its style (like hide/show) or modify its child elements (like adding/removing slides)

设置为true以在Swiper及其元素上启用Mutation Observer。在这种情况下,如果您更改其样式(如隐藏/显示)或修改其子元素(如添加/删除幻灯片),Swiper将每次更新(重新初始化)

#3


0  

I'm answering to this old question because some guys asked me to do it and because I want to help other developers with this "funny" problem.

我正在回答这个老问题,因为有些人要我这样做,因为我想帮助其他开发人员解决这个“有趣”的问题。

After more than one year, I managed to solve this issue using Promises and Factories.

一年多之后,我设法使用Promises和Factories解决了这个问题。

Essentially, I made a Factory, ItemFactory ensuring to get all the items needed (in this case slides), then, I made another Factory, SwiperFactory, which uses a Promise to trigger that funny guy, swipers(). I called all those factory methods into a MainController, which, after getting the slides and triggering the script, does $scope.apply(); to reflect the changes in the main view.

基本上,我创建了一个Factory,ItemFactory确保获得所需的所有项目(在本例中为幻灯片),然后,我创建了另一个Factory,SwiperFactory,它使用Promise来触发那个有趣的家伙,swipers()。我将所有这些工厂方法调用到MainController中,在获取幻灯片并触发脚本之后,它会执行$ scope.apply();反映主视图中的更改。

Main.html:

main.html中:

//MainController known as mainCtrl
<div ng-repeat="slide in mainCtrl.slides">
  <div class="swiper-slide" ng-click="mainCtrl.swipers()"></div>
</div>

MainController.js:

MainController.js:

angular
.module('myApp')
.controller('MainController', MainController);

function MainController($scope, ItemFactory, SwiperFactory) {

   var vm    = this;
   //Initializing slides as an empty array
   vm.slides = [];

   /** Gets called as soon as the Controller is called **/
   getData('main.json');

   function getData(path){
      ItemFactory.getItems(path).then( function(response){

        //If response is not empty
        if (response) {
           //Assigning fetched items to vm.items
           vm.slides = response.data;
        }

        //Calling triggerSwiper after we ensured fetching item data
        SwiperFactory.triggerSwiper();

        //Callig $scope.$apply() to reflect correctly the changes in the view
        $scope.$apply();
      })
   };

}

ItemFactory.js:

ItemFactory.js:

angular
.module('myApp')
.factory('ItemFactory', function ($http) {

    /** Using this referring as ItemFactory Controller **/
    var vm     = this;
    vm.API_URL = 'http://localhost/';

    /** Gets items from API based on jsonPath **/
    function getItems(jsonPath) {
        return $http.get(vm.API_URL + jsonPath
        ).then(function (response) {
            return (response);
        });
    }

    //Exposing getItems method
    return {
        getItems : getItems
    }
});

SwiperFactory.js:

SwiperFactory.js:

angular
.module('myApp')
.factory('SwiperFactory', function () {

    /** Using this referring as SwiperFactory Controller **/
    var vm     = this;

    /** Triggers Swiper Script 
     *  Resolving only after swiper has been triggered **/
    function triggerSwiper(){
      return new Promise( function(resolve, reject){
        resolve({
            $(document).ready(function (){
                var swiper = new Swiper('.swiper-container',{
                direction           : 'horizontal',
                pagination          : '.swiper-pagination',
                paginationClickable : true
            })
        });

      }, function(error){
         console.log('Error while triggering Swiper');
      })

    }

    //Exposing triggerSwiper method
    return {
        triggerSwiper : triggerSwiper
    }
});
  • $scope.$apply(); can be removed, it is not really essential
  • $范围$适用()。可以删除,这不是必不可少的
  • In general, this kind of approach works well with asynchronous tasks
  • 通常,这种方法适用于异步任务

The code above does the work, but, please, pay attention to what I'm saying below

上面的代码完成了工作,但请注意我在下面说的内容

Try avoiding using jQuery libraries such as iDangero.us Swiper in an AngularJS project. Instead, look for an Angular library which makes the same work. If back then I was skilled like I'm right now, I would have never used it, expecially for the $(document).ready stuff. Instead, study Promises or AngularJS way of doing that. I was totally new to AngularJS and Web Development in general, so I took wrong decisions.

尝试避免在AngularJS项目中使用jQuery库,例如iDangero.us Swiper。相反,寻找一个Angular库来做同样的工作。如果回来的话我就像现在这样熟练,我从来没有用过它,尤其是$(document).ready的东西。相反,研究Promises或AngularJS这样做的方式。我对AngularJS和Web Development一般都是新手,所以我做出了错误的决定。

I hope I've been helpful.

我希望我一直很有帮助。