效果演示,效果包含了下列两个案例
在这里插入图片描述

/**
 *  核心工具scrollUtil.ts
 * 
 *  @author 达瓦里氏
 */
interface Position {
    x: number | undefined
    y: number | undefined
}

/**
 * 滚动选中项
 *
 * @param wrap 外层容器
 * @param active 选中项
 * @param cost 动画时长(毫秒)默认500毫秒
 */
export const elScrollTo = (
    wrap: HTMLDivElement | undefined,
    active: Element | null | undefined,
    margin?: number,
    cost?: number
): Position => {
    const inner = wrap?.children[0]
    if (!wrap || !active || !inner) return { x: undefined, y: undefined }
    const wrapRect = wrap.getBoundingClientRect()
    const innerRect = inner.getBoundingClientRect()
    const activeRect = active.getBoundingClientRect()
    const toPosition = getToPosition(wrapRect, innerRect, activeRect, margin)
    if (toPosition.x != undefined || toPosition.y != undefined) {
        animationFrame(toPosition, wrap, cost)
    }
    return toPosition
}

/**
 * 滚动动画
 *
 * @param to 目标坐标
 * @param wrap 外层容器
 * @param cost 动画时长(毫秒)默认500毫秒
 */
export const animationFrame = (
    to: Position,
    wrap: HTMLDivElement | undefined,
    cost = 500
) => {
    if (!wrap) return
    const wrapRect = wrap.getBoundingClientRect()
    const beginTime = Date.now()
    const loop =
        window.requestAnimationFrame || ((func) => setTimeout(func, 16))
    const frame = () => {
        const progress = (Date.now() - beginTime) / cost
        if (progress < 1) {
            if (to.x != undefined) {
                if (to.x < wrapRect.left) {
                    wrap.scrollLeft =
                        wrap.scrollLeft -
                        (wrap.scrollLeft - to.x) * inOutCubic(progress)
                } else {
                    wrap.scrollLeft =
                        wrap.scrollLeft +
                        (to.x - wrap.scrollLeft) * inOutCubic(progress)
                }
            }
            if (to.y != undefined) {
                if (to.y < wrapRect.top) {
                    wrap.scrollTop =
                        wrap.scrollTop -
                        (wrap.scrollTop - to.y) * inOutCubic(progress)
                } else {
                    wrap.scrollTop =
                        wrap.scrollTop +
                        (to.y - wrap.scrollTop) * inOutCubic(progress)
                }
            }
            loop(frame)
        } else {
            if (to.x != undefined) wrap.scrollLeft = to.x
            if (to.y != undefined) wrap.scrollTop = to.y
        }
    }
    loop(frame)
}

/**
 * 取得目标坐标
 */
const getToPosition = (
    wrapRect: DOMRect,
    innerRect: DOMRect,
    activeRect: DOMRect,
    margin = 0
): Position => {
    let x, y
    if (activeRect.left - margin < wrapRect.left) {
        x = activeRect.left - innerRect.left - margin
    } else if (activeRect.right + margin > wrapRect.right) {
        x =
            activeRect.right -
            wrapRect.right +
            wrapRect.left -
            innerRect.left +
            margin
    }
    if (activeRect.top - margin < wrapRect.top) {
        y = activeRect.top - innerRect.top - margin
    } else if (activeRect.bottom + margin > wrapRect.bottom) {
        y =
            activeRect.bottom -
            wrapRect.bottom +
            wrapRect.top -
            innerRect.top +
            margin
    }
    return { x, y }
}

const cubic = (val: number) => Math.pow(val, 3)

const inOutCubic = (val: number) =>
    val < 0.5 ? cubic(val * 2) / 2 : 1 - cubic((1 - val) * 2) / 2
<!-- 简单案例 -->
<script setup lang="ts">
import { useMenu } from '@/stores/menu'
import { ElScrollbar } from 'element-plus'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import MenuItem from './MenuItem.vue'
import { elScrollTo } from '@/utils/scrollUtil'

const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const route = useRoute()
const menu = useMenu()

const scrollToActive = () => {
    nextTick(() => {
        const wrap = scrollbarRef.value?.wrapRef
        const active = wrap?.querySelector('.is-active')
        elScrollTo(wrap, active)
    })
}

