Skip to content

Commit

Permalink
feat: Add useScrollTo hook
Browse files Browse the repository at this point in the history
  • Loading branch information
kailong321200875 committed Feb 20, 2022
1 parent 9d926b2 commit 7d7fd9e
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 8 deletions.
124 changes: 117 additions & 7 deletions src/components/TagsView/src/TagsView.vue
@@ -1,14 +1,16 @@
<script setup lang="ts">
import { onMounted, watch, computed, unref, ref, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouterLinkProps } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { useI18n } from '@/hooks/web/useI18n'
import { filterAffixTags } from './helper'
import { ContextMenu, ContextMenuExpose } from '@/components/ContextMenu'
import { useDesign } from '@/hooks/web/useDesign'
import { useTemplateRefsList } from '@vueuse/core'
import { ElScrollbar } from 'element-plus'
import { useScrollTo } from '@/hooks/event/useScrollTo'
const { getPrefixCls } = useDesign()
Expand Down Expand Up @@ -111,13 +113,97 @@ const toLastView = () => {
}
}
// 滚动到选中的tag
const moveToCurrentTag = async () => {
await nextTick()
for (const v of unref(visitedViews)) {
if (v.fullPath === unref(currentRoute).path) {
moveToTarget(v)
if (v.fullPath !== unref(currentRoute).fullPath) {
tagsViewStore.updateVisitedView(unref(currentRoute))
}
break
}
}
}
const tagLinksRefs = useTemplateRefsList<RouterLinkProps>()
const moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => {
const wrap$ = unref(scrollbarRef)?.wrap$
let firstTag: Nullable<RouterLinkProps> = null
let lastTag: Nullable<RouterLinkProps> = null
const tagList = unref(tagLinksRefs)
// find first tag and last tag
if (tagList.length > 0) {
firstTag = tagList[0]
lastTag = tagList[tagList.length - 1]
}
if ((firstTag?.to as RouteLocationNormalizedLoaded).fullPath === currentTag.fullPath) {
// 直接滚动到0的位置
const { start } = useScrollTo({
el: wrap$!,
position: 'scrollLeft',
to: 0,
duration: 500
})
start()
} else if ((lastTag?.to as RouteLocationNormalizedLoaded).fullPath === currentTag.fullPath) {
// 滚动到最后的位置
const { start } = useScrollTo({
el: wrap$!,
position: 'scrollLeft',
to: wrap$!.scrollWidth - wrap$!.offsetWidth,
duration: 500
})
start()
} else {
// find preTag and nextTag
const currentIndex: number = tagList.findIndex(
(item) => (item?.to as RouteLocationNormalizedLoaded).fullPath === currentTag.fullPath
)
const tgsRefs = document.getElementsByClassName(`${prefixCls}__item`)
const prevTag = tgsRefs[currentIndex - 1] as HTMLElement
const nextTag = tgsRefs[currentIndex + 1] as HTMLElement
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + 4
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft = prevTag.offsetLeft - 4
if (afterNextTagOffsetLeft > unref(scrollLeftNumber) + wrap$!.offsetWidth) {
const { start } = useScrollTo({
el: wrap$!,
position: 'scrollLeft',
to: afterNextTagOffsetLeft - wrap$!.offsetWidth,
duration: 500
})
start()
} else if (beforePrevTagOffsetLeft < unref(scrollLeftNumber)) {
const { start } = useScrollTo({
el: wrap$!,
position: 'scrollLeft',
to: beforePrevTagOffsetLeft,
duration: 500
})
start()
}
}
}
// 是否是当前tag
const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
return route.path === unref(currentRoute).path
}
// 所有右键菜单组件的元素
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
// 右键菜单装填改变的时候
const visibleChange = (
visible: boolean,
ref: ComponentRef<typeof ContextMenu & ContextMenuExpose>
Expand All @@ -133,6 +219,28 @@ const visibleChange = (
}
}
// elscroll 实例
const scrollbarRef = ref<ComponentRef<typeof ElScrollbar>>()
// 保存滚动位置
const scrollLeftNumber = ref(0)
const scroll = ({ scrollLeft }) => {
scrollLeftNumber.value = scrollLeft as number
}
// 移动到某个位置
const move = (to: number) => {
const wrap$ = unref(scrollbarRef)?.wrap$
const { start } = useScrollTo({
el: wrap$!,
position: 'scrollLeft',
to: unref(scrollLeftNumber) + to,
duration: 500
})
start()
}
onMounted(() => {
initTags()
addTags()
Expand All @@ -142,7 +250,7 @@ watch(
() => currentRoute.value,
() => {
addTags()
// moveToCurrentTag()
moveToCurrentTag()
}
)
</script>
Expand All @@ -152,12 +260,14 @@ watch(
<span
:class="`${prefixCls}__tool`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] text-center leading-[var(--tags-view-height)] cursor-pointer"
@click="move(-200)"
>
<Icon icon="ep:d-arrow-left" color="#333" />
</span>
<div class="overflow-hidden flex-1">
<ElScrollbar class="h-full">
<ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
<div class="flex h-full">
<div></div>
<ContextMenu
:ref="itemRefs.set"
:schema="[
Expand Down Expand Up @@ -228,10 +338,10 @@ watch(
]"
@visible-change="visibleChange"
>
<router-link :to="{ ...item }" custom v-slot="{ navigate }">
<router-link :ref="tagLinksRefs.set" :to="{ ...item }" custom v-slot="{ navigate }">
<div
@click="navigate"
class="h-full flex justify-center items-center whitespace-nowrap"
class="h-full flex justify-center items-center whitespace-nowrap pl-15px"
>
{{ t(item?.meta?.title as string) }}
<Icon
Expand All @@ -250,6 +360,7 @@ watch(
<span
:class="`${prefixCls}__tool`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] text-center leading-[var(--tags-view-height)] cursor-pointer"
@click="move(200)"
>
<Icon icon="ep:d-arrow-right" color="#333" />
</span>
Expand Down Expand Up @@ -358,9 +469,8 @@ watch(
position: relative;
top: 2px;
height: calc(~'100% - 4px');
padding: 0 15px;
// padding: 0 15px;
font-size: 12px;
line-height: calc(~'var( - -tags-view-height) - 4px');
cursor: pointer;
border: 1px solid #d9d9d9;
Expand Down
62 changes: 62 additions & 0 deletions src/hooks/event/useScrollTo.ts
@@ -0,0 +1,62 @@
import { ref, unref } from 'vue'

export interface ScrollToParams {
el: HTMLElement
to: number
position: string
duration?: number
callback?: () => void
}

const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
t /= d / 2
if (t < 1) {
return (c / 2) * t * t + b
}
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const move = (el: HTMLElement, position: string, amount: number) => {
el[position] = amount
}

export function useScrollTo({
el,
position = 'scrollLeft',
to,
duration = 500,
callback
}: ScrollToParams) {
const isActiveRef = ref(false)
const start = el[position]
const change = to - start
const increment = 20
let currentTime = 0

function animateScroll() {
if (!unref(isActiveRef)) {
return
}
currentTime += increment
const val = easeInOutQuad(currentTime, start, change, duration)
move(el, position, val)
if (currentTime < duration && unref(isActiveRef)) {
requestAnimationFrame(animateScroll)
} else {
if (callback) {
callback()
}
}
}

function run() {
isActiveRef.value = true
animateScroll()
}

function stop() {
isActiveRef.value = false
}

return { start: run, stop }
}
1 change: 0 additions & 1 deletion src/store/modules/permission.ts
Expand Up @@ -53,7 +53,6 @@ export const usePermissionStore = defineStore({
// 直接读取静态路由表
routerMap = cloneDeep(asyncRouterMap)
}
console.log(routerMap)
// 动态路由,404一定要放到最后面
this.addRouters = routerMap.concat([
{
Expand Down
8 changes: 8 additions & 0 deletions src/store/modules/tagsView.ts
Expand Up @@ -124,6 +124,14 @@ export const useTagsViewStore = defineStore({
})
this.addCachedView()
}
},
updateVisitedView(view: RouteLocationNormalizedLoaded) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
}
})
Expand Down

0 comments on commit 7d7fd9e

Please sign in to comment.