Guest User

Untitled

a guest
Oct 23rd, 2017
65
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.06 KB | None | 0 0
  1. /* See LICENSE for terms of usage */
  2. (function(win, doc) {
  3.  
  4. // Number of pixels finger must move to determine horizontal or vertical motion
  5. var kLockThreshold = 10;
  6.  
  7. // Factor which reduces the length of motion by each move of the finger
  8. var kTouchMultiplier = 1;
  9.  
  10. // Maximum velocity for motion after user releases finger
  11. var kMaxVelocity = 720 / (win.devicePixelRatio||1);
  12.  
  13. // Rate of deceleration after user releases finger
  14. var kDecelRate = 350;
  15.  
  16. // Percentage of the page which content can be overscrolled before it must bounce back
  17. var kBounceLimit = 0.5;
  18.  
  19. // Rate of deceleration when content has overscrolled and is slowing down before bouncing back
  20. var kBounceDecelRate = 600;
  21.  
  22. // Duration of animation when bouncing back
  23. var kBounceTime = 80;
  24. var kPageBounceTime = 60;
  25.  
  26. // Percentage of viewport which must be scrolled past in order to snap to the next page
  27. var kPageLimit = 0.5;
  28.  
  29. // Velocity at which the animation will advance to the next page
  30. var kPageEscapeVelocity = 50;
  31.  
  32. // Vertical margin of scrollbar
  33. var kScrollbarMargin = 1;
  34.  
  35. // Time to scroll to top
  36. var kScrollToTopTime = 200;
  37.  
  38. var isWebkit = "webkitTransform" in doc.documentElement.style;
  39. var isFirefox = "MozTransform" in doc.documentElement.style;
  40. var isTouch = "ontouchstart" in win;
  41. var touchStartEvt = isTouch ? 'touchstart' : 'mousedown';
  42. var touchMoveEvt = isTouch ? 'touchmove' : 'mousemove';
  43. var touchEndEvt = isTouch ? 'touchend' : 'mouseup';
  44.  
  45. // ===============================================================================================
  46.  
  47. var startX, startY, touchX, touchY, touchDown, touchMoved, justChangedOrientation;
  48. var animationInterval = 0;
  49. var touchTargets = [];
  50.  
  51. var scrollers = {
  52. 'horizontal': createXTarget,
  53. 'vertical': createYTarget
  54. };
  55.  
  56. var scrollability = {
  57. globalScrolling: false,
  58. scrollers: scrollers,
  59.  
  60. flashIndicators: function() {
  61. var scrollables = doc.querySelectorAll('.scrollable.vertical');
  62. for (var i = 0; i < scrollables.length; ++i) {
  63. scrollability.scrollTo(scrollables[i], 0, 0, 20, true);
  64. }
  65. },
  66.  
  67. scrollToTop: function() {
  68. var scrollables = doc.getElementsByClassName('scrollable');
  69. if (scrollables.length) {
  70. var scrollable = scrollables[0];
  71. if (scrollable.className.indexOf('vertical') != -1) {
  72. scrollability.scrollTo(scrollable, 0, 0, kScrollToTopTime);
  73. }
  74. }
  75.  
  76. },
  77.  
  78. scrollTo: function(element, x, y, animationTime, muteDelegate) {
  79. stopAnimation();
  80.  
  81. var target = createTargetForElement(element);
  82. if (target) {
  83. if (muteDelegate) {
  84. target.delegate = null;
  85. }
  86. target = wrapTarget(target);
  87. touchTargets = [target];
  88. touchMoved = true;
  89. if (animationTime) {
  90. var orig = element[target.key];
  91. var dest = target.filter(x, y);
  92. var dir = dest - orig;
  93. var startTime = new Date().getTime();
  94. animationInterval = setInterval(function() {
  95. var d = new Date().getTime() - startTime;
  96. var pos = orig + ((dest-orig) * (d/animationTime));
  97. if ((dir < 0 && pos < dest) || (dir > 0 && pos > dest)) {
  98. pos = dest;
  99. }
  100. target.updater(pos);
  101. if (pos == dest) {
  102. clearInterval(animationInterval);
  103. setTimeout(stopAnimation, 200);
  104. }
  105. }, 20);
  106. } else {
  107. target.updater(y);
  108. stopAnimation();
  109. }
  110. }
  111. }
  112. };
  113.  
  114.  
  115. function init() {
  116. win.scrollability = scrollability;
  117.  
  118. doc.addEventListener(touchStartEvt, onTouchStart, false);
  119. doc.addEventListener('scroll', onScroll, false);
  120. doc.addEventListener('orientationchange', onOrientationChange, false);
  121. win.addEventListener('load', onLoad, false);
  122. }
  123.  
  124. function onLoad() {
  125. scrollability.flashIndicators();
  126. }
  127.  
  128. function onScroll(event) {
  129. setTimeout(function() {
  130. if (justChangedOrientation) {
  131. justChangedOrientation = false;
  132. } else if (isTouch) {
  133. scrollability.scrollToTop();
  134. }
  135. });
  136. }
  137.  
  138. function onOrientationChange(event) {
  139. justChangedOrientation = true;
  140. }
  141.  
  142. function onTouchStart(event) {
  143. stopAnimation();
  144.  
  145. var touchCandidate = event.target;
  146. var touch = event.touches ? event.touches[0] : event;
  147. var touched = null;
  148. var startTime = new Date().getTime();
  149.  
  150. touchX = startX = touch.clientX;
  151. touchY = startY = touch.clientY;
  152. touchDown = true;
  153. touchMoved = false;
  154.  
  155. touchTargets = getTouchTargets(event.target, touchX, touchY, startTime);
  156. if (!touchTargets.length && !scrollability.globalScrolling) {
  157. return true;
  158. }
  159.  
  160. var holdTimeout = setTimeout(function() {
  161. holdTimeout = 0;
  162. touched = setTouched(touchCandidate);
  163. }, 50);
  164.  
  165. doc.addEventListener(touchMoveEvt, onTouchMove, false);
  166. doc.addEventListener(touchEndEvt, onTouchEnd, false);
  167.  
  168. animationInterval = setInterval(touchAnimation, 0);
  169.  
  170. function onTouchMove(event) {
  171. event.preventDefault();
  172. touchMoved = true;
  173.  
  174. if (holdTimeout) {
  175. clearTimeout(holdTimeout);
  176. holdTimeout = 0;
  177. }
  178. if (touched) {
  179. releaseTouched(touched);
  180. touched = null;
  181. }
  182. var touch = event.touches ? event.touches[0] : event;
  183. touchX = touch.clientX;
  184. touchY = touch.clientY;
  185.  
  186. // Reduce the candidates down to the one whose axis follows the finger most closely
  187. if (touchTargets.length > 1) {
  188. for (var i = 0; i < touchTargets.length; ++i) {
  189. var target = touchTargets[i];
  190. if (target.disable && target.disable(touchX, touchY, startX, startY)) {
  191. target.terminator();
  192. touchTargets.splice(i, 1);
  193. break;
  194. }
  195. }
  196. }
  197. }
  198.  
  199. function onTouchEnd(event) {
  200.  
  201. if (isWebkit) {
  202. event.target.style.WebkitUserSelect = 'none';
  203. } else if (isFirefox) {
  204. event.target.style.MozUserSelect = 'none';
  205. }
  206. if (holdTimeout) {
  207. clearTimeout(holdTimeout);
  208. holdTimeout = 0;
  209. }
  210.  
  211. // Simulate a click event when releasing the finger
  212. if (touched) {
  213. var evt = doc.createEvent('MouseEvents');
  214. evt.initMouseEvent('click', true, true, win, 1);
  215. touched[0].dispatchEvent(evt);
  216. releaseTouched(touched);
  217. }
  218. doc.removeEventListener(touchMoveEvt, onTouchMove, false);
  219. doc.removeEventListener(touchEndEvt, onTouchEnd, false);
  220. touchDown = false;
  221. }
  222. }
  223.  
  224. function wrapTarget(target, startX, startY, startTime) {
  225. var delegate = target.delegate;
  226. var constrained = target.constrained;
  227. var paginated = target.paginated;
  228. var viewport = target.viewport || 0;
  229. var scrollbar = target.scrollbar;
  230. var position = target.node[target.key];
  231. var min = target.min;
  232. var max = target.max;
  233. var absMin = min;
  234. var absMax = Math.round(max/viewport)*viewport;
  235. var pageSpacing = 0;
  236. var velocity = 0;
  237. var decelerating = 0;
  238. var decelOrigin, decelDelta;
  239. var bounceTime = paginated ? kPageBounceTime : kBounceTime;
  240. var bounceLimit = target.bounce;
  241. var pageLimit = viewport * kPageLimit;
  242. var lastTouch = startTouch = target.filter(startX, startY);
  243. var lastTime = startTime;
  244. var stillTime = 0;
  245. var stillThreshold = 20;
  246. var snapped = false;
  247. var locked = false;
  248.  
  249. if (paginated) {
  250. var excess = Math.round(Math.abs(absMin) % viewport);
  251. var pageCount = ((Math.abs(absMin)-excess) / viewport)+1;
  252. var pageSpacing = excess / pageCount;
  253.  
  254. var positionSpacing = Math.round(position) % viewport;
  255. var pagePosition = Math.round((position-positionSpacing)/viewport) * viewport;
  256. min = max = Math.round(pagePosition + absMax)+positionSpacing;
  257. absMin += pageSpacing;
  258. }
  259.  
  260. if (delegate && delegate.onStartScroll) {
  261. if (!delegate.onStartScroll()) {
  262. return null;
  263. }
  264. }
  265.  
  266. if (scrollbar) {
  267. target.node.parentNode.appendChild(scrollbar);
  268. }
  269.  
  270. function animator(touch, time) {
  271. var deltaTime = 1 / (time - lastTime);
  272. lastTime = time;
  273.  
  274. var continues = true;
  275. if (touchDown) {
  276. var delta = (touch - lastTouch) * kTouchMultiplier;
  277. if (!delta) {
  278. // Heuristics to prevent out delta=0 changes from making velocity=0 and
  279. // stopping all motion in its tracks. We need to distinguish when the finger
  280. // has actually stopped moving from when the timer fired too quickly.
  281. if (!stillTime) {
  282. stillTime = time;
  283. }
  284. if (time - stillTime < stillThreshold) {
  285. return true;
  286. }
  287. } else {
  288. stillTime = 0;
  289. }
  290.  
  291. if (!locked && Math.abs(touch - startTouch) > kLockThreshold) {
  292. locked = true;
  293. if (delegate && delegate.onLockScroll) {
  294. delegate.onLockScroll(target.key);
  295. }
  296. }
  297.  
  298. lastTouch = touch;
  299. velocity = delta / deltaTime;
  300.  
  301. // Apply resistance along the edges
  302. if (position > max && absMax == max && constrained) {
  303. var excess = position - max;
  304. velocity *= (1.0 - excess / bounceLimit);
  305. } else if (position < min && absMin == min && constrained) {
  306. var excess = min - position;
  307. velocity *= (1.0 - excess / bounceLimit);
  308. }
  309. } else {
  310. if (paginated && !snapped) {
  311. // When finger is released, decide whether to jump to next/previous page
  312. // or to snap back to the current page
  313. snapped = true;
  314. if (Math.abs(position - max) > pageLimit || Math.abs(velocity) > kPageEscapeVelocity) {
  315. if (position > max) {
  316. if (max != absMax) {
  317. max += viewport+pageSpacing;
  318. min += viewport+pageSpacing;
  319. if (delegate && delegate.onScrollPage) {
  320. var totalSpacing = min % viewport;
  321. var page = -Math.round((position+viewport-totalSpacing)/viewport);
  322. delegate.onScrollPage(page, -1);
  323. }
  324. }
  325. } else {
  326. if (min != absMin) {
  327. max -= viewport+pageSpacing;
  328. min -= viewport+pageSpacing;
  329. if (delegate && delegate.onScrollPage) {
  330. var totalSpacing = min % viewport;
  331. var page = -Math.round((position-viewport-totalSpacing)/viewport);
  332. delegate.onScrollPage(page, 1);
  333. }
  334. }
  335. }
  336. }
  337. }
  338.  
  339. if (position > max && constrained) {
  340. if (velocity > 0) {
  341. // Slowing down
  342. var excess = position - max;
  343. var elasticity = (1.0 - excess / bounceLimit);
  344. velocity = Math.max(velocity - kBounceDecelRate * deltaTime, 0) * elasticity;
  345. decelerating = 0;
  346. } else {
  347. // Bouncing back
  348. if (!decelerating) {
  349. decelOrigin = position;
  350. decelDelta = max - position;
  351. }
  352.  
  353. position = easeOutExpo(decelerating, decelOrigin, decelDelta, bounceTime);
  354. return update(position, ++decelerating <= bounceTime && Math.floor(position) > max);
  355. }
  356. } else if (position < min && constrained) {
  357. if (velocity < 0) {
  358. // Slowing down
  359. var excess = min - position;
  360. var elasticity = (1.0 - excess / bounceLimit);
  361. velocity = Math.min(velocity + kBounceDecelRate * deltaTime, 0) * elasticity;
  362. decelerating = 0;
  363. } else {
  364. // Bouncing back
  365. if (!decelerating) {
  366. decelOrigin = position;
  367. decelDelta = min - position;
  368. }
  369. position = easeOutExpo(decelerating, decelOrigin, decelDelta, bounceTime);
  370. return update(position, ++decelerating <= bounceTime && Math.ceil(position) < min);
  371. }
  372. } else {
  373. // Slowing down
  374. if (!decelerating) {
  375. if (velocity < 0 && velocity < -kMaxVelocity) {
  376. velocity = -kMaxVelocity;
  377. } else if (velocity > 0 && velocity > kMaxVelocity) {
  378. velocity = kMaxVelocity;
  379. }
  380. decelOrigin = velocity;
  381. }
  382.  
  383. velocity = easeOutExpo(decelerating, decelOrigin, -decelOrigin, kDecelRate);
  384.  
  385. if (++decelerating > kDecelRate || Math.floor(velocity) == 0) {
  386. continues = false;
  387. }
  388. }
  389. }
  390.  
  391. position += velocity * deltaTime;
  392. return update(position, continues);
  393. }
  394.  
  395. function update(pos, continues) {
  396. position = pos;
  397.  
  398. target.node[target.key] = position;
  399. target.update(target.node, position);
  400.  
  401. if (delegate && delegate.onScroll) {
  402. delegate.onScroll(position);
  403. }
  404.  
  405. // Update the scrollbar
  406. var range = -min - max;
  407. if (scrollbar && viewport < range) {
  408. var viewable = viewport - kScrollbarMargin*2;
  409. var height = (viewable/range) * viewable;
  410. var scrollPosition = 0;
  411. if (position > max) {
  412. height = Math.max(height - (position-max), 7);
  413. scrollPosition = 0;
  414. } else if (position < min) {
  415. height = Math.max(height - (min - position), 7);
  416. scrollPosition = (viewable-height);
  417. } else {
  418. scrollPosition = Math.round((Math.abs(position) / range) * (viewable-height));
  419. }
  420. scrollPosition += kScrollbarMargin;
  421. scrollbar.style.height = Math.round(height) + 'px';
  422.  
  423. moveElement(scrollbar, 0, Math.round(scrollPosition));
  424.  
  425. if (touchMoved) {
  426. scrollbar.style.webkitTransition = 'none';
  427. scrollbar.style.opacity = '1';
  428. }
  429. }
  430.  
  431. return continues;
  432. }
  433.  
  434. function terminator() {
  435. // Snap to the integer endpoint, since position may be a subpixel value while animating
  436. if (paginated) {
  437. var pageIndex = Math.round(position/viewport);
  438. update(pageIndex * (viewport+pageSpacing));
  439. } else if (position > max && constrained) {
  440. update(max);
  441. } else if (position < min && constrained) {
  442. update(min);
  443. }
  444.  
  445. // Hide the scrollbar
  446. if (scrollbar) {
  447. scrollbar.style.opacity = '0';
  448. scrollbar.style.webkitTransition = 'opacity 0.33s linear';
  449. }
  450. if (delegate && delegate.onEndScroll) {
  451. delegate.onEndScroll();
  452. }
  453. }
  454.  
  455. target.updater = update;
  456. target.animator = animator;
  457. target.terminator = terminator;
  458. return target;
  459. }
  460.  
  461. function touchAnimation() {
  462. var time = new Date().getTime();
  463.  
  464. // Animate each of the targets
  465. for (var i = 0; i < touchTargets.length; ++i) {
  466. var target = touchTargets[i];
  467.  
  468. // Translate the x/y touch into the value needed by each of the targets
  469. var touch = target.filter(touchX, touchY);
  470. if (!target.animator(touch, time)) {
  471. target.terminator();
  472. touchTargets.splice(i--, 1);
  473. }
  474. }
  475.  
  476. if (!touchTargets.length) {
  477. stopAnimation();
  478. }
  479. }
  480.  
  481. // *************************************************************************************************
  482.  
  483. function getTouchTargets(node, touchX, touchY, startTime) {
  484. var targets = [];
  485. findTargets(node, targets, touchX, touchY, startTime);
  486.  
  487. var candidates = doc.querySelectorAll('.scrollable.global');
  488. for (var j = 0; j < candidates.length; ++j) {
  489. findTargets(candidates[j], targets, touchX, touchY, startTime);
  490. }
  491. return targets;
  492. }
  493.  
  494. function findTargets(element, targets, touchX, touchY, startTime) {
  495. while (element) {
  496. if (element.nodeType == 1) {
  497. var target = createTargetForElement(element, touchX, touchY, startTime);
  498. if (target) {
  499. // Look out for duplicates
  500. var exists = false;
  501. for (var j = 0; j < targets.length; ++j) {
  502. if (targets[j].node == element) {
  503. exists = true;
  504. break;
  505. }
  506. }
  507. if (!exists) {
  508. target = wrapTarget(target, touchX, touchY, startTime);
  509. if (target) {
  510. targets.push(target);
  511. }
  512. }
  513. }
  514. }
  515. element = element.parentNode;
  516. }
  517. }
  518.  
  519. function createTargetForElement(element, touchX, touchY, startTime) {
  520. var classes = element.className.split(' ');
  521. for (var i = 0; i < classes.length; ++i) {
  522. var name = classes[i];
  523. if (scrollers[name]) {
  524. var target = scrollers[name](element);
  525. target.key = 'scrollable_'+name;
  526. target.paginated = classes.indexOf('paginated') != -1;
  527. if (!(target.key in element)) {
  528. element[target.key] = target.initial ? target.initial(element) : 0;
  529. }
  530. return target;
  531. }
  532. }
  533. }
  534.  
  535. function setTouched(target) {
  536. var touched = [];
  537. for (var n = target; n; n = n.parentNode) {
  538. if (n.nodeType == 1) {
  539. n.className = (n.className ? n.className + ' ' : '') + 'touched';
  540. touched.push(n);
  541. }
  542. }
  543. return touched;
  544. }
  545.  
  546. function releaseTouched(touched) {
  547. for (var i = 0; i < touched.length; ++i) {
  548. var n = touched[i];
  549. n.className = n.className.replace('touched', '');
  550. }
  551. }
  552.  
  553. function stopAnimation() {
  554. if (animationInterval) {
  555. clearInterval(animationInterval);
  556. animationInterval = 0;
  557.  
  558. for (var i = 0; i < touchTargets.length; ++i) {
  559. var target = touchTargets[i];
  560. target.terminator();
  561. }
  562. touchTargets = [];
  563. }
  564. }
  565.  
  566. function moveElement(element, x, y) {
  567. if (isWebkit) {
  568. element.style.webkitTransform = 'translate3d('
  569. +(x ? (x+'px') : '0')+','
  570. +(y ? (y+'px') : '0')+','
  571. +'0)';
  572. element.style.WebkitUserSelect = 'none';
  573. } else if (isFirefox) {
  574. element.style.MozTransform = 'translate('
  575. +(x ? (x+'px') : '0')+','
  576. +(y ? (y+'px') : '0')+')';
  577. element.style.MozUserSelect = 'none';
  578. }
  579. }
  580.  
  581. function initScrollbar(element) {
  582. if (!element.scrollableScrollbar) {
  583. var scrollbar = element.scrollableScrollbar = doc.createElement('div');
  584. scrollbar.className = 'scrollableScrollbar';
  585.  
  586. // We hardcode this CSS here to avoid having to provide a CSS file
  587. scrollbar.style.cssText = [
  588. 'position: absolute',
  589. 'top: 0',
  590. 'right: 1px',
  591. 'width: 7px',
  592. 'min-height: 7px',
  593. 'opacity: 0',
  594. '-webkit-transform: translate3d(0,0,0)',
  595. '-webkit-box-sizing: border-box',
  596. '-webkit-border-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUhJREFUeNp0Ur1OwzAQtt1CaZQQgUjDhuicrEwoqjJlzpBAXoIHywtkcwfECyQPwIgKQkoyFJWq5k6cJcsUS5/sO993/1wpxazjAU4BJyR/A3aA0TSaGu85kbSO0y0AM/pH8lYr8ZwBLpBUluVtGIaPjuM8IYIgeEAdObwkB4xTqgv8iOP4vuu6lZEFRkUDHkWRbNv2mVJ/x4g+1pPn+RJICRlzk4Q3/lVVdUP1nwtqgpJSYqQJGbMj96RpmhXJM01kwzBcWU2x36zv+wXppro5TAihvat/HCjxa6R0V7FY5rruhx3BTtfzvDeS95rI0zSVcB+MpijL0SHLsjW9d3ocIRZvjINbKSsYx5rGsQdsNHFOC8CKolhCh+/GcbxG2ff9TZIkL3Vdv5KjT8AXN3b12MqZi4yRBiTZu7olmEvOacH/LPmPAAMA2bZzzeYUC40AAAAASUVORK5CYII=") 6 2 6 2 / 3px 1px 3px 1px round round',
  597. 'z-index: 2147483647',
  598. ].join(';');
  599. }
  600. return element.scrollableScrollbar;
  601. }
  602.  
  603. function easeOutExpo(t, b, c, d) {
  604. return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
  605. }
  606.  
  607. // *************************************************************************************************
  608.  
  609. function createXTarget(element) {
  610. var parent = element.parentNode;
  611. return {
  612. node: element,
  613. min: -parent.scrollWidth + parent.offsetWidth,
  614. max: 0,
  615. viewport: parent.offsetWidth,
  616. bounce: parent.offsetWidth * kBounceLimit,
  617. constrained: true,
  618. delegate: element.scrollDelegate,
  619.  
  620. filter: function(x, y) {
  621. return x;
  622. },
  623.  
  624. disable: function (x, y, startX, startY) {
  625. var dx = Math.abs(x - startX);
  626. var dy = Math.abs(y - startY);
  627. if (dy > dx && dy > kLockThreshold) {
  628. return true;
  629. }
  630. },
  631.  
  632. update: function(element, position) {
  633. moveElement(element, position, element.scrollable_vertical||0);
  634. }
  635. };
  636. }
  637.  
  638. function createYTarget(element) {
  639. var parent = element.parentNode;
  640. return {
  641. node: element,
  642. scrollbar: initScrollbar(element),
  643. min: -parent.scrollHeight + parent.offsetHeight,
  644. max: 0,
  645. viewport: parent.offsetHeight,
  646. bounce: parent.offsetHeight * kBounceLimit,
  647. constrained: true,
  648. delegate: element.scrollDelegate,
  649.  
  650. filter: function(x, y) {
  651. return y;
  652. },
  653.  
  654. disable: function(x, y, startX, startY) {
  655. var dx = Math.abs(x - startX);
  656. var dy = Math.abs(y - startY);
  657. if (dx > dy && dx > kLockThreshold) {
  658. return true;
  659. }
  660. },
  661.  
  662. update: function(element, position) {
  663. moveElement(element, element.scrollable_horizontal||0, position);
  664. }
  665. };
  666. }
  667.  
  668. init();
  669.  
  670. })(this, document);
Add Comment
Please, Sign In to add comment