onMounted(() => {
    scrollToActive()
    window.addEventListener('resize', scrollToActive)
})
onUnmounted(() => {
    window.removeEventListener('resize', scrollToActive)
})
watch(
    () => route.path,
    () => {
        scrollToActive()
    }
)
</script>

<template>
	<el-scrollbar ref="scrollbarRef">
		<el-menu :default-active="$route.path">
			<MenuItem :menus="menu.menus" />
		</el-menu>
	</el-scrollbar>
</template>
<!-- 横向滚动条案例 -->
<script setup lang="ts">
import router from '@/router'
import { useTab } from '@/stores/tab'
import { ElScrollbar } from 'element-plus'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { elScrollTo, animationFrame } from '@/utils/scrollUtil'

const innerRef = ref<HTMLDivElement>()
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
const wrapRef = ref<HTMLDivElement>()
const scrollLeft = ref<number>(0)
const scrollMax = ref<number>(0)
const tab = useTab()
const route = useRoute()

// 监听鼠标滚轮滚动
const handleScroll = (e: any) => {
    const wheelDelta = e.wheelDelta || -e.deltaY * 40
    if (wrapRef.value) {
        wrapRef.value.scrollLeft = wrapRef.value.scrollLeft - wheelDelta
        scrollLeft.value = wrapRef.value.scrollLeft
    }
}

const handleScrollTo = (x: number) => {
    if (wrapRef.value) {
        animationFrame({ x: x, y: undefined }, wrapRef.value)
        scrollLeft.value = x
    }
}

const scrollToActive = () => {
    nextTick(() => {
        scrollMax.value = 0
        wrapRef.value = scrollbarRef.value?.wrapRef
        const clientWidth = innerRef.value?.clientWidth
        const scrollWidth = innerRef.value?.scrollWidth
        if (clientWidth &amp;&amp; scrollWidth) {
            scrollMax.value = scrollWidth - clientWidth
            const active = wrapRef.value?.querySelector('.is-checked')
            const position = elScrollTo(wrapRef.value, active, 10)
            scrollLeft.value =
                position.x != undefined
                    ? position.x
                    : wrapRef.value?.scrollLeft || 0
        }
    })
}

onMounted(() => {
    scrollToActive()
    window.addEventListener('resize', scrollToActive)
})
onUnmounted(() => {
    window.removeEventListener('resize', scrollToActive)
})
watch(
    () => route.path,
    () => {
        scrollToActive()
    }
)
</script>

<template>
    <div class="tabs-container">
    	<!-- 移动到最左侧 -->
        <el-button
            v-if="scrollMax > 0"
            :icon="DArrowLeft"
			:disabled="scrollLeft == 0"
			@click="handleScrollTo(0)"
        />
        <div class="scrollbar-container">
            <el-scrollbar ref="scrollbarRef" @wheel.prevent="handleScroll">
                <div ref="innerRef" class="scrollbar-content">
                    <el-check-tag
                        v-for="(item, index) in tab.tabs"
                        :key="index"
                        :checked="item.path == $route.path"
                        @change="router.push(item.path)"
                    >
                        <span>{{ item.meta.title }}</span>
                    </el-check-tag>
                </div>
            </el-scrollbar>
        </div>
    	<!-- 移动到最右侧 -->
        <el-button
            v-if="scrollMax > 0"
            :icon="DArrowRight"
			:disabled="scrollLeft >= scrollMax"
			@click="handleScrollTo(scrollMax)"
        />
    </div>
</template>

<style scoped lang="scss">
.tabs-container {
    flex: auto;
    width: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.scrollbar-container {
    overflow: hidden;
    width: 100%;
}
// 滚动条横向
.scrollbar-content {
    display: flex;
}
// 隐藏横向滚动条
:deep(.el-scrollbar__bar.is-horizontal) {
    height: 0 !important;
}
</style>
<!-- 高级应用平面移动 -->
暂时没有场景需要,所以没有写,有兴趣都同学可以尝试一下!

原文地址:https://blog.csdn.net/dskpay/article/details/128700091

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_30752.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注