UNPKG

@gyenno/nutui-taro

Version:

京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)

280 lines (279 loc) 9.17 kB
import { ref, reactive, computed, watch, toRefs, openBlock, createElementBlock, normalizeClass, normalizeStyle, createElementVNode, createTextVNode, Fragment, renderList, renderSlot } from "vue"; import { c as createComponent } from "./component-25dcca32.js"; import Taro from "@tarojs/taro"; import { u as useTaroRect } from "./index-7fb26863.js"; import { _ as _export_sfc } from "./_plugin-vue_export-helper-cc2b3d55.js"; import "../locale/lang"; var CompareResult = /* @__PURE__ */ ((CompareResult2) => { CompareResult2[CompareResult2["eq"] = 1] = "eq"; CompareResult2[CompareResult2["lt"] = 2] = "lt"; CompareResult2[CompareResult2["gt"] = 3] = "gt"; return CompareResult2; })(CompareResult || {}); function binarySearch(list, value, compareFunc) { let start = 0; let end = list.length - 1; let tempIndex = null; while (start <= end) { tempIndex = Math.floor((start + end) / 2); const midValue = list[tempIndex]; const compareRes = compareFunc(midValue, value); if (compareRes === 1) { return tempIndex; } if (compareRes === 2) { start = tempIndex + 1; } else if (compareRes === 3) { end = tempIndex - 1; } } return tempIndex; } const { componentName, create } = createComponent("list"); const clientHeight = Taro.getSystemInfoSync().windowHeight || 667; const _sfc_main = create({ props: { height: { type: [Number], default: 50 }, listData: { type: Array, default: () => { return []; } }, bufferSize: { type: Number, default: 5 }, containerHeight: { type: [Number], default: clientHeight }, estimateRowHeight: { type: Number, default: 80 }, margin: { type: Number, default: 10 } }, emits: ["scroll-up", "scroll-down", "scroll-bottom"], setup(props, { emit }) { const list = ref(null); const phantom = ref(null); const actualContent = ref(null); const refRandomId = Math.random().toString(36).slice(-8); const state = reactive({ start: 0, originStartIndex: 0, scrollTop: 0, list: props.listData.slice(), cachePositions: [], phantomHeight: props.estimateRowHeight * props.listData.length }); const getContainerHeight = computed(() => { return Math.min(props.containerHeight, clientHeight); }); const visibleCount = computed(() => { return Math.ceil(getContainerHeight.value / props.estimateRowHeight); }); const end = computed(() => { return Math.min(state.originStartIndex + visibleCount.value + props.bufferSize, state.list.length); }); const classes = computed(() => { const prefixCls = componentName; return { [prefixCls]: true }; }); const visibleData = computed(() => { return state.list.slice(state.start, end.value); }); const getTransform = () => { if (actualContent.value) { return `translate3d(0, ${state.start >= 1 ? state.cachePositions[state.start - 1].bottom : 0}px, 0)`; } }; const initCachedPosition = () => { state.cachePositions = []; for (let i = 0; i < state.list.length; ++i) { state.cachePositions[i] = { index: i, height: props.estimateRowHeight, top: i * props.estimateRowHeight, bottom: (i + 1) * (props.estimateRowHeight + props.margin), dValue: 0 }; } }; const updateCachedPosition = () => { let nodes = actualContent.value.childNodes; nodes = Array.from(nodes).filter((node) => node.nodeType === 1); const start = nodes[0]; nodes.forEach(async (node, index) => { if (!node) return; const rect = await useTaroRect(node); if (rect && rect.height) { const { height: height2 } = rect; const oldHeight = state.cachePositions[index + state.start] ? state.cachePositions[index + state.start].height : props.height; const dValue = oldHeight - height2; if (dValue && state.cachePositions[index + state.start]) { state.cachePositions[index + state.start].bottom -= dValue; state.cachePositions[index + state.start].height = height2; state.cachePositions[index + state.start].dValue = dValue; } } }); let startIndex = 0; if (start) { startIndex = state.start; } const cachedPositionsLen = state.cachePositions.length; let cumulativeDiffHeight = state.cachePositions[startIndex].dValue; state.cachePositions[startIndex].dValue = 0; for (let i = startIndex + 1; i < cachedPositionsLen; ++i) { const item = state.cachePositions[i]; state.cachePositions[i].top = state.cachePositions[i - 1].bottom; state.cachePositions[i].bottom = state.cachePositions[i].bottom - cumulativeDiffHeight; if (item.dValue !== 0) { cumulativeDiffHeight += item.dValue; item.dValue = 0; } } const height = state.cachePositions[cachedPositionsLen - 1].bottom; state.phantomHeight = height; }; const getStartIndex = (scrollTop = 0) => { let idx = binarySearch( state.cachePositions, scrollTop, (currentValue, targetValue) => { const currentCompareValue = currentValue.bottom; if (currentCompareValue === targetValue) { return CompareResult.eq; } if (currentCompareValue < targetValue) { return CompareResult.lt; } return CompareResult.gt; } ); const targetItem = state.cachePositions[idx]; if (targetItem.bottom < scrollTop) { idx += 1; } return idx; }; const resetAllVirtualParam = () => { state.originStartIndex = 0; state.start = 0; state.scrollTop = 0; list.value.scrollTop = 0; initCachedPosition(); state.phantomHeight = props.estimateRowHeight * state.list.length; }; const handleScrollEvent = async (e) => { const scrollTop = Math.max(e.detail ? e.detail.scrollTop : e.target.scrollTop, 0.1); const { originStartIndex } = state; const currentIndex = getStartIndex(scrollTop); if (currentIndex !== originStartIndex) { state.originStartIndex = currentIndex; state.start = Math.max(state.originStartIndex - props.bufferSize, 0); if (end.value >= state.list.length - 1) { emit("scroll-bottom"); } } emit(scrollTop > state.scrollTop ? "scroll-up" : "scroll-down", scrollTop); state.scrollTop = scrollTop; }; watch( () => props.listData, (val) => { state.list = val.slice(); if (state.list.length === val.length) { setTimeout(() => { initCachedPosition(); updateCachedPosition(); }, 200); } else { resetAllVirtualParam(); return; } } ); watch( () => state.start, () => { if (actualContent.value && state.list.length > 0) { Taro.nextTick(() => { setTimeout(() => { updateCachedPosition(); }, 200); }); } } ); return { ...toRefs(state), list, phantom, actualContent, getTransform, visibleData, classes, refRandomId, getContainerHeight, handleScrollEvent }; } }); const _hoisted_1 = ["id"]; const _hoisted_2 = ["id"]; const _hoisted_3 = ["id"]; const _hoisted_4 = ["id"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("scroll-view", { class: normalizeClass(_ctx.classes), "scroll-y": true, style: normalizeStyle({ height: `${_ctx.getContainerHeight}px` }), "scroll-top": "0", onScroll: _cache[0] || (_cache[0] = (...args) => _ctx.handleScrollEvent && _ctx.handleScrollEvent(...args)), ref: "list", id: "list" + _ctx.refRandomId }, [ createElementVNode("div", { class: "nut-list-phantom", style: normalizeStyle({ height: _ctx.phantomHeight + "px" }), ref: "phantom", id: "phantom" + _ctx.refRandomId }, null, 12, _hoisted_2), createTextVNode(), createElementVNode("div", { class: "nut-list-container", style: normalizeStyle({ transform: _ctx.getTransform() }), ref: "actualContent", id: "actualContent" + _ctx.refRandomId }, [ (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.visibleData, (item, index) => { return openBlock(), createElementBlock("div", { class: "nut-list-item", key: item, id: "list-item-" + Number(index + _ctx.start) }, [ renderSlot(_ctx.$slots, "default", { item, index: index + _ctx.start }) ], 8, _hoisted_4); }), 128)) ], 12, _hoisted_3) ], 46, _hoisted_1); } const index_taro = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]); export { index_taro as default };