#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: node-flick/js/NodeFlick.js

    /**
     * Provide a simple Flick plugin, which can be used along with the "flick" gesture event, to
     * animate the motion of the host node in response to a (mouse or touch) flick gesture.
     *
     * <p>The current implementation is designed to move the node, relative to the bounds of a parent node and is suitable
     * for scroll/carousel type implementations. Future versions will remove that constraint, to allow open ended movement within
     * the document.</p>
     *
     * @module node-flick
     */

        var HOST = "host",
            PARENT_NODE = "parentNode",
            BOUNDING_BOX = "boundingBox",
            OFFSET_HEIGHT = "offsetHeight",
            OFFSET_WIDTH = "offsetWidth",
            SCROLL_HEIGHT = "scrollHeight",
            SCROLL_WIDTH = "scrollWidth",
            BOUNCE = "bounce",
            MIN_DISTANCE = "minDistance",
            MIN_VELOCITY = "minVelocity",
            BOUNCE_DISTANCE = "bounceDistance",
            DECELERATION = "deceleration",
            STEP = "step",
            DURATION = "duration",
            EASING = "easing",
            FLICK = "flick",

            getClassName = Y.ClassNameManager.getClassName;

        /**
         * A plugin class which can be used to animate the motion of a node, in response to a flick gesture.
         *
         * @class Flick
         * @namespace Plugin
         * @param {Object} config The initial attribute values for the plugin
         */
        function Flick(config) {
            Flick.superclass.constructor.apply(this, arguments);
        }

        Flick.ATTRS = {

            /**
             * Drag coefficent for inertial scrolling. The closer to 1 this
             * value is, the less friction during scrolling.
             *
             * @attribute deceleration
             * @default 0.98
             */
            deceleration : {
                value: 0.98
            },

            /**
             * Drag coefficient for intertial scrolling at the upper
             * and lower boundaries of the scrollview. Set to 0 to
             * disable "rubber-banding".
             *
             * @attribute bounce
             * @type Number
             * @default 0.7
             */
            bounce : {
                value: 0.7
            },

            /**
             * The bounce distance in pixels
             *
             * @attribute bounceDistance
             * @type Number
             * @default 150
             */
            bounceDistance : {
                value: 150
            },

            /**
             * The minimum flick gesture velocity (px/ms) at which to trigger the flick response
             *
             * @attribute minVelocity
             * @type Number
             * @default 0
             */
            minVelocity : {
                value: 0
            },

            /**
             * The minimum flick gesture distance (px) for which to trigger the flick response
             *
             * @attribute minVelocity
             * @type Number
             * @default 10
             */
            minDistance : {
                value: 10
            },

            /**
             * The constraining box relative to which the flick animation and bounds should be calculated.
             *
             * @attribute boundingBox
             * @type Node
             * @default parentNode
             */
            boundingBox : {
                valueFn : function() {
                    return this.get(HOST).get(PARENT_NODE);
                }
            },

            /**
             * Time between flick animation frames.
             *
             * @attribute step
             * @type Number
             * @default 10
             */
            step : {
                value:10
            },

            /**
             * The custom duration to apply to the flick animation. By default,
             * the animation duration is controlled by the deceleration factor.
             *
             * @attribute duration
             * @type Number
             * @default null
             */
            duration : {
                value:null
            },

            /**
             * The custom transition easing to use for the flick animation. If not
             * provided defaults to internally to Flick.EASING, or Flick.SNAP_EASING based
             * on whether or not we're animating the flick or bounce step.
             *
             * @attribute easing
             * @type String
             * @default null
             */
            easing : {
                value:null
            }
        };

        /**
         * The NAME of the Flick class. Used to prefix events generated
         * by the plugin.
         *
         * @property NAME
         * @static
         * @type String
         * @default "pluginFlick"
         */
        Flick.NAME = "pluginFlick";

        /**
         * The namespace for the plugin. This will be the property on the node, which will
         * reference the plugin instance, when it's plugged in.
         *
         * @property NS
         * @static
         * @type String
         * @default "flick"
         */
        Flick.NS = "flick";

        Y.extend(Flick, Y.Plugin.Base, {

            /**
             * The initializer lifecycle implementation.
             *
             * @method initializer
             * @param {Object} config The user configuration for the plugin
             */
            initializer : function(config) {
                this._node = this.get(HOST);

                this._renderClasses();
                this.setBounds();

                this._node.on(FLICK, Y.bind(this._onFlick, this), {
                    minDistance : this.get(MIN_DISTANCE),
                    minVelocity : this.get(MIN_VELOCITY)
                });
            },

            /**
             * Sets the min/max boundaries for the flick animation,
             * based on the boundingBox dimensions.
             *
             * @method setBounds
             */
            setBounds : function () {
                var box = this.get(BOUNDING_BOX),
                    node = this._node,

                    boxHeight = box.get(OFFSET_HEIGHT),
                    boxWidth = box.get(OFFSET_WIDTH),

                    contentHeight = node.get(SCROLL_HEIGHT),
                    contentWidth = node.get(SCROLL_WIDTH);

                if (contentHeight > boxHeight) {
                    this._maxY = contentHeight - boxHeight;
                    this._minY = 0;
                    this._scrollY = true;
                }

                if (contentWidth > boxWidth) {
                    this._maxX = contentWidth - boxWidth;
                    this._minX = 0;
                    this._scrollX = true;
                }

                this._x = this._y = 0;

                node.set("top", this._y + "px");
                node.set("left", this._x + "px");
            },

            /**
             * Adds the CSS classes, necessary to set up overflow/position properties on the
             * node and boundingBox.
             *
             * @method _renderClasses
             * @protected
             */
            _renderClasses : function() {
                this.get(BOUNDING_BOX).addClass(Flick.CLASS_NAMES.box);
                this._node.addClass(Flick.CLASS_NAMES.content);
            },

            /**
             * The flick event listener. Kicks off the flick animation.
             *
             * @method _onFlick
             * @param e {EventFacade} The flick event facade, containing e.flick.distance, e.flick.velocity etc.
             * @protected
             */
            _onFlick: function(e) {
                this._v = e.flick.velocity;
                this._flick = true;
                this._flickAnim();
            },

            /**
             * Executes a single frame in the flick animation
             *
             * @method _flickFrame
             * @protected
             */
            _flickAnim: function() {

                var y = this._y,
                    x = this._x,

                    maxY = this._maxY,
                    minY = this._minY,
                    maxX = this._maxX,
                    minX = this._minX,
                    velocity = this._v,

                    step = this.get(STEP),
                    deceleration = this.get(DECELERATION),
                    bounce = this.get(BOUNCE);

                this._v = (velocity * deceleration);

                this._snapToEdge = false;

                if (this._scrollX) {
                    x = x - (velocity * step);
                }

                if (this._scrollY) {
                    y = y - (velocity * step);
                }

                if (Math.abs(velocity).toFixed(4) <= Flick.VELOCITY_THRESHOLD) {

                    this._flick = false;

                    this._killTimer(!(this._exceededYBoundary || this._exceededXBoundary));

                    if (this._scrollX) {
                        if (x < minX) {
                            this._snapToEdge = true;
                            this._setX(minX);
                        } else if (x > maxX) {
                            this._snapToEdge = true;
                            this._setX(maxX);
                        }
                    }

                    if (this._scrollY) {
                        if (y < minY) {
                            this._snapToEdge = true;
                            this._setY(minY);
                        } else if (y > maxY) {
                            this._snapToEdge = true;
                            this._setY(maxY);
                        }
                    }

                } else {

                    if (this._scrollX && (x < minX || x > maxX)) {
                        this._exceededXBoundary = true;
                        this._v *= bounce;
                    }

                    if (this._scrollY && (y < minY || y > maxY)) {
                        this._exceededYBoundary = true;
                        this._v *= bounce;
                    }

                    if (this._scrollX) {
                        this._setX(x);
                    }

                    if (this._scrollY) {
                        this._setY(y);
                    }

                    this._flickTimer = Y.later(step, this, this._flickAnim);
                }
            },

            /**
             * Internal utility method to set the X offset position
             *
             * @method _setX
             * @param {Number} val
             * @private
             */
            _setX : function(val) {
                this._move(val, null, this.get(DURATION), this.get(EASING));
            },

            /**
             * Internal utility method to set the Y offset position
             *
             * @method _setY
             * @param {Number} val
             * @private
             */
            _setY : function(val) {
                this._move(null, val, this.get(DURATION), this.get(EASING));
            },

            /**
             * Internal utility method to move the node to a given XY position,
             * using transitions, if specified.
             *
             * @method _move
             * @param {Number} x The X offset position
             * @param {Number} y The Y offset position
             * @param {Number} duration The duration to use for the transition animation
             * @param {String} easing The easing to use for the transition animation.
             *
             * @private
             */
            _move: function(x, y, duration, easing) {

                if (x !== null) {
                    x = this._bounce(x);
                } else {
                    x = this._x;
                }

                if (y !== null) {
                    y = this._bounce(y);
                } else {
                    y = this._y;
                }

                duration = duration || this._snapToEdge ? Flick.SNAP_DURATION : 0;
                easing = easing || this._snapToEdge ? Flick.SNAP_EASING : Flick.EASING;

                this._x = x;
                this._y = y;

                this._anim(x, y, duration, easing);
            },

            /**
             * Internal utility method to perform the transition step
             *
             * @method _anim
             * @param {Number} x The X offset position
             * @param {Number} y The Y offset position
             * @param {Number} duration The duration to use for the transition animation
             * @param {String} easing The easing to use for the transition animation.
             *
             * @private
             */
            _anim : function(x, y, duration, easing) {
                var xn = x * -1,
                    yn = y * -1,

                    transition = {
                        duration : duration / 1000,
                        easing : easing
                    };

                Y.log("Transition: duration, easing:" + transition.duration, transition.easing, "node-flick");

                if (Y.Transition.useNative) {
                    transition.transform = 'translate('+ (xn) + 'px,' + (yn) +'px)';
                } else {
                    transition.left = xn + 'px';
                    transition.top = yn + 'px';
                }

                this._node.transition(transition);
            },

            /**
             * Internal utility method to constrain the offset value
             * based on the bounce criteria.
             *
             * @method _bounce
             * @param {Number} x The offset value to constrain.
             * @param {Number} max The max offset value.
             *
             * @private
             */
            _bounce : function(val, max) {
                var bounce = this.get(BOUNCE),
                    dist = this.get(BOUNCE_DISTANCE),
                    min = bounce ? -dist : 0;

                max = bounce ? max + dist : max;

                if(!bounce) {
                    if(val < min) {
                        val = min;
                    } else if(val > max) {
                        val = max;
                    }
                }
                return val;
            },

            /**
             * Stop the animation timer
             *
             * @method _killTimer
             * @private
             */
            _killTimer: function() {
                if(this._flickTimer) {
                    this._flickTimer.cancel();
                }
            }

        }, {

            /**
             * The threshold used to determine when the decelerated velocity of the node
             * is practically 0.
             *
             * @property VELOCITY_THRESHOLD
             * @static
             * @type Number
             * @default 0.015
             */
            VELOCITY_THRESHOLD : 0.015,

            /**
             * The duration to use for the bounce snap-back transition
             *
             * @property SNAP_DURATION
             * @static
             * @type Number
             * @default 400
             */
             SNAP_DURATION : 400,

            /**
             * The default easing to use for the main flick movement transition
             *
             * @property EASING
             * @static
             * @type String
             * @default 'cubic-bezier(0, 0.1, 0, 1.0)'
             */
            EASING : 'cubic-bezier(0, 0.1, 0, 1.0)',

            /**
             * The default easing to use for the bounce snap-back transition
             *
             * @property SNAP_EASING
             * @static
             * @type String
             * @default 'ease-out'
             */
            SNAP_EASING : 'ease-out',

            /**
             * The default CSS class names used by the plugin
             *
             * @property CLASS_NAMES
             * @static
             * @type Object
             */
            CLASS_NAMES : {
                box: getClassName(Flick.NS),
                content: getClassName(Flick.NS, "content")
            }
        });

        Y.Plugin.Flick = Flick;