jQuery: Rotating elements through a set of classes

2019-05-11 12:33发布

问题:

toggleClass is great if I just have two presentations styles I want to use, but a lot of times I've got more than two states that I want to use.

For example, if I want to rotate through three classes:

if ($(this).hasClass("A"))
   $(this).removeClass("A").addClass("B");
else if ($(this).hasClass("B"))
   $(this).removeClass("B").addClass("C");
else if ($(this).hasClass("C"))
   $(this).removeClass("C").addClass("A");

I'd like to have a simple method that did this for me, something like:

$(this).rotateClass('A', 'B', 'C');

Is there an existing jQuery method or plugin that does this?

回答1:

Here's my attempt at it.

  $.fn.rotate= function( classes ) {
      $element = this;  // element the rotate is being applied to
      for( var i = 0; i <  classes.length; i++ ){   // look through the classes array
          if( $element.hasClass( classes[i] ) ){  // check if current element has the class specified in the array 
              $element.removeClass( classes[i] );  // if it does then remove the class 
              $element.addClass( classes[ ++i % classes.length ] );  
              return $element; // return the current element so other functions can be applied to it. 
           }
      }
  }

You would use it by calling .rotate on the element and passing in an array of classes to rotate.

Here's an interactive demo. I just added the animate to show that you can apply other jQuery functions to the element after the rotate is finished.

 $('#test').rotate(['A','B','C']); 

The reason we use the modulus operator is, if i > the length of the array then we want the correct index. E.g array of classes is ['A','B','C'], if the current class on our element is 'C' then the index of the current class is 2 (arrays are 0 index based), then if we increment 2 + 1 = 3 to get the class we want to rotate it to but we don't have any class at index 3 hence 3 % 3  = 0 which is the index of 'A', that's what we want, hence the modulus will get us the correct index every time. 



回答2:

Here's what I use:

$.fn.rotateClass = function(classes) {
    var $set = this.each(function () {
        var $this = $(this);
        var index = (($this.data("currentClassIndex")) ? $this.data("currentClassIndex") : 0) % classes.length;

        $this.removeClass(classes.join(" "))
             .addClass(classes[index])
             .data("currentClassIndex", ++index);
    });

    return $set;
}

No for loops, no hasClass calls, compatible with sets of elements, very compact and very performant.

Usage:

$("#randomDiv").rotateClass(["classA", "classB", "classC", "classD"]);


回答3:

This plugin code will rotate between the passed classes (however many there are (2-n). If none of the passed classes are found on the object, then the first class is set.

$.fn.rotateClass(/* pass multiple class names here */) {
    for (var i = 0; i < arguments.length; i++) {
        if (this.hasClass(arguments[i])) {
            this.removeClass(arguments[i]));
            i++;
            if (i >= arguments.length) {
                i = 0;
            }
            this.addClass(arguments[i]);
            return(this);
        }
    }
    // none found so set the first class
    if (arguments.length > 0) {
        this.addClass(arguments[0]);
    }
    return(this);
}

This version of the code assumes that you are applying the same classes to all the DOM objects in the jQuery object and they all have the same state. It could be extended to treat each DOM object in the jQuery object separately and rotate each ones separately if that was required.



回答4:

Put them in an Array:

var arr = ['A', 'B', 'C'];
var i = 0;

Then in the handler:

i = ++i % arr.length;

$(this).toggleClass( this.className + ' ' + arr[ i ] );

The use of this.className assumes that there's not other classes applied to this element.

JSFIDDLE DEMO


If there may be other classes, use this:

$(this).removeClass( arr[ i ] );
i = ++i % arr.length;
$(this).addClass( arr[ i ] );

JSFIDDLE DEMO


If there may be other elements, and each need their own state, you could use a closure to associate a unique i with each element:

$('div').each(function() {
    var i = 0;

    $(this).click(function() {
        $(this).removeClass( arr[ i ] );
        i = ++i % arr.length;
        $(this).addClass( arr[ i ] );
    });
});

JSFIDDLE DEMO



回答5:

You can still use toggleClass and pass it a function instead:

$(this).toggleClass(function() {
    if ($(this).hasClass('red')) {
      return 'green red';
    }

    if ($(this).hasClass('green')) {
      return 'blue green';
    }

    if ($(this).hasClass('blue')) {
      return 'red blue';
    }
});

Not the easiest code to maintain though, so it would probably be better to wrap it in a plugin.

http://jsbin.com/inaduy/edit#javascript,html,live

And now made in to a very basic plugin (that needs a bit of validation). You just pass an array of objects containing pairs of classes to swap.

var classArray = [{current: 'red', next:'green'}, {current:'green', next:'blue'}, {current:'blue', next:'red'}];

$('#hello').click(function() {
  $(this).toggleRotate(classArray);
});

(function($) {
  $.fn.toggleRotate = function(classes) {

    return $(this).toggleClass(function() {
      var l = classes.length;
      for (var i = 0; i < l; i++) {
        if ($(this).hasClass(classes[i].current)) {
          return classes[i].current + ' ' + classes[i].next;
        }        
      }
      // If it makes it here return the first current value
      return classes[0].current;
    });
  };
})(jQuery);

http://jsbin.com/inaduy/2/edit



回答6:

My current solution:

$.fn.mapClasses = function(mapping){
  return this.each(function(){
    this.className = this.className.split(/\s+/).map(function(klass){
      return mapping[klass] || klass
    }).join(' ');
  });
};
$.fn.rotateClasses(){
  var mapping = {};
  var prev = arguments[0];
  for (var i = arguments.length - 1; i >= 0; i--){
    mapping[ arguments[i] ] = prev;
    prev = arguments[i];
  }
  return this.mapClasses(mapping);
};


标签: jquery class