useScrollTo.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import raf from '../../_util/raf';
  2. export default function useScrollTo(containerRef, mergedData, heights, props, getKey, collectHeight, syncScrollTop, triggerFlash) {
  3. let scroll;
  4. return arg => {
  5. // When not argument provided, we think dev may want to show the scrollbar
  6. if (arg === null || arg === undefined) {
  7. triggerFlash();
  8. return;
  9. }
  10. // Normal scroll logic
  11. raf.cancel(scroll);
  12. const data = mergedData.value;
  13. const itemHeight = props.itemHeight;
  14. if (typeof arg === 'number') {
  15. syncScrollTop(arg);
  16. } else if (arg && typeof arg === 'object') {
  17. let index;
  18. const {
  19. align
  20. } = arg;
  21. if ('index' in arg) {
  22. ({
  23. index
  24. } = arg);
  25. } else {
  26. index = data.findIndex(item => getKey(item) === arg.key);
  27. }
  28. const {
  29. offset = 0
  30. } = arg;
  31. // We will retry 3 times in case dynamic height shaking
  32. const syncScroll = (times, targetAlign) => {
  33. if (times < 0 || !containerRef.value) return;
  34. const height = containerRef.value.clientHeight;
  35. let needCollectHeight = false;
  36. let newTargetAlign = targetAlign;
  37. // Go to next frame if height not exist
  38. if (height) {
  39. const mergedAlign = targetAlign || align;
  40. // Get top & bottom
  41. let stackTop = 0;
  42. let itemTop = 0;
  43. let itemBottom = 0;
  44. const maxLen = Math.min(data.length, index);
  45. for (let i = 0; i <= maxLen; i += 1) {
  46. const key = getKey(data[i]);
  47. itemTop = stackTop;
  48. const cacheHeight = heights.get(key);
  49. itemBottom = itemTop + (cacheHeight === undefined ? itemHeight : cacheHeight);
  50. stackTop = itemBottom;
  51. if (i === index && cacheHeight === undefined) {
  52. needCollectHeight = true;
  53. }
  54. }
  55. const scrollTop = containerRef.value.scrollTop;
  56. // Scroll to
  57. let targetTop = null;
  58. switch (mergedAlign) {
  59. case 'top':
  60. targetTop = itemTop - offset;
  61. break;
  62. case 'bottom':
  63. targetTop = itemBottom - height + offset;
  64. break;
  65. default:
  66. {
  67. const scrollBottom = scrollTop + height;
  68. if (itemTop < scrollTop) {
  69. newTargetAlign = 'top';
  70. } else if (itemBottom > scrollBottom) {
  71. newTargetAlign = 'bottom';
  72. }
  73. }
  74. }
  75. if (targetTop !== null && targetTop !== scrollTop) {
  76. syncScrollTop(targetTop);
  77. }
  78. }
  79. // We will retry since element may not sync height as it described
  80. scroll = raf(() => {
  81. if (needCollectHeight) {
  82. collectHeight();
  83. }
  84. syncScroll(times - 1, newTargetAlign);
  85. }, 2);
  86. };
  87. syncScroll(5);
  88. }
  89. };
  90. }