uix-kit
Version:
A free web kits for fast web design and development, compatible with Bootstrap v5.
835 lines (565 loc) • 25.7 kB
JavaScript
/*
*************************************
* <!-- SVG Mask Slider -->
*************************************
*/
import {
UixModuleInstance,
UixGUID,
UixDebounce,
} from '@uixkit/core/_global/js';
import '../scss/_style.scss';
export const SVG_MASK_SLIDER = ( ( module, $, window, document ) => {
if ( window.SVG_MASK_SLIDER === null ) return false;
module.SVG_MASK_SLIDER = module.SVG_MASK_SLIDER || {};
module.SVG_MASK_SLIDER.version = '0.0.5';
module.SVG_MASK_SLIDER.pageLoaded = function() {
let windowWidth = window.innerWidth,
windowHeight = window.innerHeight;
let animDelay = 0;
let animSpeed = 1000;
const $sliderWrapper = $( '.uix-svgMask-slider' );
let svgAnimating = false;
//
sliderInit( false );
function windowUpdate() {
// Check window width has actually changed and it's not just iOS triggering a resize event on scroll
if ( window.innerWidth != windowWidth ) {
// Update the window width for next time
windowWidth = window.innerWidth;
// Do stuff here
sliderInit( true );
}
}
// Add function to the window that should be resized
const debounceFuncWindow = UixDebounce(windowUpdate, 50);
window.removeEventListener('resize', debounceFuncWindow);
window.addEventListener('resize', debounceFuncWindow);
/*
* Initialize slideshow
*
* @param {Boolean} resize - Determine whether the window size changes.
* @return {Void}
*/
function sliderInit( resize ) {
$sliderWrapper.each( function() {
const $this = $( this );
const $items = $this.find( '.uix-svgMask-slider__item' ),
$first = $items.first(),
activated = $this.data( 'activated' );
let nativeItemW,
nativeItemH;
if ( typeof activated === typeof undefined || activated === 0 ) {
//Get parameter configuration from the data-* attribute of HTML
let dataControlsPagination = $this.data( 'controls-pagination' ),
dataControlsArrows = $this.data( 'controls-arrows' ),
dataDraggable = $this.data( 'draggable' ),
dataDraggableCursor = $this.data( 'draggable-cursor' ),
dataCountTotal = $this.data( 'count-total' ),
dataCountCur = $this.data( 'count-now' ),
dataSpeed = $this.data( 'speed' );
if ( typeof dataControlsPagination === typeof undefined ) dataControlsPagination = '.uix-svgMask-slider__pagination';
if ( typeof dataControlsArrows === typeof undefined || dataControlsArrows == false ) dataControlsArrows = '.uix-svgMask-slider__arrows';
if ( typeof dataDraggable === typeof undefined ) dataDraggable = false;
if ( typeof dataDraggableCursor === typeof undefined || dataDraggableCursor == false ) dataDraggableCursor = 'move';
if ( typeof dataCountTotal === typeof undefined ) dataCountTotal = 'p.count em.count';
if ( typeof dataCountCur === typeof undefined ) dataCountCur = 'p.count em.current';
//Autoplay parameters
let dataAuto = $this.data( 'auto' ),
dataTiming = $this.data( 'timing' ),
dataLoop = $this.data( 'loop' );
if ( typeof dataAuto === typeof undefined ) dataAuto = false;
if ( typeof dataTiming === typeof undefined ) dataTiming = 10000;
if ( typeof dataLoop === typeof undefined ) dataLoop = false;
//Autoplay times
let playTimes;
//A function called "timer" once every second (like a digital watch).
$this[0].animatedSlides;
//Get the animation speed
//-------------------------------------
if ( typeof dataSpeed != typeof undefined && dataSpeed != false ) {
animSpeed = dataSpeed;
}
//Get the duration of the animation
//-------------------------------------
animDelay = animSpeed;
//Get timeline elements
//-------------------------------------
const txtTimeline = new TimelineMax({delay: 0});
const txtMaskTimeline = new TimelineMax({delay: 0});
txtTimeline
.to( $items.find( '.uix-svgMask-slider__txt__content' ), 0.1, {opacity: 0})
.to( $items.find( 'svg image' ), 0.2, {scale: 1.1},'-=0.1')
.to( $this.find( '.uix-svgMask-slider__inner' ), 0.9, {ease: Circ.easeOut, scale: 0.85},'-=0.2')
.to( $this.find( '.uix-svgMask-slider__inner' ), 0.75, {ease: Elastic.easeOut.config(4, 1.5), scale: 1} )
.pause();
txtMaskTimeline
.to( $items.find( '.uix-svgMask-slider__txt__mask' ), 0.6, {css: { marginLeft: 0 } })
.to( $items.find( '.uix-svgMask-slider__txt__content' ), 0.1, {opacity:1}, "-=0.1")
.to( $items.find( 'svg image' ), 0.2, {scale: 1},'-=0.1')
.to( $items.find( '.uix-svgMask-slider__txt__mask' ), 0.6, {css: { marginLeft: '-100vw' } });
//Initialize the properties of each Item
//-------------------------------------
$items.each( function( index ) {
const _id = UixGUID.create();
const _item = $( this );
_item.find( 'clipPath' ).attr( 'id', _id + '-img' );
_item.find( 'image' ).attr( 'clip-path', 'url(#'+_id+'-img)' );
_item.delay( animDelay*index ).queue( 'fx', function() {
$( this ).addClass( 'is-loaded' ).dequeue();
});
});
//Initialize the first item container
//-------------------------------------
$items.addClass( 'next' );
setTimeout( function() {
$first.addClass( 'is-active' );
}, animDelay );
//
let imgURL = $first.find( 'img' ).attr( 'src' );
if ( typeof imgURL != typeof undefined ) {
const img = new Image();
img.onload = function() {
$this.css( 'height', $this.width()*(this.height/this.width) + 'px' );
nativeItemW = this.width;
nativeItemH = this.height;
//Initialize all the items to the stage
addItemsToStage( $this, nativeItemW, nativeItemH, dataControlsPagination, dataControlsArrows, dataLoop, dataDraggable, dataDraggableCursor, dataCountTotal, dataCountCur, txtTimeline, txtMaskTimeline );
};
img.src = imgURL;
}
//Autoplay Slider
//-------------------------------------
if ( !resize ) {
if ( dataAuto && !isNaN( parseFloat( dataTiming ) ) && isFinite( dataTiming ) ) {
sliderAutoPlay( playTimes, dataTiming, dataLoop, $this, dataCountTotal, dataCountCur, dataControlsPagination, dataControlsArrows );
const autoplayEnter = function() {
clearInterval( $this[0].animatedSlides );
};
const autoplayLeave = function() {
sliderAutoPlay( playTimes, dataTiming, dataLoop, $this, dataCountTotal, dataCountCur, dataControlsPagination, dataControlsArrows );
};
// Do not use the `off()` method, otherwise it will cause the second mouseenter to be invalid
$this.on( 'mouseenter', autoplayEnter );
$this.on( 'mouseleave', autoplayLeave );
// To determine if it is a touch screen.
if (Modernizr.touchevents) {
$this.on( 'pointerenter', autoplayEnter );
$this.on( 'pointerleave', autoplayLeave );
}
}
}
//Prevents front-end javascripts that are activated with AJAX to repeat loading.
$this.data( 'activated', 1 );
}//endif activated
});
}
/*
* Trigger slider autoplay
*
* @param {Function} playTimes - Number of times.
* @param {Number} timing - Autoplay interval.
* @param {Boolean} loop - Gives the slider a seamless infinite loop.
* @param {Element} slider - Selector of the slider .
* @param {String} countTotalID - Total number ID or class of counter.
* @param {String} countCurID - Current number ID or class of counter.
* @param {String} paginationID - Navigation ID for paging control of each slide.
* @param {String} arrowsID - Previous/Next arrow navigation ID.
* @return {Void} - The constructor.
*/
function sliderAutoPlay( playTimes, timing, loop, slider, countTotalID, countCurID, paginationID, arrowsID ) {
const items = slider.find( '.uix-svgMask-slider__item' ),
total = items.length;
slider[0].animatedSlides = setInterval( function() {
playTimes = parseFloat( items.filter( '.is-active' ).index() );
playTimes++;
if ( !loop ) {
if ( playTimes < total && playTimes >= 0 ) sliderUpdates( playTimes, slider, 'next', countTotalID, countCurID, paginationID, arrowsID, loop );
} else {
if ( playTimes == total ) playTimes = 0;
if ( playTimes < 0 ) playTimes = total-1;
sliderUpdates( playTimes, slider, 'next', countTotalID, countCurID, paginationID, arrowsID, loop );
}
}, timing );
}
/*
* Initialize all the items to the stage
*
* @param {Element} slider - Current selector of each slider.
* @param {Number} nativeItemW - Returns the intrinsic width of the image.
* @param {Number} nativeItemH - Returns the intrinsic height of the image.
* @param {String} paginationID - Navigation ID for paging control of each slide.
* @param {String} arrowsID - Previous/Next arrow navigation ID.
* @param {Boolean} loop - Gives the slider a seamless infinite loop.
* @param {Boolean} draggable - Allow drag and drop on the slider.
* @param {String} draggableCursor - Drag & Drop Change icon/cursor while dragging.
* @param {String} countTotalID - Total number ID or class of counter.
* @param {String} countCurID - Current number ID or class of counter.
* @param {Function} tl1 - Timeline animation of text field.
* @param {Function} tl2 - Timeline animation of text mask field.
* @return {Void}
*/
function addItemsToStage( slider, nativeItemW, nativeItemH, paginationID, arrowsID, loop, draggable, draggableCursor, countTotalID, countCurID, tl1, tl2 ) {
const $this = slider,
$items = $this.find( '.uix-svgMask-slider__item' ),
$first = $items.first(),
itemsTotal = $items.length;
//If arrows does not exist on the page, it will be added by default,
//and the drag and drop function will be activated.
if ( $( arrowsID ).length == 0 ) {
$( 'body' ).prepend( '<div style="display:none;" class="uix-svgMask-slider__arrows '+arrowsID.replace('#','').replace('.','')+'"><a href="#" class="uix-svgMask-slider__arrows--prev"></a><a href="#" class="uix-svgMask-slider__arrows--next"></a></div>' );
}
//Add identifiers for the first and last items
$items.last().addClass( 'last' );
$items.first().addClass( 'first' );
//Prevent bubbling
if ( itemsTotal == 1 ) {
$( paginationID ).hide();
$( arrowsID ).hide();
}
//Pagination dots
//-------------------------------------
let _dot = '',
_dotActive = '';
_dot += '<ul>';
for ( let i = 0; i < itemsTotal; i++ ) {
_dotActive = ( i == 0 ) ? 'class="is-active"' : '';
_dot += '<li><a '+_dotActive+' data-index="'+i+'" href="javascript:"></a></li>';
}
_dot += '</ul>';
if ( $( paginationID ).html() == '' ) $( paginationID ).html( _dot );
$( paginationID ).find( 'li a' ).off( 'click' ).on( 'click', function( e ) {
e.preventDefault();
if ( svgAnimating ) return false;
//Prevent buttons' events from firing multiple times
const $btn = $( this );
if ( $btn.attr( 'aria-disabled' ) == 'true' ) return false;
$( paginationID ).find( 'li a' ).attr( 'aria-disabled', 'true' );
$( paginationID ).find( 'li a' )
.delay(animDelay)
.queue(function(next) { $( paginationID ).find( 'li a' ).attr( 'aria-disabled', 'false' ); next(); });
//
if ( !$( this ).hasClass( 'is-active' ) ) {
//Text animation from timeline
tl1.restart();
setTimeout(function() {
tl2.restart();
}, 1500 );
//Determine the direction
let curDir = 'prev';
if ( $( this ).attr( 'data-index' ) > parseFloat( $items.filter( '.is-active' ).index() ) ) {
curDir = 'next';
}
sliderUpdates( $( this ).attr( 'data-index' ), $this, curDir, countTotalID, countCurID, paginationID, arrowsID, loop );
//Pause the auto play event
clearInterval( $this[0].animatedSlides );
}
});
//Next/Prev buttons
//-------------------------------------
const _prev = $( arrowsID ).find( '.uix-svgMask-slider__arrows--prev' ),
_next = $( arrowsID ).find( '.uix-svgMask-slider__arrows--next' );
$( arrowsID ).find( 'a' ).attr( 'href', 'javascript:' );
$( arrowsID ).find( 'a' ).removeClass( 'is-disabled' );
if ( !loop ) {
_prev.addClass( 'is-disabled' );
}
_prev.off( 'click' ).on( 'click', function( e ) {
e.preventDefault();
//Pause the auto play event
clearInterval( $this[0].animatedSlides );
//Move animation
prevMove();
});
_next.off( 'click' ).on( 'click', function( e ) {
e.preventDefault();
//Pause the auto play event
clearInterval( $this[0].animatedSlides );
//Move animation
nextMove();
});
function prevMove() {
if ( svgAnimating ) return false;
//Prevent buttons' events from firing multiple times
if ( _prev.attr( 'aria-disabled' ) == 'true' ) return false;
_prev.attr( 'aria-disabled', 'true' );
_prev
.delay(animDelay)
.queue(function(next) { _prev.attr( 'aria-disabled', 'false' ); next(); });
//
if ( _prev.hasClass( 'is-disabled' ) ) return false;
//Text animation from timeline
tl1.restart();
setTimeout(function() {
tl2.restart();
}, 1500 );
//
sliderUpdates( parseFloat( $items.filter( '.is-active' ).index() ) - 1, $this, 'prev', countTotalID, countCurID, paginationID, arrowsID, loop );
}
function nextMove() {
if ( svgAnimating ) return false;
//Prevent buttons' events from firing multiple times
if ( _next.attr( 'aria-disabled' ) == 'true' ) return false;
_next.attr( 'aria-disabled', 'true' );
_next
.delay(animDelay)
.queue(function(next) { _next.attr( 'aria-disabled', 'false' ); next(); });
//
if ( _next.hasClass( 'is-disabled' ) ) return false;
//Text animation from timeline
tl1.restart();
setTimeout(function() {
tl2.restart();
}, 1500 );
//
sliderUpdates( parseFloat( $items.filter( '.is-active' ).index() ) + 1, $this, 'next', countTotalID, countCurID, paginationID, arrowsID, loop );
}
//Added touch method to mobile device and desktop
//-------------------------------------
const $dragTrigger = $this.find( '.uix-svgMask-slider__inner' );
let mouseX, mouseY;
let isMoving = false;
//Avoid images causing mouseup to fail
$dragTrigger.find( 'img' ).css({
'pointer-events': 'none',
'user-select': 'none'
});
//Make the cursor a move icon when a user hovers over an item
if ( draggable && draggableCursor != '' && draggableCursor != false ) $dragTrigger.css( 'cursor', draggableCursor );
//draggable for touch devices
if (Modernizr.touchevents) draggable = true;
if ( draggable ) {
$dragTrigger[0].removeEventListener( 'mousedown', dragStart );
document.removeEventListener( 'mouseup', dragEnd );
$dragTrigger[0].removeEventListener( 'touchstart', dragStart );
document.removeEventListener( 'touchend', dragEnd );
//
$dragTrigger[0].addEventListener( 'mousedown', dragStart );
$dragTrigger[0].addEventListener( 'touchstart', dragStart );
}
function dragStart(e) {
//Do not use "e.preventDefault()" to avoid prevention page scroll on drag in IOS and Android
const touches = e.touches;
if ( touches && touches.length ) {
mouseX = touches[0].clientX;
mouseY = touches[0].clientY;
} else {
mouseX = e.clientX;
mouseY = e.clientY;
}
document.addEventListener( 'mouseup', dragEnd );
document.addEventListener( 'mousemove', dragProcess );
document.addEventListener( 'touchend', dragEnd );
document.addEventListener( 'touchmove', dragProcess );
}
function dragProcess(e) {
const touches = e.touches;
let offsetX, offsetY;
if ( touches && touches.length ) {
offsetX = mouseX - touches[0].clientX,
offsetY = mouseY - touches[0].clientY;
} else {
offsetX = mouseX - e.clientX,
offsetY = mouseY - e.clientY;
}
//--- left
if ( offsetX >= 50) {
if ( !isMoving ) {
isMoving = true;
nextMove();
}
}
//--- right
if ( offsetX <= -50) {
if ( !isMoving ) {
isMoving = true;
prevMove();
}
}
//--- up
if ( offsetY >= 50) {
}
//--- down
if ( offsetY <= -50) {
}
}
function dragEnd(e) {
document.removeEventListener( 'mousemove', dragProcess);
document.removeEventListener( 'touchmove', dragProcess);
//restore move action status
setTimeout( function() {
isMoving = false;
}, animDelay);
}
}
/*
* Transition Between Slides
*
* @param {Number} elementIndex - Index of current slider.
* @param {Element} slider - Selector of the slider .
* @param {String} dir - Switching direction indicator.
* @param {String} countTotalID - Total number ID or class of counter.
* @param {String} countCurID - Current number ID or class of counter.
* @param {String} paginationID - Navigation ID for paging control of each slide.
* @param {String} arrowsID - Previous/Next arrow navigation ID.
* @param {Boolean} loop - Gives the slider a seamless infinite loop.
* @return {Void}
*/
function sliderUpdates( elementIndex, slider, dir, countTotalID, countCurID, paginationID, arrowsID, loop ) {
const $items = slider.find( '.uix-svgMask-slider__item' ),
total = $items.length;
//Prevent bubbling
if ( total == 1 ) {
$( paginationID ).hide();
$( arrowsID ).hide();
return false;
}
//Transition Interception
//-------------------------------------
if ( loop ) {
if ( elementIndex == total ) elementIndex = 0;
if ( elementIndex < 0 ) elementIndex = total-1;
} else {
$( arrowsID ).find( 'a' ).removeClass( 'is-disabled' );
if ( elementIndex == total - 1 ) $( arrowsID ).find( '.uix-svgMask-slider__arrows--next' ).addClass( 'is-disabled' );
if ( elementIndex == 0 ) $( arrowsID ).find( '.uix-svgMask-slider__arrows--prev' ).addClass( 'is-disabled' );
}
// To determine if it is a touch screen.
if (Modernizr.touchevents) {
if ( elementIndex == total ) elementIndex = total-1;
if ( elementIndex < 0 ) elementIndex = 0;
//Prevent bubbling
if ( !loop ) {
//first item
if ( elementIndex == 0 ) {
$( arrowsID ).find( '.uix-svgMask-slider__arrows--prev' ).addClass( 'is-disabled' );
}
//last item
if ( elementIndex == total - 1 ) {
$( arrowsID ).find( '.uix-svgMask-slider__arrows--next' ).addClass( 'is-disabled' );
}
}
}
// call the current item
//-------------------------------------
const $current = $items.eq( elementIndex );
//Determine the direction and add class to switching direction indicator.
let dirIndicatorClass = '';
if ( dir == 'prev' ) dirIndicatorClass = 'prev';
if ( dir == 'next' ) dirIndicatorClass = 'next';
//Add transition class to Controls Pagination
$( paginationID ).find( 'li a' ).removeClass( 'leave' );
$( paginationID ).find( 'li a.is-active' ).removeClass( 'is-active' ).addClass( 'leave');
$( paginationID ).find( 'li a[data-index="'+elementIndex+'"]' ).addClass( 'is-active').removeClass( 'leave' );
//Add transition class to each item
$items.removeClass( 'leave prev next' );
$items.addClass( dirIndicatorClass );
slider.find( '.uix-svgMask-slider__item.is-active' ).removeClass( 'is-active' ).addClass( 'leave ' + dirIndicatorClass );
$current.addClass( 'is-active ' + dirIndicatorClass ).removeClass( 'leave' );
//SVG Animation
//-------------------------------------
if( !svgAnimating ) {
//don't animate if already animating
svgAnimating = true;
const path1 = '1 0.5 1 540.5 1 1080.5 0 1080.5 4 1080.5 4 540.5 4 0.5 0 0.5 1 0.5',
path2 = '0.5 0.5 0.5 540.5 0.5 1080.5 1519.5 1080.5 1531.5 1080.5 1066.5 525.5 601.5 1.5 589.5 1.5 0.5 0.5',
path3 = '0.5 0.5 0.5 540.5 0.5 1080.5 960.5 1080.5 1920.5 1080.5 1920.5 540.5 1920.5 0.5 960.5 0.5 0.5 0.5';
if ( dir == 'next' ) {
$current.find('polygon').css({
'transform-origin': 'center',
'transform': 'rotate(180deg)'
});
} else {
$current.find('polygon').css({
'transform-origin': 'center',
'transform': 'rotate(0)'
});
}
//----
//@required: MorphSVGPlugin
/*
TweenMax.set( $current.find('polygon')[0], {
attr: {
points: path1
},
onComplete: function() {
TweenMax.to( this.target, animSpeed/1000, {
morphSVG: path2,
delay: 0,
ease: Power2.easeOut,
onComplete: function() {
TweenMax.to( this.target, animSpeed/1000, {
morphSVG: path3,
delay: 0,
ease: Power2.easeInOut,
onComplete: function() {
svgAnimating = false;
}
});
}
});
}
});
*/
TweenMax.set( $current.find('polygon')[0], {
attr: {
points: path1
},
onComplete: function() {
anime.timeline({
loop: false
}).add({
targets: $current.find('polygon')[0],
points: [
{value: path2}
],
duration: animSpeed,
easing: "easeOutExpo"
}).add({
targets: $current.find('polygon')[0],
points: [
{value: path3}
],
duration: animSpeed,
easing: "easeOutExpo",
complete: function() {
svgAnimating = false;
}
});
}
});
}//endif svgAnimating
//Display counter
//-------------------------------------
$( countTotalID ).text( total );
$( countCurID ).text( parseFloat( elementIndex ) + 1 );
//Reset the default height of item
//-------------------------------------
itemDefaultInit( slider, $current );
}
/*
* Initialize the default height of item
*
* @param {Element} slider - Selector of the slider .
* @param {Element} currentLlement - Current selector of each slider.
* @return {Void}
*/
function itemDefaultInit( slider, currentLlement ) {
//
let imgURL = currentLlement.find( 'img' ).attr( 'src' );
if ( typeof imgURL != typeof undefined ) {
const img = new Image();
img.onload = function() {
slider.css( 'height', currentLlement.closest( '.uix-svgMask-slider__outline' ).width()*(this.height/this.width) + 'px' );
};
img.src = imgURL;
}
}
};
module.components.pageLoaded.push( module.SVG_MASK_SLIDER.pageLoaded );
return class SVG_MASK_SLIDER {
constructor() {
this.module = module;
}
};
})( UixModuleInstance, jQuery, window, document );