You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

256 lines
9.2 KiB

  1. import { __assign, __extends } from "tslib";
  2. import clamp from 'lodash-es/clamp';
  3. import debounce from 'lodash-es/debounce';
  4. import { ScrollbarPlugin } from 'smooth-scrollbar';
  5. import { Bounce } from './bounce';
  6. import { Glow } from './glow';
  7. export var OverscrollEffect;
  8. (function (OverscrollEffect) {
  9. OverscrollEffect["BOUNCE"] = "bounce";
  10. OverscrollEffect["GLOW"] = "glow";
  11. })(OverscrollEffect || (OverscrollEffect = {}));
  12. var ALLOWED_EVENTS = /wheel|touch/;
  13. var OverscrollPlugin = /** @class */ (function (_super) {
  14. __extends(OverscrollPlugin, _super);
  15. function OverscrollPlugin() {
  16. var _this = _super !== null && _super.apply(this, arguments) || this;
  17. _this._glow = new Glow(_this.scrollbar);
  18. _this._bounce = new Bounce(_this.scrollbar);
  19. _this._wheelScrollBack = {
  20. x: false,
  21. y: false,
  22. };
  23. _this._lockWheel = {
  24. x: false,
  25. y: false,
  26. };
  27. _this._touching = false;
  28. _this._amplitude = {
  29. x: 0,
  30. y: 0,
  31. };
  32. _this._position = {
  33. x: 0,
  34. y: 0,
  35. };
  36. // since we can't detect whether user release touchpad
  37. // handle it with debounce is the best solution now, as a trade-off
  38. _this._releaseWheel = debounce(function () {
  39. _this._lockWheel.x = false;
  40. _this._lockWheel.y = false;
  41. }, 30);
  42. return _this;
  43. }
  44. Object.defineProperty(OverscrollPlugin.prototype, "_isWheelLocked", {
  45. get: function () {
  46. return this._lockWheel.x || this._lockWheel.y;
  47. },
  48. enumerable: true,
  49. configurable: true
  50. });
  51. Object.defineProperty(OverscrollPlugin.prototype, "_enabled", {
  52. get: function () {
  53. return !!this.options.effect;
  54. },
  55. enumerable: true,
  56. configurable: true
  57. });
  58. OverscrollPlugin.prototype.onInit = function () {
  59. var _a = this, _glow = _a._glow, options = _a.options, scrollbar = _a.scrollbar;
  60. // observe
  61. var effect = options.effect;
  62. Object.defineProperty(options, 'effect', {
  63. get: function () {
  64. return effect;
  65. },
  66. set: function (val) {
  67. if (!val) {
  68. effect = undefined;
  69. return;
  70. }
  71. if (val !== OverscrollEffect.BOUNCE && val !== OverscrollEffect.GLOW) {
  72. throw new TypeError("unknow overscroll effect: " + val);
  73. }
  74. effect = val;
  75. scrollbar.options.continuousScrolling = false;
  76. if (val === OverscrollEffect.GLOW) {
  77. _glow.mount();
  78. _glow.adjust();
  79. }
  80. else {
  81. _glow.unmount();
  82. }
  83. },
  84. });
  85. options.effect = effect; // init
  86. };
  87. OverscrollPlugin.prototype.onUpdate = function () {
  88. if (this.options.effect === OverscrollEffect.GLOW) {
  89. this._glow.adjust();
  90. }
  91. };
  92. OverscrollPlugin.prototype.onRender = function (remainMomentum) {
  93. if (!this._enabled) {
  94. return;
  95. }
  96. if (this.scrollbar.options.continuousScrolling) {
  97. // turn off continuous scrolling
  98. this.scrollbar.options.continuousScrolling = false;
  99. }
  100. var nextX = remainMomentum.x, nextY = remainMomentum.y;
  101. // transfer remain momentum to overscroll
  102. if (!this._amplitude.x &&
  103. this._willOverscroll('x', remainMomentum.x)) {
  104. nextX = 0;
  105. this._absorbMomentum('x', remainMomentum.x);
  106. }
  107. if (!this._amplitude.y &&
  108. this._willOverscroll('y', remainMomentum.y)) {
  109. nextY = 0;
  110. this._absorbMomentum('y', remainMomentum.y);
  111. }
  112. this.scrollbar.setMomentum(nextX, nextY);
  113. this._render();
  114. };
  115. OverscrollPlugin.prototype.transformDelta = function (delta, fromEvent) {
  116. this._lastEventType = fromEvent.type;
  117. if (!this._enabled || !ALLOWED_EVENTS.test(fromEvent.type)) {
  118. return delta;
  119. }
  120. if (this._isWheelLocked && /wheel/.test(fromEvent.type)) {
  121. this._releaseWheel();
  122. if (this._willOverscroll('x', delta.x)) {
  123. delta.x = 0;
  124. }
  125. if (this._willOverscroll('y', delta.y)) {
  126. delta.y = 0;
  127. }
  128. }
  129. var nextX = delta.x, nextY = delta.y;
  130. if (this._willOverscroll('x', delta.x)) {
  131. nextX = 0;
  132. this._addAmplitude('x', delta.x);
  133. }
  134. if (this._willOverscroll('y', delta.y)) {
  135. nextY = 0;
  136. this._addAmplitude('y', delta.y);
  137. }
  138. switch (fromEvent.type) {
  139. case 'touchstart':
  140. case 'touchmove':
  141. this._touching = true;
  142. this._glow.recordTouch(fromEvent);
  143. break;
  144. case 'touchcancel':
  145. case 'touchend':
  146. this._touching = false;
  147. break;
  148. }
  149. return {
  150. x: nextX,
  151. y: nextY,
  152. };
  153. };
  154. OverscrollPlugin.prototype._willOverscroll = function (direction, delta) {
  155. if (!delta) {
  156. return false;
  157. }
  158. // away from origin
  159. if (this._position[direction]) {
  160. return true;
  161. }
  162. var offset = this.scrollbar.offset[direction];
  163. var limit = this.scrollbar.limit[direction];
  164. if (limit === 0) {
  165. return false;
  166. }
  167. // cond:
  168. // 1. next scrolling position is supposed to stay unchange
  169. // 2. current position is on the edge
  170. return clamp(offset + delta, 0, limit) === offset &&
  171. (offset === 0 || offset === limit);
  172. };
  173. OverscrollPlugin.prototype._absorbMomentum = function (direction, remainMomentum) {
  174. var _a = this, options = _a.options, _lastEventType = _a._lastEventType, _amplitude = _a._amplitude;
  175. if (!ALLOWED_EVENTS.test(_lastEventType)) {
  176. return;
  177. }
  178. _amplitude[direction] = clamp(remainMomentum, -options.maxOverscroll, options.maxOverscroll);
  179. };
  180. OverscrollPlugin.prototype._addAmplitude = function (direction, delta) {
  181. var _a = this, options = _a.options, scrollbar = _a.scrollbar, _amplitude = _a._amplitude, _position = _a._position;
  182. var currentAmp = _amplitude[direction];
  183. var isOpposite = delta * currentAmp < 0;
  184. var friction;
  185. if (isOpposite) {
  186. // opposite direction
  187. friction = 0;
  188. }
  189. else {
  190. friction = this._wheelScrollBack[direction] ?
  191. 1 : Math.abs(currentAmp / options.maxOverscroll);
  192. }
  193. var amp = currentAmp + delta * (1 - friction);
  194. _amplitude[direction] = scrollbar.offset[direction] === 0 ?
  195. /* top | left */ clamp(amp, -options.maxOverscroll, 0) :
  196. /* bottom | right */ clamp(amp, 0, options.maxOverscroll);
  197. if (isOpposite) {
  198. // scroll back
  199. _position[direction] = _amplitude[direction];
  200. }
  201. };
  202. OverscrollPlugin.prototype._render = function () {
  203. var _a = this, options = _a.options, _amplitude = _a._amplitude, _position = _a._position;
  204. if (this._enabled &&
  205. (_amplitude.x || _amplitude.y || _position.x || _position.y)) {
  206. var nextX = this._nextAmp('x');
  207. var nextY = this._nextAmp('y');
  208. _amplitude.x = nextX.amplitude;
  209. _position.x = nextX.position;
  210. _amplitude.y = nextY.amplitude;
  211. _position.y = nextY.position;
  212. switch (options.effect) {
  213. case OverscrollEffect.BOUNCE:
  214. this._bounce.render(_position);
  215. break;
  216. case OverscrollEffect.GLOW:
  217. this._glow.render(_position, this.options.glowColor);
  218. break;
  219. }
  220. if (typeof options.onScroll === 'function') {
  221. options.onScroll.call(this, __assign({}, _position));
  222. }
  223. }
  224. };
  225. OverscrollPlugin.prototype._nextAmp = function (direction) {
  226. var _a = this, options = _a.options, _amplitude = _a._amplitude, _position = _a._position;
  227. var t = 1 - options.damping;
  228. var amp = _amplitude[direction];
  229. var pos = _position[direction];
  230. var nextAmp = this._touching ? amp : (amp * t | 0);
  231. var distance = nextAmp - pos;
  232. var nextPos = pos + distance - (distance * t | 0);
  233. if (!this._touching && Math.abs(nextPos) < Math.abs(pos)) {
  234. this._wheelScrollBack[direction] = true;
  235. }
  236. if (this._wheelScrollBack[direction] && Math.abs(nextPos) <= 1) {
  237. this._wheelScrollBack[direction] = false;
  238. this._lockWheel[direction] = true;
  239. }
  240. return {
  241. amplitude: nextAmp,
  242. position: nextPos,
  243. };
  244. };
  245. OverscrollPlugin.pluginName = 'overscroll';
  246. OverscrollPlugin.defaultOptions = {
  247. effect: OverscrollEffect.BOUNCE,
  248. onScroll: undefined,
  249. damping: 0.2,
  250. maxOverscroll: 150,
  251. glowColor: '#87ceeb',
  252. };
  253. return OverscrollPlugin;
  254. }(ScrollbarPlugin));
  255. export default OverscrollPlugin;
  256. //# sourceMappingURL=index.js.map