animate.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // animate
  2. ( function( window, factory ) {
  3. // universal module definition
  4. /* jshint strict: false */
  5. if ( typeof define == 'function' && define.amd ) {
  6. // AMD
  7. define( [
  8. 'fizzy-ui-utils/utils'
  9. ], function( utils ) {
  10. return factory( window, utils );
  11. });
  12. } else if ( typeof module == 'object' && module.exports ) {
  13. // CommonJS
  14. module.exports = factory(
  15. window,
  16. require('fizzy-ui-utils')
  17. );
  18. } else {
  19. // browser global
  20. window.Flickity = window.Flickity || {};
  21. window.Flickity.animatePrototype = factory(
  22. window,
  23. window.fizzyUIUtils
  24. );
  25. }
  26. }( window, function factory( window, utils ) {
  27. 'use strict';
  28. // -------------------------- animate -------------------------- //
  29. var proto = {};
  30. proto.startAnimation = function() {
  31. if ( this.isAnimating ) {
  32. return;
  33. }
  34. this.isAnimating = true;
  35. this.restingFrames = 0;
  36. this.animate();
  37. };
  38. proto.animate = function() {
  39. this.applyDragForce();
  40. this.applySelectedAttraction();
  41. var previousX = this.x;
  42. this.integratePhysics();
  43. this.positionSlider();
  44. this.settle( previousX );
  45. // animate next frame
  46. if ( this.isAnimating ) {
  47. var _this = this;
  48. requestAnimationFrame( function animateFrame() {
  49. _this.animate();
  50. });
  51. }
  52. };
  53. proto.positionSlider = function() {
  54. var x = this.x;
  55. // wrap position around
  56. if ( this.options.wrapAround && this.cells.length > 1 ) {
  57. x = utils.modulo( x, this.slideableWidth );
  58. x = x - this.slideableWidth;
  59. this.shiftWrapCells( x );
  60. }
  61. this.setTranslateX( x, this.isAnimating );
  62. this.dispatchScrollEvent();
  63. };
  64. proto.setTranslateX = function( x, is3d ) {
  65. x += this.cursorPosition;
  66. // reverse if right-to-left and using transform
  67. x = this.options.rightToLeft ? -x : x;
  68. var translateX = this.getPositionValue( x );
  69. // use 3D tranforms for hardware acceleration on iOS
  70. // but use 2D when settled, for better font-rendering
  71. this.slider.style.transform = is3d ?
  72. 'translate3d(' + translateX + ',0,0)' : 'translateX(' + translateX + ')';
  73. };
  74. proto.dispatchScrollEvent = function() {
  75. var firstSlide = this.slides[0];
  76. if ( !firstSlide ) {
  77. return;
  78. }
  79. var positionX = -this.x - firstSlide.target;
  80. var progress = positionX / this.slidesWidth;
  81. this.dispatchEvent( 'scroll', null, [ progress, positionX ] );
  82. };
  83. proto.positionSliderAtSelected = function() {
  84. if ( !this.cells.length ) {
  85. return;
  86. }
  87. this.x = -this.selectedSlide.target;
  88. this.velocity = 0; // stop wobble
  89. this.positionSlider();
  90. };
  91. proto.getPositionValue = function( position ) {
  92. if ( this.options.percentPosition ) {
  93. // percent position, round to 2 digits, like 12.34%
  94. return ( Math.round( ( position / this.size.innerWidth ) * 10000 ) * 0.01 )+ '%';
  95. } else {
  96. // pixel positioning
  97. return Math.round( position ) + 'px';
  98. }
  99. };
  100. proto.settle = function( previousX ) {
  101. // keep track of frames where x hasn't moved
  102. if ( !this.isPointerDown && Math.round( this.x * 100 ) == Math.round( previousX * 100 ) ) {
  103. this.restingFrames++;
  104. }
  105. // stop animating if resting for 3 or more frames
  106. if ( this.restingFrames > 2 ) {
  107. this.isAnimating = false;
  108. delete this.isFreeScrolling;
  109. // render position with translateX when settled
  110. this.positionSlider();
  111. this.dispatchEvent( 'settle', null, [ this.selectedIndex ] );
  112. }
  113. };
  114. proto.shiftWrapCells = function( x ) {
  115. // shift before cells
  116. var beforeGap = this.cursorPosition + x;
  117. this._shiftCells( this.beforeShiftCells, beforeGap, -1 );
  118. // shift after cells
  119. var afterGap = this.size.innerWidth - ( x + this.slideableWidth + this.cursorPosition );
  120. this._shiftCells( this.afterShiftCells, afterGap, 1 );
  121. };
  122. proto._shiftCells = function( cells, gap, shift ) {
  123. for ( var i=0; i < cells.length; i++ ) {
  124. var cell = cells[i];
  125. var cellShift = gap > 0 ? shift : 0;
  126. cell.wrapShift( cellShift );
  127. gap -= cell.size.outerWidth;
  128. }
  129. };
  130. proto._unshiftCells = function( cells ) {
  131. if ( !cells || !cells.length ) {
  132. return;
  133. }
  134. for ( var i=0; i < cells.length; i++ ) {
  135. cells[i].wrapShift( 0 );
  136. }
  137. };
  138. // -------------------------- physics -------------------------- //
  139. proto.integratePhysics = function() {
  140. this.x += this.velocity;
  141. this.velocity *= this.getFrictionFactor();
  142. };
  143. proto.applyForce = function( force ) {
  144. this.velocity += force;
  145. };
  146. proto.getFrictionFactor = function() {
  147. return 1 - this.options[ this.isFreeScrolling ? 'freeScrollFriction' : 'friction' ];
  148. };
  149. proto.getRestingPosition = function() {
  150. // my thanks to Steven Wittens, who simplified this math greatly
  151. return this.x + this.velocity / ( 1 - this.getFrictionFactor() );
  152. };
  153. proto.applyDragForce = function() {
  154. if ( !this.isDraggable || !this.isPointerDown ) {
  155. return;
  156. }
  157. // change the position to drag position by applying force
  158. var dragVelocity = this.dragX - this.x;
  159. var dragForce = dragVelocity - this.velocity;
  160. this.applyForce( dragForce );
  161. };
  162. proto.applySelectedAttraction = function() {
  163. // do not attract if pointer down or no slides
  164. var dragDown = this.isDraggable && this.isPointerDown;
  165. if ( dragDown || this.isFreeScrolling || !this.slides.length ) {
  166. return;
  167. }
  168. var distance = this.selectedSlide.target * -1 - this.x;
  169. var force = distance * this.options.selectedAttraction;
  170. this.applyForce( force );
  171. };
  172. return proto;
  173. }));