diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue index edd6749..a393194 100644 --- a/src/layout/components/TagsView/ScrollPane.vue +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -29,11 +29,46 @@ function handleScroll(e: WheelEvent): void { const eventDelta = -e.deltaY * 40 const $scrollWrapper = scrollWrapper.value $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4 + emits('updateArrows') } -const emits = defineEmits() +function smoothScrollTo(target: number): void { + const $scrollWrapper = scrollWrapper.value + const start: number = $scrollWrapper.scrollLeft + const distance: number = target - start + const duration = 300 + let startTime: number | null = null + + function ease(t: number, b: number, c: number, d: number): number { + t /= d / 2 + if (t < 1) return c / 2 * t * t + b + t-- + return -c / 2 * (t * (t - 2) - 1) + b + } + + function step(timestamp: number): void { + if (!startTime) startTime = timestamp + const elapsed = timestamp - startTime + $scrollWrapper.scrollLeft = ease(elapsed, start, distance, duration) + if (elapsed < duration) { + requestAnimationFrame(step) + } else { + $scrollWrapper.scrollLeft = target + emits('updateArrows') + } + } + + requestAnimationFrame(step) +} + +const emits = defineEmits<{ + (e: 'scroll'): void + (e: 'updateArrows'): void +}>() + const emitScroll = (): void => { emits('scroll') + emits('updateArrows') } const tagsViewStore = useTagsViewStore() @@ -47,16 +82,15 @@ function moveToTarget(currentTag: any): void { let firstTag: any = null let lastTag: any = null - // find first tag and last tag if (visitedViews.value.length > 0) { firstTag = visitedViews.value[0] lastTag = visitedViews.value[visitedViews.value.length - 1] } if (firstTag === currentTag) { - $scrollWrapper.scrollLeft = 0 + smoothScrollTo(0) } else if (lastTag === currentTag) { - $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth + smoothScrollTo($scrollWrapper.scrollWidth - $containerWidth) } else { const tagListDom = document.getElementsByClassName('tags-view-item') const currentIndex = visitedViews.value.findIndex((item: any) => item === currentTag) @@ -75,22 +109,39 @@ function moveToTarget(currentTag: any): void { } if (prevTag && nextTag) { - // the tag's offsetLeft after of nextTag const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value - - // the tag's offsetLeft before of prevTag const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) { - $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth + smoothScrollTo(afterNextTagOffsetLeft - $containerWidth) } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) { - $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft + smoothScrollTo(beforePrevTagOffsetLeft) } } } } +function scrollToStart(): void { + smoothScrollTo(0) +} + +function scrollToEnd(): void { + const $scrollWrapper = scrollWrapper.value + smoothScrollTo($scrollWrapper.scrollWidth - $scrollWrapper.clientWidth) +} + +function getScrollState(): { canLeft: boolean; canRight: boolean } { + const $scrollWrapper = scrollWrapper.value + return { + canLeft: $scrollWrapper.scrollLeft > 0, + canRight: $scrollWrapper.scrollLeft < $scrollWrapper.scrollWidth - $scrollWrapper.clientWidth - 1 + } +} + defineExpose({ moveToTarget, + scrollToStart, + scrollToEnd, + getScrollState }) @@ -100,11 +151,16 @@ defineExpose({ position: relative; overflow: hidden; width: 100%; + height: 100%; :deep(.el-scrollbar__bar) { - bottom: 0px; + display: none; } :deep(.el-scrollbar__wrap) { - height: 39px; + height: 34px; + display: flex; + align-items: center; + overflow-x: auto; + overflow-y: hidden; } } \ No newline at end of file diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue index d1460ea..203445c 100644 --- a/src/layout/components/TagsView/index.vue +++ b/src/layout/components/TagsView/index.vue @@ -1,6 +1,12 @@