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.

scrolling.ts 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { debounce } from 'lodash';
  21. const SCROLLING_DURATION = 100;
  22. const SCROLLING_INTERVAL = 10;
  23. const SCROLLING_STEPS = SCROLLING_DURATION / SCROLLING_INTERVAL;
  24. function isWindow(element: Element | Window): element is Window {
  25. return element === window;
  26. }
  27. function getScrollPosition(element: Element | Window): number {
  28. return isWindow(element) ? window.pageYOffset : element.scrollTop;
  29. }
  30. function scrollElement(element: Element | Window, position: number): void {
  31. if (isWindow(element)) {
  32. window.scrollTo(0, position);
  33. } else {
  34. element.scrollTop = position;
  35. }
  36. }
  37. let smoothScrollTop = (y: number, parent: Element | Window) => {
  38. let scrollTop = getScrollPosition(parent);
  39. const scrollingDown = y > scrollTop;
  40. const step = Math.ceil(Math.abs(y - scrollTop) / SCROLLING_STEPS);
  41. let stepsDone = 0;
  42. const interval = setInterval(() => {
  43. if (scrollTop === y || SCROLLING_STEPS === stepsDone) {
  44. clearInterval(interval);
  45. } else {
  46. let goal;
  47. if (scrollingDown) {
  48. goal = Math.min(y, scrollTop + step);
  49. } else {
  50. goal = Math.max(y, scrollTop - step);
  51. }
  52. stepsDone++;
  53. scrollTop = goal;
  54. scrollElement(parent, goal);
  55. }
  56. }, SCROLLING_INTERVAL);
  57. };
  58. smoothScrollTop = debounce(smoothScrollTop, SCROLLING_DURATION, { leading: true });
  59. export function scrollToElement(
  60. element: Element,
  61. options: {
  62. topOffset?: number;
  63. bottomOffset?: number;
  64. parent?: Element;
  65. smooth?: boolean;
  66. }
  67. ): void {
  68. const opts = { topOffset: 0, bottomOffset: 0, parent: window, smooth: true, ...options };
  69. const { parent } = opts;
  70. const { top, bottom } = element.getBoundingClientRect();
  71. const scrollTop = getScrollPosition(parent);
  72. const height: number = isWindow(parent)
  73. ? window.innerHeight
  74. : parent.getBoundingClientRect().height;
  75. const parentTop = isWindow(parent) ? 0 : parent.getBoundingClientRect().top;
  76. if (top - parentTop < opts.topOffset) {
  77. const goal = scrollTop - opts.topOffset + top - parentTop;
  78. if (opts.smooth) {
  79. smoothScrollTop(goal, parent);
  80. } else {
  81. scrollElement(parent, goal);
  82. }
  83. }
  84. if (bottom - parentTop > height - opts.bottomOffset) {
  85. const goal = scrollTop + bottom - parentTop - height + opts.bottomOffset;
  86. if (opts.smooth) {
  87. smoothScrollTop(goal, parent);
  88. } else {
  89. scrollElement(parent, goal);
  90. }
  91. }
  92. }