index.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { VantComponent } from '../common/component';
  2. import { GREEN } from '../common/color';
  3. import { pageScrollMixin } from '../mixins/page-scroll';
  4. const indexList = () => {
  5. const indexList = [];
  6. const charCodeOfA = 'A'.charCodeAt(0);
  7. for (let i = 0; i < 26; i++) {
  8. indexList.push(String.fromCharCode(charCodeOfA + i));
  9. }
  10. return indexList;
  11. };
  12. VantComponent({
  13. relation: {
  14. name: 'index-anchor',
  15. type: 'descendant',
  16. current: 'index-bar',
  17. linked() {
  18. this.updateData();
  19. },
  20. unlinked() {
  21. this.updateData();
  22. },
  23. },
  24. props: {
  25. sticky: {
  26. type: Boolean,
  27. value: true,
  28. },
  29. zIndex: {
  30. type: Number,
  31. value: 1,
  32. },
  33. highlightColor: {
  34. type: String,
  35. value: GREEN,
  36. },
  37. stickyOffsetTop: {
  38. type: Number,
  39. value: 0,
  40. },
  41. indexList: {
  42. type: Array,
  43. value: indexList(),
  44. },
  45. },
  46. mixins: [
  47. pageScrollMixin(function (event) {
  48. this.scrollTop = event.scrollTop || 0;
  49. this.onScroll();
  50. }),
  51. ],
  52. data: {
  53. activeAnchorIndex: null,
  54. showSidebar: false,
  55. },
  56. created() {
  57. this.scrollTop = 0;
  58. },
  59. methods: {
  60. updateData() {
  61. wx.nextTick(() => {
  62. if (this.timer != null) {
  63. clearTimeout(this.timer);
  64. }
  65. this.timer = setTimeout(() => {
  66. this.setData({
  67. showSidebar: !!this.children.length,
  68. });
  69. this.setRect().then(() => {
  70. this.onScroll();
  71. });
  72. }, 0);
  73. });
  74. },
  75. setRect() {
  76. return Promise.all([
  77. this.setAnchorsRect(),
  78. this.setListRect(),
  79. this.setSiderbarRect(),
  80. ]);
  81. },
  82. setAnchorsRect() {
  83. return Promise.all(
  84. this.children.map((anchor) =>
  85. anchor.getRect('.van-index-anchor-wrapper').then((rect) => {
  86. Object.assign(anchor, {
  87. height: rect.height,
  88. top: rect.top + this.scrollTop,
  89. });
  90. })
  91. )
  92. );
  93. },
  94. setListRect() {
  95. return this.getRect('.van-index-bar').then((rect) => {
  96. Object.assign(this, {
  97. height: rect.height,
  98. top: rect.top + this.scrollTop,
  99. });
  100. });
  101. },
  102. setSiderbarRect() {
  103. return this.getRect('.van-index-bar__sidebar').then((res) => {
  104. this.sidebar = {
  105. height: res.height,
  106. top: res.top,
  107. };
  108. });
  109. },
  110. setDiffData({ target, data }) {
  111. const diffData = {};
  112. Object.keys(data).forEach((key) => {
  113. if (target.data[key] !== data[key]) {
  114. diffData[key] = data[key];
  115. }
  116. });
  117. if (Object.keys(diffData).length) {
  118. target.setData(diffData);
  119. }
  120. },
  121. getAnchorRect(anchor) {
  122. return anchor.getRect('.van-index-anchor-wrapper').then((rect) => ({
  123. height: rect.height,
  124. top: rect.top,
  125. }));
  126. },
  127. getActiveAnchorIndex() {
  128. const { children, scrollTop } = this;
  129. const { sticky, stickyOffsetTop } = this.data;
  130. for (let i = this.children.length - 1; i >= 0; i--) {
  131. const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
  132. const reachTop = sticky ? preAnchorHeight + stickyOffsetTop : 0;
  133. if (reachTop + scrollTop >= children[i].top) {
  134. return i;
  135. }
  136. }
  137. return -1;
  138. },
  139. onScroll() {
  140. const { children = [], scrollTop } = this;
  141. if (!children.length) {
  142. return;
  143. }
  144. const { sticky, stickyOffsetTop, zIndex, highlightColor } = this.data;
  145. const active = this.getActiveAnchorIndex();
  146. this.setDiffData({
  147. target: this,
  148. data: {
  149. activeAnchorIndex: active,
  150. },
  151. });
  152. if (sticky) {
  153. let isActiveAnchorSticky = false;
  154. if (active !== -1) {
  155. isActiveAnchorSticky =
  156. children[active].top <= stickyOffsetTop + scrollTop;
  157. }
  158. children.forEach((item, index) => {
  159. if (index === active) {
  160. let wrapperStyle = '';
  161. let anchorStyle = `
  162. color: ${highlightColor};
  163. `;
  164. if (isActiveAnchorSticky) {
  165. wrapperStyle = `
  166. height: ${children[index].height}px;
  167. `;
  168. anchorStyle = `
  169. position: fixed;
  170. top: ${stickyOffsetTop}px;
  171. z-index: ${zIndex};
  172. color: ${highlightColor};
  173. `;
  174. }
  175. this.setDiffData({
  176. target: item,
  177. data: {
  178. active: true,
  179. anchorStyle,
  180. wrapperStyle,
  181. },
  182. });
  183. } else if (index === active - 1) {
  184. const currentAnchor = children[index];
  185. const currentOffsetTop = currentAnchor.top;
  186. const targetOffsetTop =
  187. index === children.length - 1
  188. ? this.top
  189. : children[index + 1].top;
  190. const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
  191. const translateY = parentOffsetHeight - currentAnchor.height;
  192. const anchorStyle = `
  193. position: relative;
  194. transform: translate3d(0, ${translateY}px, 0);
  195. z-index: ${zIndex};
  196. color: ${highlightColor};
  197. `;
  198. this.setDiffData({
  199. target: item,
  200. data: {
  201. active: true,
  202. anchorStyle,
  203. },
  204. });
  205. } else {
  206. this.setDiffData({
  207. target: item,
  208. data: {
  209. active: false,
  210. anchorStyle: '',
  211. wrapperStyle: '',
  212. },
  213. });
  214. }
  215. });
  216. }
  217. },
  218. onClick(event) {
  219. this.scrollToAnchor(event.target.dataset.index);
  220. },
  221. onTouchMove(event) {
  222. const sidebarLength = this.children.length;
  223. const touch = event.touches[0];
  224. const itemHeight = this.sidebar.height / sidebarLength;
  225. let index = Math.floor((touch.clientY - this.sidebar.top) / itemHeight);
  226. if (index < 0) {
  227. index = 0;
  228. } else if (index > sidebarLength - 1) {
  229. index = sidebarLength - 1;
  230. }
  231. this.scrollToAnchor(index);
  232. },
  233. onTouchStop() {
  234. this.scrollToAnchorIndex = null;
  235. },
  236. scrollToAnchor(index) {
  237. if (typeof index !== 'number' || this.scrollToAnchorIndex === index) {
  238. return;
  239. }
  240. this.scrollToAnchorIndex = index;
  241. const anchor = this.children.find(
  242. (item) => item.data.index === this.data.indexList[index]
  243. );
  244. if (anchor) {
  245. anchor.scrollIntoView(this.scrollTop);
  246. this.$emit('select', anchor.data.index);
  247. }
  248. },
  249. },
  250. });