| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- import { nextTick, onBeforeUnmount, onMounted, ref, unref, watch } from "vue";
- const ITEM_MODE_RATIO = 0.8;
- const closestCardIndex = (cards, scrollLeft) => {
- const origin = cards[0].offsetLeft;
- let closest = 0;
- let closestDistance = Infinity;
- cards.forEach((card, index) => {
- const distance = Math.abs(card.offsetLeft - origin - scrollLeft);
- if (distance < closestDistance) {
- closestDistance = distance;
- closest = index;
- }
- });
- return closest;
- };
- /**
- * @param {Ref<Array>|Function} source - The rendered items (ref or getter)
- * @returns {{ trackRef: Ref, activeIndex: Ref<number>, pageCount: Ref<number>, onTrackScroll: Function }}
- */
- export const useCarouselIndicator = (source) => {
- const activeIndex = ref(0);
- const pageCount = ref(0);
- const trackRef = ref(null);
- let resizeObserver = null;
- const resolveItems = () => {
- const value = typeof source === "function" ? source() : unref(source);
- return Array.isArray(value) ? value : [];
- };
- const measure = () => {
- const track = trackRef.value;
- if (!track || !track.clientWidth) return;
- const cards = Array.from(track.children).filter(
- (element) => element.nodeType === Node.ELEMENT_NODE,
- );
- if (!cards.length) {
- activeIndex.value = 0;
- pageCount.value = 0;
- return;
- }
- const isItemMode =
- cards[0].offsetWidth >= track.clientWidth * ITEM_MODE_RATIO;
- pageCount.value = isItemMode
- ? cards.length
- : Math.max(1, Math.ceil(track.scrollWidth / track.clientWidth));
- if (pageCount.value <= 1) {
- activeIndex.value = 0;
- return;
- }
- const maxScroll = track.scrollWidth - track.clientWidth;
- if (maxScroll > 0 && track.scrollLeft >= maxScroll - 1) {
- activeIndex.value = pageCount.value - 1;
- return;
- }
- const index = isItemMode
- ? closestCardIndex(cards, track.scrollLeft)
- : Math.round(track.scrollLeft / track.clientWidth);
- activeIndex.value = Math.min(Math.max(index, 0), pageCount.value - 1);
- };
- const stopObserving = () => {
- resizeObserver?.disconnect();
- resizeObserver = null;
- };
- const startObserving = (track) => {
- if (!track || typeof ResizeObserver === "undefined") return;
- resizeObserver = new ResizeObserver(() => measure());
- resizeObserver.observe(track);
- };
- watch(
- trackRef,
- (track) => {
- stopObserving();
- if (track) startObserving(track);
- },
- { flush: "post" },
- );
- watch(
- () => resolveItems().length,
- () => nextTick(measure),
- );
- onMounted(() => nextTick(measure));
- onBeforeUnmount(stopObserving);
- return {
- activeIndex,
- onTrackScroll: measure,
- pageCount,
- trackRef,
- };
- };
|