packages feed

reload-0.0.0.1: web/bower_components/app-layout/app-scroll-effects/app-scroll-effects-behavior.html

<!--
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../iron-scroll-target-behavior/iron-scroll-target-behavior.html">
<link rel="import" href="../helpers/helpers.html">

<script>
  /**
   * `Polymer.AppScrollEffectsBehavior` provides an interface that allows an element to use scrolls effects.
   *
   * ### Importing the app-layout effects
   *
   * app-layout provides a set of scroll effects that can be used by explicitly importing
   * `app-scroll-effects.html`:
   *
   * ```html
   * <link rel="import" href="/bower_components/app-layout/app-scroll-effects/app-scroll-effects.html">
   * ```
   *
   * The scroll effects can also be used by individually importing
   * `app-layout/app-scroll-effects/effects/[effectName].html`. For example:
   *
   * ```html
   *  <link rel="import" href="/bower_components/app-layout/app-scroll-effects/effects/waterfall.html">
   * ```
   *
   * ### Consuming effects
   *
   * Effects can be consumed via the `effects` property. For example:
   *
   * ```html
   * <app-header effects="waterfall"></app-header>
   * ```
   *
   * ### Creating scroll effects
   *
   * You may want to create a custom scroll effect if you need to modify the CSS of an element
   * based on the scroll position.
   *
   * A scroll effect definition is an object with `setUp()`, `tearDown()` and `run()` functions.
   * 
   * To register the effect, you can use `Polymer.AppLayout.registerEffect(effectName, effectDef)`
   * For example, let's define an effect that resizes the header's logo:
   *
   * ```js
   * Polymer.AppLayout.registerEffect('resizable-logo', {
   *   setUp: function(config) {
   *     // the effect's config is passed to the setUp.
   *     this._fxResizeLogo = { logo: Polymer.dom(this).querySelector('[logo]') };
   *   },
   *
   *   run: function(progress) {
   *      // the progress of the effect
   *      this.transform('scale3d(' + progress + ', '+ progress +', 1)',  this._fxResizeLogo.logo);
   *   },
   *
   *   tearDown: function() {
   *      // clean up and reset of states
   *      delete this._fxResizeLogo;
   *   }
   * });
   * ```
   * Now, you can consume the effect:
   *
   * ```html
   * <app-header id="appHeader" effects="resizable-logo">
   *   <img logo src="logo.svg">
   * </app-header>
   * ```
   *
   * ### Imperative API
   *
   * ```js
   * var logoEffect = appHeader.createEffect('resizable-logo', effectConfig);
   * // run the effect: logoEffect.run(progress);
   * // tear down the effect: logoEffect.tearDown();
   * ```
   *
   * ### Configuring effects
   *
   * For effects installed via the `effects` property, their configuration can be set
   * via the `effectsConfig` property. For example:
   *
   * ```html
   * <app-header effects="waterfall"
   *   effects-config='{"waterfall": {"startsAt": 0, "endsAt": 0.5}}'>
   * </app-header>
   * ```
   *
   * All effects have a `startsAt` and `endsAt` config property. They specify at what
   * point the effect should start and end. This value goes from 0 to 1 inclusive.
   *
   * @polymerBehavior
   */
  Polymer.AppScrollEffectsBehavior = [
    Polymer.IronScrollTargetBehavior,
   {

    properties: {

      /**
       * A space-separated list of the effects names that will be triggered when the user scrolls.
       * e.g. `waterfall parallax-background` installs the `waterfall` and `parallax-background`.
       */
      effects: {
        type: String
      },

      /**
       * An object that configurates the effects installed via the `effects` property. e.g.
       * ```js
       *  element.effectsConfig = {
       *   "blend-background": {
       *     "startsAt": 0.5
       *   }
       * };
       * ```
       * Every effect has at least two config properties: `startsAt` and `endsAt`.
       * These properties indicate when the event should start and end respectively
       * and relative to the overall element progress. So for example, if `blend-background`
       * starts at `0.5`, the effect will only start once the current element reaches 0.5
       * of its progress. In this context, the progress is a value in the range of `[0, 1]`
       * that indicates where this element is on the screen relative to the viewport.
       */
      effectsConfig: {
        type: Object,
        value: function() {
          return {};
        }
      },

      /**
       * Disables CSS transitions and scroll effects on the element.
       */
      disabled: {
        type: Boolean,
        reflectToAttribute: true,
        value: false
      }
    },

    observers: [
      '_effectsChanged(effects, effectsConfig, isAttached)'
    ],

    /**
     * Updates the scroll state. This method should be overridden
     * by the consumer of this behavior.
     *
     * @method _updateScrollState
     */
    _updateScrollState: function() {},

    /**
     * Returns true if the current element is on the screen.
     * That is, visible in the current viewport. This method should be
     * overridden by the consumer of this behavior.
     *
     * @method isOnScreen
     * @return {boolean}
     */
    isOnScreen: function() {
      return false;
    },

    /**
     * Returns true if there's content below the current element. This method
     * should be overridden by the consumer of this behavior.
     *
     * @method isContentBelow
     * @return {boolean}
     */
    isContentBelow: function() {
      return false;
    },

    /**
     * List of effects handlers that will take place during scroll.
     *
     * @type {Array<Function>}
     */
    _effectsRunFn: null,

    /**
     * List of the effects definitions installed via the `effects` property.
     *
     * @type {Array<Object>}
     */
    _effects: null,

    /**
     * The clamped value of `_scrollTop`.
     * @type number
     */
    get _clampedScrollTop() {
      return Math.max(0, this._scrollTop);
    },

    detached: function() {
      this._tearDownEffects();
    },

    /**
     * Creates an effect object from an effect's name that can be used to run
     * effects programmatically.
     *
     * @method createEffect
     * @param {string} effectName The effect's name registered via `Polymer.AppLayout.registerEffect`.
     * @param {Object=} effectConfig The effect config object. (Optional)
     * @return {Object} An effect object with the following functions:
     *
     *  * `effect.setUp()`, Sets up the requirements for the effect.
     *       This function is called automatically before the `effect` function returns.
     *  * `effect.run(progress, y)`, Runs the effect given a `progress`.
     *  * `effect.tearDown()`, Cleans up any DOM nodes or element references used by the effect.
     *
     * Example:
     * ```js
     * var parallax = element.createEffect('parallax-background');
     * // runs the effect
     * parallax.run(0.5, 0);
     * ```
     */
    createEffect: function(effectName, effectConfig) {
      var effectDef = Polymer.AppLayout._scrollEffects[effectName];
      if (!effectDef) {
        throw new ReferenceError(this._getUndefinedMsg(effectName));
      }
      var prop = this._boundEffect(effectDef, effectConfig || {});
      prop.setUp();
      return prop;
    },

    /**
     * Called when `effects` or `effectsConfig` changes.
     */
    _effectsChanged: function(effects, effectsConfig, isAttached) {
      this._tearDownEffects();

      if (effects === '' || !isAttached) {
        return;
      }
      effects.split(' ').forEach(function(effectName) {
        var effectDef;
        if (effectName !== '') {
          if ((effectDef = Polymer.AppLayout._scrollEffects[effectName])) {
            this._effects.push(this._boundEffect(effectDef, effectsConfig[effectName]));
          } else {
            this._warn(this._logf('_effectsChanged', this._getUndefinedMsg(effectName)));
          }
        }
      }, this);

      this._setUpEffect();
    },

    /**
     * Forces layout
     */
    _layoutIfDirty: function() {
      return this.offsetWidth;
    },

    /**
     * Returns an effect object bound to the current context.
     *
     * @param {Object} effectDef
     * @param {Object=} effectsConfig The effect config object if the effect accepts config values. (Optional)
     */
    _boundEffect: function(effectDef, effectsConfig) {
      effectsConfig = effectsConfig || {};
      var startsAt = parseFloat(effectsConfig.startsAt || 0);
      var endsAt = parseFloat(effectsConfig.endsAt || 1);
      var deltaS = endsAt - startsAt;
      var noop = Function();
      // fast path if possible
      var runFn = (startsAt === 0 && endsAt === 1) ? effectDef.run :
        function(progress, y) {
          effectDef.run.call(this,
              Math.max(0, (progress - startsAt) / deltaS), y);
        };
      return {
        setUp: effectDef.setUp ? effectDef.setUp.bind(this, effectsConfig) : noop,
        run: effectDef.run ? runFn.bind(this) : noop,
        tearDown: effectDef.tearDown ? effectDef.tearDown.bind(this) : noop
      };
    },

    /**
     * Sets up the effects.
     */
    _setUpEffect: function() {
      if (this.isAttached && this._effects) {
        this._effectsRunFn = [];
        this._effects.forEach(function(effectDef) {
          // install the effect only if no error was reported
          if (effectDef.setUp() !== false) {
            this._effectsRunFn.push(effectDef.run);
          }
        }, this);
      }
    },

    /**
     * Tears down the effects.
     */
    _tearDownEffects: function() {
      if (this._effects) {
        this._effects.forEach(function(effectDef) {
          effectDef.tearDown();
        });
      }
      this._effectsRunFn = [];
      this._effects = [];
    },

    /**
     * Runs the effects.
     *
     * @param {number} p The progress
     * @param {number} y The top position of the current element relative to the viewport.
     */
    _runEffects: function(p, y) {
      if (this._effectsRunFn) {
        this._effectsRunFn.forEach(function(run) {
          run(p, y);
        });
      }
    },

    /**
     * Overrides the `_scrollHandler`.
     */
    _scrollHandler: function() {
      if (!this.disabled) {
        this._updateScrollState(this._clampedScrollTop);
      }
    },
    
    /**
     * Override this method to return a reference to a node in the local DOM.
     * The node is consumed by a scroll effect.
     *
     * @param {string} id The id for the node.
     */
    _getDOMRef: function(id) {
      this._warn(this._logf('_getDOMRef', '`'+ id +'` is undefined'));
    },

    _getUndefinedMsg: function(effectName) {
      return 'Scroll effect `' + effectName + '` is undefined. ' +
          'Did you forget to import app-layout/app-scroll-effects/effects/' + effectName + '.html ?';
    }

  }];

</script>