index.vue 9.4 KB

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