/* ========================================================================= *\ vmcarousel plugin by vedmant \* ========================================================================= */ (function ($, window, document, undefined) { "use strict"; var pluginname = 'vmcarousel'; /** * defaults * * @type {} */ var defaults = { delay: 4000, speed: 500, autoplay: true, items_to_show: 0, // 0 for auto calc min_items_to_show: 2, items_to_slide: 1, dont_cut: true, centered: false, start_item: 0, start_item_centered: false, infinite: false, changed_slide: $.noop() }; /** * plugin constructor * * @param element * @param options * @constructor */ function plugin(element, options) { this._name = pluginname; this.element = element; this.$element = $(element); var data_options = parse_data_options(this.$element.data('options')); this.options = $.extend({}, defaults, options); this.options = $.extend({}, this.options, data_options); this.init(); } /** * parse data-options attribute options * * @param data_options_raw * @returns {array} */ function parse_data_options(data_options_raw) { if(data_options_raw === undefined) return []; var options = []; data_options_raw.split(';').foreach(function(el){ var pair = el.split(':'); if(pair.length == 2) options[pair[0].trim()] = pair[1].trim(); }); return options; } /** * plugin functions */ plugin.prototype = { /** * plugin init */ init: function () { var that = this; // add class this.$element.addclass('vmcarousel'); // wrap this.$viewport = this.$element.wrap('
').parent(); this.$container = this.$viewport.wrap('
').parent(); // some initial procedures with slides this.init_slides(); // items vars this.$orig_items = this.$element.find('>li'); this.$items = this.$orig_items; this.orig_items_count = this.$orig_items.length; this.items_count = this.$items.length; this.orig_item_width = this.$items.outerwidth(true); this.item_width = this.orig_item_width; // other vars this.current_position = 0; // init functions this.calc_variables(); this.init_infinite(this.options.start_item); this.init_controls(); this.update_state(); // reorder slides to make start item at the center if(this.options.start_item_centered) this.reorder_to_center(this.options.start_item); // initial set slide if( ! this.options.infinite) this.set_slide(this.options.start_item); else this.set_active_infinite(this.options.start_item); // start timer if (this.options.autoplay) this.start_timer(); // window resize event $(window).resize(function () { that.resize() }); }, /** * calculate all needed variables */ calc_variables: function() { this.viewport_width = this.$viewport.width(); // calc items to show this.items_to_show = this.options.items_to_show; if( ! this.options.items_to_show || (this.orig_item_width * this.items_to_show) > this.viewport_width) { this.items_to_show = math.floor(this.viewport_width / this.orig_item_width); } // set odd number for centered type for not to cut items if(this.options.centered && this.options.dont_cut) { this.items_to_show = this.items_to_show % 2 ? this.items_to_show : this.items_to_show - 1; } // min items to show if(this.items_to_show < this.options.min_items_to_show) this.items_to_show = this.options.min_items_to_show; // calc item width for centered or dont_cut if(this.options.centered || this.options.dont_cut) { this.item_width = math.floor(this.viewport_width / this.items_to_show); if(this.item_width < this.orig_item_width) this.item_width = this.orig_item_width; this.$items.width(this.item_width); this.full_items_width = this.item_width * this.items_count; this.$element.css({width: this.full_items_width + 'px'}); } // calc items to slide this.items_to_slide = this.options.items_to_slide; if( ! this.options.items_to_slide) this.items_to_slide = math.floor(this.viewport_width / this.item_width); if(this.items_to_slide > this.items_to_show) this.items_to_slide = this.items_to_show; if(this.items_to_slide <= 0) this.items_to_slide = 1; this.hide_controls = this.items_count <= this.items_to_show; this.infinite_initial_margin = - this.item_width; if(this.items_to_show % 2 == 0) this.infinite_initial_margin += this.item_width / 2; }, /** * update carousel state (clases, so on) */ update_state: function() { this.$element.css({transition: 'transform ' + this.options.speed / 1000 + 's'}); if(this.hide_controls) this.$container.addclass('hide-controls'); else this.$container.removeclass('hide-controls'); }, /** * set slides properties */ init_slides: function() { this.$element.find('>li').each(function(i){ $(this).attr('data-slide', i); }); }, /** * init controls */ init_controls: function() { var that = this; // controls this.$btn_left = this.$container.append('').find('.vmc-arrow-left'); this.$btn_right = this.$container.append('').find('.vmc-arrow-right'); // bind controls this.$btn_left.click(function (e) { e.preventdefault(); that.slide_relative(-1); }); this.$btn_right.click(function (e) { e.preventdefault(); that.slide_relative(1); }); }, /** * reorder slider to place item at the center * * @param position */ reorder_to_center: function(position) { // dont reorder if 2 or less items if(this.orig_items_count < 3) return; // calc shift times and direction var shift_count = math.floor(this.orig_items_count / 2) - position; var dir = shift_count > 0 ? -1 : 1; // shift items shift_count = math.abs(shift_count); for(var i = 0; i < shift_count; i++) this.switch_slides(dir); }, /** * move to exact slide * * @param slide */ set_slide: function(slide) { var position = this.$element.find('>[data-slide="'+slide+'"]').index(); this.slide_relative(position); }, /** * slide n items forth or back * * @param offset */ slide_relative: function (offset) { if(this.options.centered && this.options.infinite) this.slide_relative_centered_infinite(offset); else if(this.options.centered) this.slide_relative_centered(offset); else this.slide_relative_left(offset); }, /** * slide n items forth or back for left mode * * @param offset */ slide_relative_left: function (offset) { var new_position = this.current_position + (offset * this.items_to_slide); // if now is ribbon tail on go back reverse to slide_count step if (this.current_position == this.items_count && offset < 0) { new_position = (math.floor(this.items_count / this.items_to_slide) + offset) * this.items_to_slide; // show ribbon tail (last slide to right border) } else if (new_position < 0 || (this.items_to_slide > (this.items_count - new_position) && new_position < this.items_count)) { new_position = this.items_count - this.items_to_show; // scroll to beggining } else if (new_position > (this.items_count - this.items_to_show)) { new_position = 0; } var margin_left = - this.item_width * new_position; // animate slide this.animate_slide(margin_left); this.change_slide(new_position, new_position); }, /** * slide n items forth or back for centered mode * * @param offset */ slide_relative_centered: function (offset) { var new_position = this.current_position + (offset * this.items_to_slide); if (new_position < 0) { new_position = this.items_count - 1; // scroll to beggining } else if (new_position >= this.items_count) { new_position = 0; } var margin_left = this.viewport_width / 2 - (this.item_width * (new_position + 1) - this.item_width / 2); // animate slide this.animate_slide(margin_left); var new_active_slide = this.$items.eq(new_position).attr('data-slide'); this.change_slide(new_position, new_active_slide); }, /** * init infinite carousel feature */ init_infinite: function (start_item) { if( ! this.options.infinite) return; this.make_clones(); this.calc_variables(); this.$element.css("margin-left", this.infinite_initial_margin + "px"); }, /** * make clones for infinite carousel */ make_clones: function () { var times = 1; if(this.items_count < this.items_to_show) times = math.ceil(this.items_to_show / this.items_count); for(var i = 0; i < times; i++) { this.$element.prepend(this.$orig_items.clone().addclass('vmc-clone')); } this.$items = this.$element.find('>li'); this.items_count = this.$items.length; }, /** * slide n items forth or back for centered mode with infinite mode * * @param offset */ slide_relative_centered_infinite: function (offset) { var that = this; // only one item to slide offset = offset < 0 ? -1 : 1; var margin_left = this.infinite_initial_margin - this.item_width * offset; //if(this.items_to_show % 2 == 0) margin_left += this.item_width / 2; var new_position = math.ceil(this.items_to_show / 2) + offset; var new_active_slide = this.$items.eq(new_position).attr('data-slide'); this.animate_slide(margin_left, function(e){ that.switch_slides(offset); that.$element.css("margin-left", that.infinite_initial_margin + "px"); }, 'margin'); this.change_slide(new_position, new_active_slide); }, /** * place first slide at the end or last slide before first * * @param dir */ switch_slides: function(dir) { var that = this; // switch last or first item if(dir > 0) { that.$items.last().after(that.$items.first()); } else { that.$items.first().before(that.$items.last()); } // reload elements that.$items = that.$element.find('>li'); }, /** * set first active slide for infinite carousel * */ set_active_infinite: function(position) { var center_position = math.ceil(this.items_to_show / 2); for(var i = 0; i < this.orig_items_count; i++) { this.switch_slides(1); if(this.$items.eq(center_position).attr('data-slide') == position) { this.$items.eq(center_position).addclass('vmc_active'); return true; } } return false; }, /** * change slide * * @param new_position * @param margin_left */ change_slide: function (new_position, new_active_slide) { var that = this; // update current position this.current_position = new_position; // add active class this.$items.removeclass('vmc_active').eq(this.current_position).addclass('vmc_active'); // restart timer if (this.options.autoplay) this.start_timer(); // call callback if (typeof this.options.changed_slide === "function") { this.options.changed_slide.call(this, new_active_slide); } }, /** * slide animation * * @param margin_left */ animate_slide: function (margin_left, complete, type) { var that = this; if(type == undefined) type = 'css3'; if(complete == undefined) complete = $.noop(); if (modernizr.csstransitions && type == 'css3') { this.$element.css("transform", "translate3d(" + margin_left + "px,0px,0px)"); this.$element.one('webkittransitionend otransitionend otransitionend mstransitionend transitionend', complete); } else { this.$element.stop(true).animate({'margin-left': margin_left + 'px'}, this.options.speed, 'swing', complete); } }, /** * resize event */ resize: function () { this.calc_variables(); this.update_state(); // update slider position this.slide_relative(0); }, /** * start timer */ start_timer: function () { var that = this; if (this.timer_id != 0) cleartimeout(this.timer_id); this.timer_id = settimeout(function () { that.slide_relative(1); }, this.options.delay); }, /** * stop timer */ stop_timer: function () { cleartimeout(this.timer_id); this.timer_id = 0; } } // plugin.prototype /** * attach to jquery * * @param options * @returns {*} */ $.fn[pluginname] = function (options) { var args = [].slice.call(arguments, 1); return this.each(function () { if (!$.data(this, 'plugin_' + pluginname)) $.data(this, 'plugin_' + pluginname, new plugin(this, options)); else if ($.isfunction(plugin.prototype[options])) $.data(this, 'plugin_' + pluginname)[options].apply($.data(this, 'plugin_' + pluginname), args); }); } // auto init for tags with data-vmcarousel attribute $('[data-vmcarousel]').vmcarousel(); })(jquery, window, document);