123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /**
- * Outlayer Item
- */
- ( function( window, factory ) {
- 'use strict';
- // universal module definition
- if ( typeof define === 'function' && define.amd ) {
- // AMD
- define( [
- 'eventEmitter/EventEmitter',
- 'get-size/get-size',
- 'get-style-property/get-style-property',
- 'fizzy-ui-utils/utils'
- ],
- function( EventEmitter, getSize, getStyleProperty, utils ) {
- return factory( window, EventEmitter, getSize, getStyleProperty, utils );
- }
- );
- } else if (typeof exports === 'object') {
- // CommonJS
- module.exports = factory(
- window,
- require('wolfy87-eventemitter'),
- require('get-size'),
- require('desandro-get-style-property'),
- require('fizzy-ui-utils')
- );
- } else {
- // browser global
- window.Outlayer = {};
- window.Outlayer.Item = factory(
- window,
- window.EventEmitter,
- window.getSize,
- window.getStyleProperty,
- window.fizzyUIUtils
- );
- }
- }( window, function factory( window, EventEmitter, getSize, getStyleProperty, utils ) {
- 'use strict';
- // ----- helpers ----- //
- var getComputedStyle = window.getComputedStyle;
- var getStyle = getComputedStyle ?
- function( elem ) {
- return getComputedStyle( elem, null );
- } :
- function( elem ) {
- return elem.currentStyle;
- };
- function isEmptyObj( obj ) {
- for ( var prop in obj ) {
- return false;
- }
- prop = null;
- return true;
- }
- // -------------------------- CSS3 support -------------------------- //
- var transitionProperty = getStyleProperty('transition');
- var transformProperty = getStyleProperty('transform');
- var supportsCSS3 = transitionProperty && transformProperty;
- var is3d = !!getStyleProperty('perspective');
- var transitionEndEvent = {
- WebkitTransition: 'webkitTransitionEnd',
- MozTransition: 'transitionend',
- OTransition: 'otransitionend',
- transition: 'transitionend'
- }[ transitionProperty ];
- // properties that could have vendor prefix
- var prefixableProperties = [
- 'transform',
- 'transition',
- 'transitionDuration',
- 'transitionProperty'
- ];
- // cache all vendor properties
- var vendorProperties = ( function() {
- var cache = {};
- for ( var i=0, len = prefixableProperties.length; i < len; i++ ) {
- var prop = prefixableProperties[i];
- var supportedProp = getStyleProperty( prop );
- if ( supportedProp && supportedProp !== prop ) {
- cache[ prop ] = supportedProp;
- }
- }
- return cache;
- })();
- // -------------------------- Item -------------------------- //
- function Item( element, layout ) {
- if ( !element ) {
- return;
- }
- this.element = element;
- // parent layout class, i.e. Masonry, Isotope, or Packery
- this.layout = layout;
- this.position = {
- x: 0,
- y: 0
- };
- this._create();
- }
- // inherit EventEmitter
- utils.extend( Item.prototype, EventEmitter.prototype );
- Item.prototype._create = function() {
- // transition objects
- this._transn = {
- ingProperties: {},
- clean: {},
- onEnd: {}
- };
- this.css({
- position: 'absolute'
- });
- };
- // trigger specified handler for event type
- Item.prototype.handleEvent = function( event ) {
- var method = 'on' + event.type;
- if ( this[ method ] ) {
- this[ method ]( event );
- }
- };
- Item.prototype.getSize = function() {
- this.size = getSize( this.element );
- };
- /**
- * apply CSS styles to element
- * @param {Object} style
- */
- Item.prototype.css = function( style ) {
- var elemStyle = this.element.style;
- for ( var prop in style ) {
- // use vendor property if available
- var supportedProp = vendorProperties[ prop ] || prop;
- elemStyle[ supportedProp ] = style[ prop ];
- }
- };
- // measure position, and sets it
- Item.prototype.getPosition = function() {
- var style = getStyle( this.element );
- var layoutOptions = this.layout.options;
- var isOriginLeft = layoutOptions.isOriginLeft;
- var isOriginTop = layoutOptions.isOriginTop;
- var xValue = style[ isOriginLeft ? 'left' : 'right' ];
- var yValue = style[ isOriginTop ? 'top' : 'bottom' ];
- // convert percent to pixels
- var layoutSize = this.layout.size;
- var x = xValue.indexOf('%') != -1 ?
- ( parseFloat( xValue ) / 100 ) * layoutSize.width : parseInt( xValue, 10 );
- var y = yValue.indexOf('%') != -1 ?
- ( parseFloat( yValue ) / 100 ) * layoutSize.height : parseInt( yValue, 10 );
- // clean up 'auto' or other non-integer values
- x = isNaN( x ) ? 0 : x;
- y = isNaN( y ) ? 0 : y;
- // remove padding from measurement
- x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight;
- y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom;
- this.position.x = x;
- this.position.y = y;
- };
- // set settled position, apply padding
- Item.prototype.layoutPosition = function() {
- var layoutSize = this.layout.size;
- var layoutOptions = this.layout.options;
- var style = {};
- // x
- var xPadding = layoutOptions.isOriginLeft ? 'paddingLeft' : 'paddingRight';
- var xProperty = layoutOptions.isOriginLeft ? 'left' : 'right';
- var xResetProperty = layoutOptions.isOriginLeft ? 'right' : 'left';
- var x = this.position.x + layoutSize[ xPadding ];
- // set in percentage or pixels
- style[ xProperty ] = this.getXValue( x );
- // reset other property
- style[ xResetProperty ] = '';
- // y
- var yPadding = layoutOptions.isOriginTop ? 'paddingTop' : 'paddingBottom';
- var yProperty = layoutOptions.isOriginTop ? 'top' : 'bottom';
- var yResetProperty = layoutOptions.isOriginTop ? 'bottom' : 'top';
- var y = this.position.y + layoutSize[ yPadding ];
- // set in percentage or pixels
- style[ yProperty ] = this.getYValue( y );
- // reset other property
- style[ yResetProperty ] = '';
- this.css( style );
- this.emitEvent( 'layout', [ this ] );
- };
- Item.prototype.getXValue = function( x ) {
- var layoutOptions = this.layout.options;
- return layoutOptions.percentPosition && !layoutOptions.isHorizontal ?
- ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px';
- };
- Item.prototype.getYValue = function( y ) {
- var layoutOptions = this.layout.options;
- return layoutOptions.percentPosition && layoutOptions.isHorizontal ?
- ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px';
- };
- Item.prototype._transitionTo = function( x, y ) {
- this.getPosition();
- // get current x & y from top/left
- var curX = this.position.x;
- var curY = this.position.y;
- var compareX = parseInt( x, 10 );
- var compareY = parseInt( y, 10 );
- var didNotMove = compareX === this.position.x && compareY === this.position.y;
- // save end position
- this.setPosition( x, y );
- // if did not move and not transitioning, just go to layout
- if ( didNotMove && !this.isTransitioning ) {
- this.layoutPosition();
- return;
- }
- var transX = x - curX;
- var transY = y - curY;
- var transitionStyle = {};
- transitionStyle.transform = this.getTranslate( transX, transY );
- this.transition({
- to: transitionStyle,
- onTransitionEnd: {
- transform: this.layoutPosition
- },
- isCleaning: true
- });
- };
- Item.prototype.getTranslate = function( x, y ) {
- // flip cooridinates if origin on right or bottom
- var layoutOptions = this.layout.options;
- x = layoutOptions.isOriginLeft ? x : -x;
- y = layoutOptions.isOriginTop ? y : -y;
- if ( is3d ) {
- return 'translate3d(' + x + 'px, ' + y + 'px, 0)';
- }
- return 'translate(' + x + 'px, ' + y + 'px)';
- };
- // non transition + transform support
- Item.prototype.goTo = function( x, y ) {
- this.setPosition( x, y );
- this.layoutPosition();
- };
- // use transition and transforms if supported
- Item.prototype.moveTo = supportsCSS3 ?
- Item.prototype._transitionTo : Item.prototype.goTo;
- Item.prototype.setPosition = function( x, y ) {
- this.position.x = parseInt( x, 10 );
- this.position.y = parseInt( y, 10 );
- };
- // ----- transition ----- //
- /**
- * @param {Object} style - CSS
- * @param {Function} onTransitionEnd
- */
- // non transition, just trigger callback
- Item.prototype._nonTransition = function( args ) {
- this.css( args.to );
- if ( args.isCleaning ) {
- this._removeStyles( args.to );
- }
- for ( var prop in args.onTransitionEnd ) {
- args.onTransitionEnd[ prop ].call( this );
- }
- };
- /**
- * proper transition
- * @param {Object} args - arguments
- * @param {Object} to - style to transition to
- * @param {Object} from - style to start transition from
- * @param {Boolean} isCleaning - removes transition styles after transition
- * @param {Function} onTransitionEnd - callback
- */
- Item.prototype._transition = function( args ) {
- // redirect to nonTransition if no transition duration
- if ( !parseFloat( this.layout.options.transitionDuration ) ) {
- this._nonTransition( args );
- return;
- }
- var _transition = this._transn;
- // keep track of onTransitionEnd callback by css property
- for ( var prop in args.onTransitionEnd ) {
- _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ];
- }
- // keep track of properties that are transitioning
- for ( prop in args.to ) {
- _transition.ingProperties[ prop ] = true;
- // keep track of properties to clean up when transition is done
- if ( args.isCleaning ) {
- _transition.clean[ prop ] = true;
- }
- }
- // set from styles
- if ( args.from ) {
- this.css( args.from );
- // force redraw. http://blog.alexmaccaw.com/css-transitions
- var h = this.element.offsetHeight;
- // hack for JSHint to hush about unused var
- h = null;
- }
- // enable transition
- this.enableTransition( args.to );
- // set styles that are transitioning
- this.css( args.to );
- this.isTransitioning = true;
- };
- // dash before all cap letters, including first for
- // WebkitTransform => -webkit-transform
- function toDashedAll( str ) {
- return str.replace( /([A-Z])/g, function( $1 ) {
- return '-' + $1.toLowerCase();
- });
- }
- var transitionProps = 'opacity,' +
- toDashedAll( vendorProperties.transform || 'transform' );
- Item.prototype.enableTransition = function(/* style */) {
- // HACK changing transitionProperty during a transition
- // will cause transition to jump
- if ( this.isTransitioning ) {
- return;
- }
- // make `transition: foo, bar, baz` from style object
- // HACK un-comment this when enableTransition can work
- // while a transition is happening
- // var transitionValues = [];
- // for ( var prop in style ) {
- // // dash-ify camelCased properties like WebkitTransition
- // prop = vendorProperties[ prop ] || prop;
- // transitionValues.push( toDashedAll( prop ) );
- // }
- // enable transition styles
- this.css({
- transitionProperty: transitionProps,
- transitionDuration: this.layout.options.transitionDuration
- });
- // listen for transition end event
- this.element.addEventListener( transitionEndEvent, this, false );
- };
- Item.prototype.transition = Item.prototype[ transitionProperty ? '_transition' : '_nonTransition' ];
- // ----- events ----- //
- Item.prototype.onwebkitTransitionEnd = function( event ) {
- this.ontransitionend( event );
- };
- Item.prototype.onotransitionend = function( event ) {
- this.ontransitionend( event );
- };
- // properties that I munge to make my life easier
- var dashedVendorProperties = {
- '-webkit-transform': 'transform',
- '-moz-transform': 'transform',
- '-o-transform': 'transform'
- };
- Item.prototype.ontransitionend = function( event ) {
- // disregard bubbled events from children
- if ( event.target !== this.element ) {
- return;
- }
- var _transition = this._transn;
- // get property name of transitioned property, convert to prefix-free
- var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName;
- // remove property that has completed transitioning
- delete _transition.ingProperties[ propertyName ];
- // check if any properties are still transitioning
- if ( isEmptyObj( _transition.ingProperties ) ) {
- // all properties have completed transitioning
- this.disableTransition();
- }
- // clean style
- if ( propertyName in _transition.clean ) {
- // clean up style
- this.element.style[ event.propertyName ] = '';
- delete _transition.clean[ propertyName ];
- }
- // trigger onTransitionEnd callback
- if ( propertyName in _transition.onEnd ) {
- var onTransitionEnd = _transition.onEnd[ propertyName ];
- onTransitionEnd.call( this );
- delete _transition.onEnd[ propertyName ];
- }
- this.emitEvent( 'transitionEnd', [ this ] );
- };
- Item.prototype.disableTransition = function() {
- this.removeTransitionStyles();
- this.element.removeEventListener( transitionEndEvent, this, false );
- this.isTransitioning = false;
- };
- /**
- * removes style property from element
- * @param {Object} style
- **/
- Item.prototype._removeStyles = function( style ) {
- // clean up transition styles
- var cleanStyle = {};
- for ( var prop in style ) {
- cleanStyle[ prop ] = '';
- }
- this.css( cleanStyle );
- };
- var cleanTransitionStyle = {
- transitionProperty: '',
- transitionDuration: ''
- };
- Item.prototype.removeTransitionStyles = function() {
- // remove transition
- this.css( cleanTransitionStyle );
- };
- // ----- show/hide/remove ----- //
- // remove element from DOM
- Item.prototype.removeElem = function() {
- this.element.parentNode.removeChild( this.element );
- // remove display: none
- this.css({ display: '' });
- this.emitEvent( 'remove', [ this ] );
- };
- Item.prototype.remove = function() {
- // just remove element if no transition support or no transition
- if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) {
- this.removeElem();
- return;
- }
- // start transition
- var _this = this;
- this.once( 'transitionEnd', function() {
- _this.removeElem();
- });
- this.hide();
- };
- Item.prototype.reveal = function() {
- delete this.isHidden;
- // remove display: none
- this.css({ display: '' });
- var options = this.layout.options;
- var onTransitionEnd = {};
- var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle');
- onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd;
- this.transition({
- from: options.hiddenStyle,
- to: options.visibleStyle,
- isCleaning: true,
- onTransitionEnd: onTransitionEnd
- });
- };
- Item.prototype.onRevealTransitionEnd = function() {
- // check if still visible
- // during transition, item may have been hidden
- if ( !this.isHidden ) {
- this.emitEvent('reveal');
- }
- };
- /**
- * get style property use for hide/reveal transition end
- * @param {String} styleProperty - hiddenStyle/visibleStyle
- * @returns {String}
- */
- Item.prototype.getHideRevealTransitionEndProperty = function( styleProperty ) {
- var optionStyle = this.layout.options[ styleProperty ];
- // use opacity
- if ( optionStyle.opacity ) {
- return 'opacity';
- }
- // get first property
- for ( var prop in optionStyle ) {
- return prop;
- }
- };
- Item.prototype.hide = function() {
- // set flag
- this.isHidden = true;
- // remove display: none
- this.css({ display: '' });
- var options = this.layout.options;
- var onTransitionEnd = {};
- var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle');
- onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd;
- this.transition({
- from: options.visibleStyle,
- to: options.hiddenStyle,
- // keep hidden stuff hidden
- isCleaning: true,
- onTransitionEnd: onTransitionEnd
- });
- };
- Item.prototype.onHideTransitionEnd = function() {
- // check if still hidden
- // during transition, item may have been un-hidden
- if ( this.isHidden ) {
- this.css({ display: 'none' });
- this.emitEvent('hide');
- }
- };
- Item.prototype.destroy = function() {
- this.css({
- position: '',
- left: '',
- right: '',
- top: '',
- bottom: '',
- transition: '',
- transform: ''
- });
- };
- return Item;
- }));
|