mirror of
https://github.com/VTECRM/vtenext.git
synced 2026-02-26 16:18:47 +00:00
569 lines
18 KiB
JavaScript
569 lines
18 KiB
JavaScript
/*************************************
|
|
* SPDX-FileCopyrightText: 2009-2020 Vtenext S.r.l. <info@vtenext.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
************************************/
|
|
(function ($) {
|
|
|
|
var methods = {
|
|
|
|
init : function(options) {
|
|
var defaults = {
|
|
duration: 200, // ms
|
|
dist: -100, // zoom scale TODO: make this more intuitive as an option
|
|
shift: 0, // spacing for center image
|
|
padding: 0, // Padding between non center items
|
|
fullWidth: false, // Change to full width styles
|
|
indicators: false, // Toggle indicators
|
|
noWrap: false, // Don't wrap around and cycle through items.
|
|
onCycleTo: null // Callback for when a new slide is cycled to.
|
|
};
|
|
options = $.extend(defaults, options);
|
|
var namespace = Materialize.objectSelectorString($(this));
|
|
|
|
return this.each(function(i) {
|
|
|
|
var images, item_width, item_height, offset, center, pressed, dim, count,
|
|
reference, referenceY, amplitude, target, velocity, scrolling,
|
|
xform, frame, timestamp, ticker, dragged, vertical_dragged;
|
|
var $indicators = $('<ul class="indicators"></ul>');
|
|
var scrollingTimeout = null;
|
|
var oneTimeCallback = null;
|
|
|
|
|
|
// Initialize
|
|
var view = $(this);
|
|
var hasMultipleSlides = view.find('.carousel-item').length > 1;
|
|
var showIndicators = (view.attr('data-indicators') || options.indicators) && hasMultipleSlides;
|
|
var noWrap = (view.attr('data-no-wrap') || options.noWrap) || !hasMultipleSlides;
|
|
var uniqueNamespace = view.attr('data-namespace') || namespace+i;
|
|
view.attr('data-namespace', uniqueNamespace);
|
|
|
|
|
|
// Options
|
|
var setCarouselHeight = function(imageOnly) {
|
|
var firstSlide = view.find('.carousel-item.active').length ? view.find('.carousel-item.active').first() : view.find('.carousel-item').first();
|
|
var firstImage = firstSlide.find('img').first();
|
|
if (firstImage.length) {
|
|
if (firstImage[0].complete) {
|
|
// If image won't trigger the load event
|
|
var imageHeight = firstImage.height();
|
|
if (imageHeight > 0) {
|
|
view.css('height', firstImage.height());
|
|
} else {
|
|
// If image still has no height, use the natural dimensions to calculate
|
|
var naturalWidth = firstImage[0].naturalWidth;
|
|
var naturalHeight = firstImage[0].naturalHeight;
|
|
var adjustedHeight = (view.width() / naturalWidth) * naturalHeight;
|
|
view.css('height', adjustedHeight);
|
|
}
|
|
} else {
|
|
// Get height when image is loaded normally
|
|
firstImage.on('load', function(){
|
|
view.css('height', $(this).height());
|
|
});
|
|
}
|
|
} else if (!imageOnly) {
|
|
var slideHeight = firstSlide.height();
|
|
view.css('height', slideHeight);
|
|
}
|
|
};
|
|
|
|
if (options.fullWidth) {
|
|
options.dist = 0;
|
|
setCarouselHeight();
|
|
|
|
// Offset fixed items when indicators.
|
|
if (showIndicators) {
|
|
view.find('.carousel-fixed-item').addClass('with-indicators');
|
|
}
|
|
}
|
|
|
|
|
|
// Don't double initialize.
|
|
if (view.hasClass('initialized')) {
|
|
// Recalculate variables
|
|
$(window).trigger('resize');
|
|
|
|
// Redraw carousel.
|
|
view.trigger('carouselNext', [0.000001]);
|
|
return true;
|
|
}
|
|
|
|
|
|
view.addClass('initialized');
|
|
pressed = false;
|
|
offset = target = 0;
|
|
images = [];
|
|
item_width = view.find('.carousel-item').first().innerWidth();
|
|
item_height = view.find('.carousel-item').first().innerHeight();
|
|
dim = item_width * 2 + options.padding;
|
|
|
|
view.find('.carousel-item').each(function (i) {
|
|
images.push($(this)[0]);
|
|
if (showIndicators) {
|
|
var $indicator = $('<li class="indicator-item"></li>');
|
|
|
|
// Add active to first by default.
|
|
if (i === 0) {
|
|
$indicator.addClass('active');
|
|
}
|
|
|
|
// Handle clicks on indicators.
|
|
$indicator.click(function (e) {
|
|
e.stopPropagation();
|
|
|
|
var index = $(this).index();
|
|
cycleTo(index);
|
|
});
|
|
$indicators.append($indicator);
|
|
}
|
|
});
|
|
|
|
if (showIndicators) {
|
|
view.append($indicators);
|
|
}
|
|
count = images.length;
|
|
|
|
|
|
function setupEvents() {
|
|
if (typeof window.ontouchstart !== 'undefined') {
|
|
view.on('touchstart.carousel', tap);
|
|
view.on('touchmove.carousel', drag);
|
|
view.on('touchend.carousel', release);
|
|
}
|
|
view.on('mousedown.carousel', tap);
|
|
view.on('mousemove.carousel', drag);
|
|
view.on('mouseup.carousel', release);
|
|
view.on('mouseleave.carousel', release);
|
|
view.on('click.carousel', click);
|
|
}
|
|
|
|
function xpos(e) {
|
|
// touch event
|
|
if (e.targetTouches && (e.targetTouches.length >= 1)) {
|
|
return e.targetTouches[0].clientX;
|
|
}
|
|
|
|
// mouse event
|
|
return e.clientX;
|
|
}
|
|
|
|
function ypos(e) {
|
|
// touch event
|
|
if (e.targetTouches && (e.targetTouches.length >= 1)) {
|
|
return e.targetTouches[0].clientY;
|
|
}
|
|
|
|
// mouse event
|
|
return e.clientY;
|
|
}
|
|
|
|
function wrap(x) {
|
|
return (x >= count) ? (x % count) : (x < 0) ? wrap(count + (x % count)) : x;
|
|
}
|
|
|
|
function scroll(x) {
|
|
// Track scrolling state
|
|
scrolling = true;
|
|
if (!view.hasClass('scrolling')) {
|
|
view.addClass('scrolling');
|
|
}
|
|
if (scrollingTimeout != null) {
|
|
window.clearTimeout(scrollingTimeout);
|
|
}
|
|
scrollingTimeout = window.setTimeout(function() {
|
|
scrolling = false;
|
|
view.removeClass('scrolling');
|
|
}, options.duration);
|
|
|
|
// Start actual scroll
|
|
var i, half, delta, dir, tween, el, alignment, xTranslation;
|
|
var lastCenter = center;
|
|
|
|
offset = (typeof x === 'number') ? x : offset;
|
|
center = Math.floor((offset + dim / 2) / dim);
|
|
delta = offset - center * dim;
|
|
dir = (delta < 0) ? 1 : -1;
|
|
tween = -dir * delta * 2 / dim;
|
|
half = count >> 1;
|
|
|
|
if (!options.fullWidth) {
|
|
alignment = 'translateX(' + (view[0].clientWidth - item_width) / 2 + 'px) ';
|
|
alignment += 'translateY(' + (view[0].clientHeight - item_height) / 2 + 'px)';
|
|
} else {
|
|
alignment = 'translateX(0)';
|
|
}
|
|
|
|
// Set indicator active
|
|
if (showIndicators) {
|
|
var diff = (center % count);
|
|
var activeIndicator = $indicators.find('.indicator-item.active');
|
|
if (activeIndicator.index() !== diff) {
|
|
activeIndicator.removeClass('active');
|
|
$indicators.find('.indicator-item').eq(diff).addClass('active');
|
|
}
|
|
}
|
|
|
|
// center
|
|
// Don't show wrapped items.
|
|
if (!noWrap || (center >= 0 && center < count)) {
|
|
el = images[wrap(center)];
|
|
|
|
// Add active class to center item.
|
|
if (!$(el).hasClass('active')) {
|
|
view.find('.carousel-item').removeClass('active');
|
|
$(el).addClass('active');
|
|
}
|
|
el.style[xform] = alignment +
|
|
' translateX(' + (-delta / 2) + 'px)' +
|
|
' translateX(' + (dir * options.shift * tween * i) + 'px)' +
|
|
' translateZ(' + (options.dist * tween) + 'px)';
|
|
el.style.zIndex = 0;
|
|
if (options.fullWidth) { tweenedOpacity = 1; }
|
|
else { tweenedOpacity = 1 - 0.2 * tween; }
|
|
el.style.opacity = tweenedOpacity;
|
|
el.style.display = 'block';
|
|
}
|
|
|
|
for (i = 1; i <= half; ++i) {
|
|
// right side
|
|
if (options.fullWidth) {
|
|
zTranslation = options.dist;
|
|
tweenedOpacity = (i === half && delta < 0) ? 1 - tween : 1;
|
|
} else {
|
|
zTranslation = options.dist * (i * 2 + tween * dir);
|
|
tweenedOpacity = 1 - 0.2 * (i * 2 + tween * dir);
|
|
}
|
|
// Don't show wrapped items.
|
|
if (!noWrap || center + i < count) {
|
|
el = images[wrap(center + i)];
|
|
el.style[xform] = alignment +
|
|
' translateX(' + (options.shift + (dim * i - delta) / 2) + 'px)' +
|
|
' translateZ(' + zTranslation + 'px)';
|
|
el.style.zIndex = -i;
|
|
el.style.opacity = tweenedOpacity;
|
|
el.style.display = 'block';
|
|
}
|
|
|
|
|
|
// left side
|
|
if (options.fullWidth) {
|
|
zTranslation = options.dist;
|
|
tweenedOpacity = (i === half && delta > 0) ? 1 - tween : 1;
|
|
} else {
|
|
zTranslation = options.dist * (i * 2 - tween * dir);
|
|
tweenedOpacity = 1 - 0.2 * (i * 2 - tween * dir);
|
|
}
|
|
// Don't show wrapped items.
|
|
if (!noWrap || center - i >= 0) {
|
|
el = images[wrap(center - i)];
|
|
el.style[xform] = alignment +
|
|
' translateX(' + (-options.shift + (-dim * i - delta) / 2) + 'px)' +
|
|
' translateZ(' + zTranslation + 'px)';
|
|
el.style.zIndex = -i;
|
|
el.style.opacity = tweenedOpacity;
|
|
el.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// center
|
|
// Don't show wrapped items.
|
|
if (!noWrap || (center >= 0 && center < count)) {
|
|
el = images[wrap(center)];
|
|
el.style[xform] = alignment +
|
|
' translateX(' + (-delta / 2) + 'px)' +
|
|
' translateX(' + (dir * options.shift * tween) + 'px)' +
|
|
' translateZ(' + (options.dist * tween) + 'px)';
|
|
el.style.zIndex = 0;
|
|
if (options.fullWidth) { tweenedOpacity = 1; }
|
|
else { tweenedOpacity = 1 - 0.2 * tween; }
|
|
el.style.opacity = tweenedOpacity;
|
|
el.style.display = 'block';
|
|
}
|
|
|
|
// onCycleTo callback
|
|
if (lastCenter !== center &&
|
|
typeof(options.onCycleTo) === "function") {
|
|
var $curr_item = view.find('.carousel-item').eq(wrap(center));
|
|
options.onCycleTo.call(this, $curr_item, dragged);
|
|
}
|
|
|
|
// One time callback
|
|
if (typeof(oneTimeCallback) === "function") {
|
|
oneTimeCallback.call(this, $curr_item, dragged);
|
|
oneTimeCallback = null;
|
|
}
|
|
}
|
|
|
|
function track() {
|
|
var now, elapsed, delta, v;
|
|
|
|
now = Date.now();
|
|
elapsed = now - timestamp;
|
|
timestamp = now;
|
|
delta = offset - frame;
|
|
frame = offset;
|
|
|
|
v = 1000 * delta / (1 + elapsed);
|
|
velocity = 0.8 * v + 0.2 * velocity;
|
|
}
|
|
|
|
function autoScroll() {
|
|
var elapsed, delta;
|
|
|
|
if (amplitude) {
|
|
elapsed = Date.now() - timestamp;
|
|
delta = amplitude * Math.exp(-elapsed / options.duration);
|
|
if (delta > 2 || delta < -2) {
|
|
scroll(target - delta);
|
|
requestAnimationFrame(autoScroll);
|
|
} else {
|
|
scroll(target);
|
|
}
|
|
}
|
|
}
|
|
|
|
function click(e) {
|
|
// Disable clicks if carousel was dragged.
|
|
if (dragged) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
|
|
} else if (!options.fullWidth) {
|
|
var clickedIndex = $(e.target).closest('.carousel-item').index();
|
|
var diff = wrap(center) - clickedIndex;
|
|
|
|
// Disable clicks if carousel was shifted by click
|
|
if (diff !== 0) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
cycleTo(clickedIndex);
|
|
}
|
|
}
|
|
|
|
function cycleTo(n) {
|
|
var diff = (center % count) - n;
|
|
|
|
// Account for wraparound.
|
|
if (!noWrap) {
|
|
if (diff < 0) {
|
|
if (Math.abs(diff + count) < Math.abs(diff)) { diff += count; }
|
|
|
|
} else if (diff > 0) {
|
|
if (Math.abs(diff - count) < diff) { diff -= count; }
|
|
}
|
|
}
|
|
|
|
// Call prev or next accordingly.
|
|
if (diff < 0) {
|
|
view.trigger('carouselNext', [Math.abs(diff)]);
|
|
|
|
} else if (diff > 0) {
|
|
view.trigger('carouselPrev', [diff]);
|
|
}
|
|
}
|
|
|
|
function tap(e) {
|
|
// Fixes firefox draggable image bug
|
|
if (e.type === 'mousedown' && $(e.target).is('img')) {
|
|
e.preventDefault();
|
|
}
|
|
pressed = true;
|
|
dragged = false;
|
|
vertical_dragged = false;
|
|
reference = xpos(e);
|
|
referenceY = ypos(e);
|
|
|
|
velocity = amplitude = 0;
|
|
frame = offset;
|
|
timestamp = Date.now();
|
|
clearInterval(ticker);
|
|
ticker = setInterval(track, 100);
|
|
}
|
|
|
|
function drag(e) {
|
|
var x, delta, deltaY;
|
|
if (pressed) {
|
|
x = xpos(e);
|
|
y = ypos(e);
|
|
delta = reference - x;
|
|
deltaY = Math.abs(referenceY - y);
|
|
if (deltaY < 30 && !vertical_dragged) {
|
|
// If vertical scrolling don't allow dragging.
|
|
if (delta > 2 || delta < -2) {
|
|
dragged = true;
|
|
reference = x;
|
|
scroll(offset + delta);
|
|
}
|
|
|
|
} else if (dragged) {
|
|
// If dragging don't allow vertical scroll.
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
|
|
} else {
|
|
// Vertical scrolling.
|
|
vertical_dragged = true;
|
|
}
|
|
}
|
|
|
|
if (dragged) {
|
|
// If dragging don't allow vertical scroll.
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function release(e) {
|
|
if (pressed) {
|
|
pressed = false;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
clearInterval(ticker);
|
|
target = offset;
|
|
if (velocity > 10 || velocity < -10) {
|
|
amplitude = 0.9 * velocity;
|
|
target = offset + amplitude;
|
|
}
|
|
target = Math.round(target / dim) * dim;
|
|
|
|
// No wrap of items.
|
|
if (noWrap) {
|
|
if (target >= dim * (count - 1)) {
|
|
target = dim * (count - 1);
|
|
} else if (target < 0) {
|
|
target = 0;
|
|
}
|
|
}
|
|
amplitude = target - offset;
|
|
timestamp = Date.now();
|
|
requestAnimationFrame(autoScroll);
|
|
|
|
if (dragged) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
xform = 'transform';
|
|
['webkit', 'Moz', 'O', 'ms'].every(function (prefix) {
|
|
var e = prefix + 'Transform';
|
|
if (typeof document.body.style[e] !== 'undefined') {
|
|
xform = e;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
|
|
var throttledResize = Materialize.throttle(function() {
|
|
if (options.fullWidth) {
|
|
item_width = view.find('.carousel-item').first().innerWidth();
|
|
var imageHeight = view.find('.carousel-item.active').height();
|
|
dim = item_width * 2 + options.padding;
|
|
offset = center * 2 * item_width;
|
|
target = offset;
|
|
setCarouselHeight(true);
|
|
} else {
|
|
scroll();
|
|
}
|
|
}, 200);
|
|
$(window)
|
|
.off('resize.carousel-'+uniqueNamespace)
|
|
.on('resize.carousel-'+uniqueNamespace, throttledResize);
|
|
|
|
setupEvents();
|
|
scroll(offset);
|
|
|
|
$(this).on('carouselNext', function(e, n, callback) {
|
|
if (n === undefined) {
|
|
n = 1;
|
|
}
|
|
if (typeof(callback) === "function") {
|
|
oneTimeCallback = callback;
|
|
}
|
|
|
|
target = (dim * Math.round(offset / dim)) + (dim * n);
|
|
if (offset !== target) {
|
|
amplitude = target - offset;
|
|
timestamp = Date.now();
|
|
requestAnimationFrame(autoScroll);
|
|
}
|
|
});
|
|
|
|
$(this).on('carouselPrev', function(e, n, callback) {
|
|
if (n === undefined) {
|
|
n = 1;
|
|
}
|
|
if (typeof(callback) === "function") {
|
|
oneTimeCallback = callback;
|
|
}
|
|
|
|
target = (dim * Math.round(offset / dim)) - (dim * n);
|
|
if (offset !== target) {
|
|
amplitude = target - offset;
|
|
timestamp = Date.now();
|
|
requestAnimationFrame(autoScroll);
|
|
}
|
|
});
|
|
|
|
$(this).on('carouselSet', function(e, n, callback) {
|
|
if (n === undefined) {
|
|
n = 0;
|
|
}
|
|
if (typeof(callback) === "function") {
|
|
oneTimeCallback = callback;
|
|
}
|
|
|
|
cycleTo(n);
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
},
|
|
next : function(n, callback) {
|
|
$(this).trigger('carouselNext', [n, callback]);
|
|
},
|
|
prev : function(n, callback) {
|
|
$(this).trigger('carouselPrev', [n, callback]);
|
|
},
|
|
set : function(n, callback) {
|
|
$(this).trigger('carouselSet', [n, callback]);
|
|
},
|
|
destroy : function() {
|
|
var uniqueNamespace = $(this).attr('data-namespace');
|
|
$(this).removeAttr('data-namespace');
|
|
$(this).removeClass('initialized');
|
|
$(this).find('.indicators').remove();
|
|
|
|
// Remove event handlers
|
|
$(this).off('carouselNext carouselPrev carouselSet');
|
|
$(window).off('resize.carousel-'+uniqueNamespace);
|
|
if (typeof window.ontouchstart !== 'undefined') {
|
|
$(this).off('touchstart.carousel touchmove.carousel touchend.carousel');
|
|
}
|
|
$(this).off('mousedown.carousel mousemove.carousel mouseup.carousel mouseleave.carousel click.carousel');
|
|
}
|
|
};
|
|
|
|
|
|
$.fn.carousel = function(methodOrOptions) {
|
|
if ( methods[methodOrOptions] ) {
|
|
return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
|
|
} else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
|
|
// Default to "init"
|
|
return methods.init.apply( this, arguments );
|
|
} else {
|
|
$.error( 'Method ' + methodOrOptions + ' does not exist on jQuery.carousel' );
|
|
}
|
|
}; // Plugin end
|
|
}( jQuery )); |