/**
* 核心工具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 && 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进行投诉反馈,一经查实,立即删除!
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。