lory-8.x-1.x-dev/js/src/lory.slicebox.js
js/src/lory.slicebox.js
/**
* @file
* Adapted from jquery.slicebox.js v1.1.0, and converted into vanilla JS.
*
* Licensed under the MIT license.
* @see http://www.opensource.org/licenses/mit-license.php
* @see https://github.com/codrops/slicebox#readme
*
* This is a modified version, made vanilla JS, modernized using Web Animations
* API, with rAF and Blazy supports, by @gausarts.
*/
(function (_db, window) {
'use strict';
// Cache the prototype once.
var _l = lory.prototype;
/**
* Contructs a Slicebox.
*
* @param {Object} _lory
* The lory instance object.
*/
_l.effects.slicebox = function (_lory) {
var me = this;
if (me.support) {
me.$track = _lory.$track;
me.$slider = me.$track.parentNode.parentNode;
me.options = _lory.options;
me.transformProp = _lory.transformProp;
me.options = _db.extend({}, me.defaults, me.options);
}
};
/**
* Adds Slicebox prototypes.
*/
_l.effects.slicebox.prototype = {
/**
* It is 2017, do not bother for oldies.
*
* No need to bother for oldies like IE9, fallbacks to regular slideshow.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
* @see http://w3c.github.io/web-animations/
*/
support: 'AnimationEvent' in window,
/**
* Provides defaults.
*/
defaults: {
// The orientation: (v)ertical, (h)orizontal or (r)andom.
orientation: 'v',
// Perspective value.
perspective: 1200,
// Number of slices / cuboids.
// Needs to be an odd number 15 => number > 0 (if you want the limit higher,
// change the _validate function).
cuboidsCount: 5,
// If true then the number of slices / cuboids is going to be random
// (cuboidsCount is overwitten)
cuboidsRandom: false,
// The range of possible number of cuboids if cuboidsRandom is true
// it is strongly recommended that you do not set a very large number :)
maxCuboidsCount: 5,
// Each cuboid will move x pixels left / top (depending on orientation).
// The middle cuboid doesn't move. the middle cuboid's neighbors will move
// disperseFactor pixels.
disperseFactor: 0,
// Color of the hidden sides.
colorHiddenSides: '#222',
// The animation will start from left to right. The left most cuboid will be
// the first one to rotate.
// This is the interval between each rotation in ms.
sequentialFactor: 150,
// Animation speed.
// This is the speed that takes "1" cuboid to rotate.
slideSpeed: 600,
// Transition easing.
ease: 'ease',
// If true the slicebox will start the animation automatically.
interval: 3000,
// The fallback will just fade out / fade in the items.
// This is the time fr the fade effect.
fallbackFadeSpeed: 300
},
run: function () {
var me = this;
// Don't even think about it, Oldies!
if (me.$slider.animate === 'undefined') {
return false;
}
me._validate();
me.$slides = me.$track.childNodes;
me.itemsCount = me.$slides.length;
// If there's no items return.
if (me.itemsCount === 0 || !me.transformProp) {
return false;
}
// Current image index.
me.current = me.options.initialSlide || 0;
me.isAnimating = false;
me.isReady = false;
me.$box = document.createElement('div');
me.$box.classList.add('lory__perspective');
me.$slider.appendChild(me.$box);
window.setTimeout(function () {
// Preload the images.
var $curr = me.$track.querySelector('.slide.is-current');
var src = $curr.querySelector('.b-lazy') !== null ? $curr.querySelector('.b-lazy').getAttribute('data-src') : '';
if (!src) {
src = $curr.querySelector('img').getAttribute('src');
}
// Get real size of image.
var img = new Image();
if ($curr.querySelector('img.b-loaded') !== null) {
src = $curr.querySelector('.b-loaded').getAttribute('src');
}
img.src = src;
if (src !== null) {
me.realWidth = img.width;
me._initEvents();
_db.trigger(me.$slider, 'sliceboxReady', {slicebox: me});
me.isReady = true;
me.$slider.classList.add('is-sliced');
me.$slider.classList.remove('is-animating', 'is-slicing');
}
}, 2000);
// Rotate cuboids before sliding.
me.$slider.addEventListener('before.lory.slide', me.rotateTo.bind(me));
},
_validate: function () {
var me = this;
var options = me.options;
if (options.cuboidsCount < 0) {
options.cuboidsCount = 1;
}
else if (options.cuboidsCount > 15) {
options.cuboidsCount = 15;
}
else if (options.cuboidsCount % 2 === 0) {
++options.cuboidsCount;
}
if (options.maxCuboidsCount < 0) {
options.maxCuboidsCount = 1;
}
else if (options.maxCuboidsCount > 15) {
options.maxCuboidsCount = 15;
}
else if (options.maxCuboidsCount % 2 === 0) {
++options.maxCuboidsCount;
}
if (options.disperseFactor < 0) {
options.disperseFactor = 0;
}
if (options.orientation !== 'v' && options.orientation !== 'h' && options.orientation !== 'r') {
options.orientation = 'v';
}
},
_setSize: function () {
var me = this;
var $item = me.$slides[me.current].querySelector('img');
var w;
var h;
if ($item === null) {
$item = me.$slides[me.current];
w = $item.offsetWidth;
h = $item.offsetHeight;
}
else {
w = $item.width;
h = $item.height;
}
me.size = {
width: w,
height: h
};
},
_initEvents: function () {
var me = this;
_db.resize(function () {
// Assuming all images with same size.
me._setSize();
})();
},
_layout: function (dir) {
var me = this;
var orientation = me.options.orientation;
if (orientation === 'r') {
orientation = Math.floor(Math.random() * 2) === 0 ? 'v' : 'h';
}
if (me.options.cuboidsRandom) {
me.options.cuboidsCount = Math.floor(Math.random() * me.options.maxCuboidsCount + 1);
}
me._validate();
var boxStyle = {
width: me.size.width,
height: me.size.height,
perspective: me.options.perspective
};
var config = _db.extend(me.options, {
size: me.size,
items: me.$slides,
direction: dir,
prev: me.prev,
current: me.current,
o: orientation,
support: me.support
});
me.cuboids = [];
var slices = document.createDocumentFragment();
for (var i = 0; i < me.options.cuboidsCount; ++i) {
var cuboid = new Cuboid(config, i);
slices.appendChild(cuboid.getEl());
me.cuboids.push(cuboid);
}
me.$box.appendChild(slices);
window.setTimeout(function () {
me.$slider.classList.add('is-slicing');
me.$slider.classList.remove('is-sliced');
}, 600);
_db.forEach(boxStyle, function (val, prop) {
me.$box.style[prop] = val + 'px';
});
},
_rotate: function () {
var me = this;
// Reacts on current item, rotate and remove the animated item.
for (var i = 0; i < me.options.cuboidsCount; ++i) {
var cuboid = me.cuboids[i];
cuboid.rotate(function (pos) {
if (pos === me.options.cuboidsCount - 1) {
window.setTimeout(function () {
me.isAnimating = false;
me.$box.innerHTML = '';
me.$slider.classList.remove('is-animating', 'is-slicing');
me.$slider.classList.add('is-sliced');
me.$slider.querySelector('.slide.is-current').classList.add('is-focus');
}, 0);
_db.trigger(me.$slider, 'sliceboxAfterChange', {currentSlide: me.current});
}
});
}
},
rotateTo: function (e) {
var me = this;
me.navigate(e.detail.nextSlide, 'next');
},
navigate: function (pos, dir) {
var me = this;
if (me.isAnimating || !me.isReady || me.itemsCount < 2) {
return false;
}
me.isAnimating = true;
// Current item's index.
me.prev = me.current;
// If position is passed.
if (pos !== 'undefined') {
me.current = pos;
}
// If not check the boundaries.
else if (dir === 'next') {
me.current = me.current < me.itemsCount - 1 ? me.current + 1 : 0;
}
else if (dir === 'prev') {
me.current = me.current > 0 ? me.current - 1 : me.itemsCount - 1;
}
// Callback trigger.
_db.trigger(me.$slider, 'sliceboxBeforeChange', {currentSlide: me.current});
me._layout(dir);
me._rotate();
}
};
/**
* Contructs a Cuboid.
*
* @param {Object} config
* The Cuboid configuration option objects.
* @param {int} pos
* The current Cuboid index.
*/
function Cuboid(config, pos) {
var me = this;
me.config = config;
me.pos = pos;
me.side = 1;
me._setSize();
me._configureStyles();
}
Cuboid.prototype = {
_setSize: function () {
var me = this;
var options = me.config;
me.size = {
width: options.o === 'v' ? Math.floor(options.size.width / options.cuboidsCount) : options.size.width,
height: options.o === 'v' ? options.size.height : Math.floor(options.size.height / options.cuboidsCount)
};
// Extra space to fix gaps.
me.extra = options.o === 'v'
? options.size.width - (me.size.width * options.cuboidsCount)
: options.size.height - (me.size.height * options.cuboidsCount);
},
_configureStyles: function () {
var me = this;
var options = me.config;
// Style for the cuboid element.
// Set z-indexes based on the cuboid's position.
var middlepos = Math.ceil(options.cuboidsCount / 2);
var positionStyle = me.pos < middlepos ? {
zIndex: (me.pos + 1) * 100,
left: (options.o === 'v') ? me.size.width * me.pos : 0,
top: (options.o === 'v') ? 0 : me.size.height * me.pos
} : {
zIndex: (options.cuboidsCount - me.pos) * 100,
left: (options.o === 'v') ? me.size.width * me.pos : 0,
top: (options.o === 'v') ? 0 : me.size.height * me.pos
};
// How much me cuboid is going to move (left or top values).
me.disperseFactor = options.disperseFactor * ((me.pos + 1) - middlepos);
me.style = _db.extend({
transition: 'transform ' + options.slideSpeed + 'ms ' + options.ease
}, positionStyle, me.size);
me.animationStyles = {
side1: (options.o === 'v') ? {
transform: 'translate3d(0, 0, -' + (me.size.height / 2) + 'px)'
} : {
transform: 'translate3d(0, 0, -' + (me.size.width / 2) + 'px)'
},
side2: (options.o === 'v') ? {
transform: 'translate3d(0, 0, -' + (me.size.height / 2) + 'px) rotate3d(1, 0, 0, -90deg)'
} : {
transform: 'translate3d(0, 0, -' + (me.size.width / 2) + 'px) rotate3d(0, 1, 0, -90deg)'
},
side3: (options.o === 'v') ? {
transform: 'translate3d(0, 0, -' + (me.size.height / 2) + 'px) rotate3d(1, 0, 0, -180deg)'
} : {
transform: 'translate3d(0, 0, -' + (me.size.width / 2) + 'px) rotate3d(0, 1, 0, -180deg)'
},
side4: (options.o === 'v') ? {
transform: 'translate3d(0, 0, -' + (me.size.height / 2) + 'px) rotate3d(1, 0, 0, -270deg)'
} : {
transform: 'translate3d(0, 0, -' + (me.size.width / 2) + 'px) rotate3d(0, 1, 0, -270deg)'
}
};
var measure = (options.o === 'v') ? me.size.height : me.size.width;
me.sidesStyles = {
frontSideStyle: {
width: (options.o === 'v') ? me.size.width + me.extra : me.size.width,
height: (options.o === 'v') ? me.size.height : me.size.height + me.extra,
backgroundColor: options.colorHiddenSides,
transform: 'rotate3d(0, 1, 0, 0deg) translate3d(0, 0, ' + (measure / 2) + 'px)'
},
backSideStyle: {
width: me.size.width,
height: me.size.height,
backgroundColor: options.colorHiddenSides,
transform: 'rotate3d(0, 1, 0, 180deg) translate3d(0, 0, ' + (measure / 2) + 'px) rotateZ(180deg)'
},
rightSideStyle: {
width: measure,
height: (options.o === 'v') ? me.size.height : me.size.height + me.extra,
left: (options.o === 'v') ? me.size.width / 2 - me.size.height / 2 : 0,
backgroundColor: options.colorHiddenSides,
transform: 'rotate3d(0, 1, 0, 90deg) translate3d(0, 0, ' + (me.size.width / 2) + 'px)'
},
leftSideStyle: {
width: measure,
height: (options.o === 'v') ? me.size.height : me.size.height + me.extra,
left: (options.o === 'v') ? me.size.width / 2 - me.size.height / 2 : 0,
backgroundColor: options.colorHiddenSides,
transform: 'rotate3d(0, 1, 0, -90deg) translate3d(0, 0, ' + (me.size.width / 2) + 'px)'
},
topSideStyle: {
width: (options.o === 'v') ? me.size.width + me.extra : me.size.width,
height: measure,
top: (options.o === 'v') ? 0 : me.size.height / 2 - me.size.width / 2,
backgroundColor: options.colorHiddenSides,
transform: 'rotate3d(1, 0, 0, 90deg) translate3d(0, 0, ' + (me.size.height / 2) + 'px)'
},
bottomSideStyle: {
width: (options.o === 'v') ? me.size.width + me.extra : me.size.width,
height: measure,
top: (options.o === 'v') ? 0 : me.size.height / 2 - me.size.width / 2,
backgroundColor: options.colorHiddenSides,
transform: 'rotate3d(1, 0, 0, -90deg) translate3d(0, 0, ' + (me.size.height / 2) + 'px)'
}
};
},
addPx: function (val, prop) {
if (prop === 'left' || prop === 'top' || prop === 'width' || prop === 'height') {
val += 'px';
}
return val;
},
getEl: function () {
var me = this;
me.$el = document.createElement('div');
_db.forEach(me.style, function (val, prop) {
me.$el.style[prop] = me.addPx(val, prop);
});
_db.forEach(me.animationStyles.side1, function (val, prop) {
me.$el.style[prop] = me.addPx(val, prop);
});
_db.forEach(me.sidesStyles, function (val) {
var sliceItem = me.sliceEl(val);
me.$el.appendChild(sliceItem);
});
me.$el.classList.add('lory__box');
me._showImage(me.config.prev);
return me.$el;
},
sliceEl: function (styles) {
var me = this;
var box = document.createElement('div');
var item = box.cloneNode(true);
item.classList.add('lory__slice');
_db.forEach(styles, function (val, prop) {
item.style[prop] = me.addPx(val, prop);
});
return item;
},
_showImage: function (imgPos) {
var me = this;
var options = me.config;
var sideIdx;
var $item = options.items[imgPos];
var $loaded = $item.querySelector('.b-loaded');
var url = null;
var bgSize = options.size.width + 'px ' + options.size.height + 'px';
var imgParam = {
backgroundSize: bgSize
};
if ($loaded !== null && $loaded.getAttribute('src')) {
url = $loaded.getAttribute('src');
}
if (url === null) {
url = $item.querySelector('.b-lazy') !== null ? $item.querySelector('.b-lazy').getAttribute('data-src') : '';
if (!url) {
url = $item.querySelector('img').getAttribute('src');
}
}
imgParam.backgroundImage = 'url(' + url + ')';
switch (me.side) {
case 1:
sideIdx = 0;
break;
case 2:
sideIdx = (options.o === 'v') ? 4 : 2;
break;
case 3:
sideIdx = 1;
break;
case 4:
sideIdx = (options.o === 'v') ? 5 : 3;
break;
}
imgParam.backgroundPosition = (options.o === 'v')
? -(me.pos * me.size.width) + 'px 0px'
: '0px -' + (me.pos * me.size.height) + 'px';
_db.forEach(imgParam, function (val, prop) {
me.$el.childNodes[sideIdx].style[prop] = val;
});
},
rotate: function (callback) {
var me = this;
var options = me.config;
var animationStyle;
window.setTimeout(function () {
if (options.direction === 'next') {
switch (me.side) {
case 1:
animationStyle = me.animationStyles.side2;
me.side = 2;
break;
case 2:
animationStyle = me.animationStyles.side3;
me.side = 3;
break;
case 3:
animationStyle = me.animationStyles.side4;
me.side = 4;
break;
case 4:
animationStyle = me.animationStyles.side1;
me.side = 1;
break;
}
}
else {
switch (me.side) {
case 1:
animationStyle = me.animationStyles.side4;
me.side = 4;
break;
case 2:
animationStyle = me.animationStyles.side1;
me.side = 1;
break;
case 3:
animationStyle = me.animationStyles.side2;
me.side = 2;
break;
case 4:
animationStyle = me.animationStyles.side3;
me.side = 3;
break;
}
}
me._showImage(options.current);
var animateOut = {};
var animateIn = {};
var currPos = 0;
var toOut;
var toIn;
// @todo: Improve, and or merge into existing translate3d().
if (options.o === 'v') {
currPos = parseInt(me.getCssProp(me.$el, 'left'));
toOut = currPos - me.disperseFactor;
toIn = currPos + me.disperseFactor;
animateOut.left = [currPos + 'px', toOut + 'px', currPos + 'px'];
animateIn.left = [currPos + 'px', toIn + 'px', currPos + 'px'];
}
else if (options.o === 'h') {
currPos = parseInt(me.getCssProp(me.$el, 'top'));
toOut = currPos - me.disperseFactor;
toIn = currPos + me.disperseFactor;
animateOut.top = [currPos + 'px', toOut + 'px', currPos + 'px'];
animateIn.top = [currPos + 'px', toIn + 'px', currPos + 'px'];
}
_db.forEach(animationStyle, function (val, prop) {
me.$el.style[prop] = val;
});
// We use Web Animations API for modern browsers only.
// https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
// http://w3c.github.io/web-animations/
var animProps = {
// Available options: normal, alternate, alternate-reverse, reverse.
direction: 'normal',
duration: options.slideSpeed * 2.5,
delay: 0,
// Available options: backwards, forwards, both, none.
fill: 'forwards',
easing: options.ease,
iterationStart: 0.0
};
me.$el.animate(animateOut, animProps);
var animation = me.$el.animate(animateIn, animProps);
// Update callback once the animation finishes.
var onFinish = function () {
if (callback) {
callback.call(me, me.pos);
}
};
if (animation.finished) {
animation.finished.then(onFinish);
}
else {
animation.onfinish = onFinish;
}
}, options.sequentialFactor * me.pos + 30);
},
getCssProp: function (el, prop) {
var value = null;
if (el.currentStyle) {
value = el.currentStyle[prop];
}
else if (window.getComputedStyle) {
value = window.getComputedStyle(el, null).getPropertyValue(prop);
}
return value;
}
};
/**
* Add to global namespace.
*/
window.Cuboid = Cuboid;
}(dBlazy, window));
