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));

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc