<template>
|
<div class="scroll-pane">
|
<div v-if="state.showLeftIcon" class="arrow-content left-icon-content" @click="scollByIcon('left')">
|
<!-- <i class="iconfont ic-arrow left-icon"></i> -->
|
</div>
|
<div v-if="state.showRightIcon" class="arrow-content right-icon-content" @click="scollByIcon('right')">
|
<!-- <i class="iconfont ic-arrow right-icon"></i> -->
|
</div>
|
<div ref="container" :style="containerStyle" class="scroll-container" @scroll="scollOrContentResize">
|
<div class="scoll-slot">
|
<slot />
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script lang="ts" setup>
|
import { reactive, ref } from '@vue/reactivity';
|
import { computed } from 'vue-demi';
|
|
const container = ref<null | HTMLElement>(null);
|
interface State {
|
showLeftIcon: boolean;
|
showRightIcon: boolean;
|
leftArr: number[];
|
}
|
const state: State = reactive({
|
showLeftIcon: true,
|
showRightIcon: true,
|
leftArr: [],
|
});
|
// 通过计算属性,动态更改容器的样式,不存在按钮时宽度和左边距
|
const containerStyle = computed(() => {
|
let style = {};
|
let clacWidth = 0;
|
if (state.showLeftIcon) {
|
clacWidth += 40;
|
const leftStyle = {
|
'margin-left': '40px',
|
width: `calc(100% - ${clacWidth}px)`,
|
};
|
style = { ...leftStyle };
|
}
|
if (state.showRightIcon) {
|
clacWidth += 40;
|
const leftStyle = {
|
width: `calc(100% - ${clacWidth}px)`,
|
};
|
style = { ...style, ...leftStyle };
|
}
|
return style;
|
});
|
|
// 检测他的滚动或者宽度发生变化
|
function scollOrContentResize() {
|
// 获取累加宽度数组,这样跳转只需要判断对应哪个下标就行了
|
function buildSumWidth(nodeArr: Element[]) {
|
let currentWidth = 0;
|
const widthArr: number[] = [];
|
nodeArr.forEach((item: Element) => {
|
widthArr.push(currentWidth);
|
// 第一个有个15px的left margin,其他的就加5px的left margin就好了 (还要加2pxborder,不知道为什么没有offsetWidth属性,有时间再看看)
|
currentWidth += currentWidth ? item.clientWidth + 7 : 17 + item.clientWidth;
|
});
|
return widthArr;
|
}
|
if (container) {
|
const childrenNodeList = [];
|
for (let index = 0; index < container.value!.children[0].children.length; index += 1) {
|
childrenNodeList.push(container.value!.children[0].children[index]);
|
}
|
state.leftArr = buildSumWidth(childrenNodeList);
|
const { clientWidth, scrollWidth, scrollLeft } = container.value!;
|
// 预留1px避免精度问题
|
state.showLeftIcon = scrollLeft > 1;
|
state.showRightIcon = scrollLeft + clientWidth < scrollWidth - 1;
|
}
|
}
|
// 点击图标跳转
|
function scollByIcon(params: string) {
|
console.log('container.value', container.value)
|
const { scrollLeft, clientWidth } = container.value!;
|
console.log('scrollLeft', scrollLeft)
|
console.log('clientWidth', clientWidth)
|
// 左右滚动一屏,保留200px用于连贯显示
|
let stepNum = scrollLeft + (clientWidth - 200);
|
if (params === 'left') {
|
stepNum = scrollLeft - (clientWidth - 200);
|
}
|
// 获取当前应该滚到以哪个开头
|
let index = state.leftArr.findIndex((i) => i > stepNum);
|
index = index > -1 ? index - 1 : state.leftArr.length - 2;
|
if (stepNum <= 0 || index < 0) {
|
index = 0;
|
}
|
container.value!.scrollTo(state.leftArr[index], 0);
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.scroll-pane {
|
position: relative;
|
width: 100%;
|
|
.arrow-content {
|
position: absolute;
|
top: 4px;
|
box-sizing: border-box;
|
width: 26px;
|
height: 26px;
|
line-height: 26px;
|
text-align: center;
|
background: #fff;
|
border: 1px solid #d8dce5;
|
border-radius: 4px;
|
|
.ic-arrow {
|
color: #666;
|
font-size: 14px;
|
}
|
|
&:hover {
|
.ic-arrow {
|
color: var(--el-color-primary) !important;
|
}
|
|
cursor: pointer;
|
}
|
|
.left-icon {
|
transform: rotate(180deg);
|
}
|
}
|
|
.left-icon-content {
|
left: 5px;
|
transform: rotate(180deg);
|
}
|
|
.right-icon-content {
|
right: 5px;
|
}
|
|
.scroll-container {
|
position: relative;
|
width: 100%;
|
overflow-x: auto;
|
|
&::-webkit-scrollbar {
|
display: none;
|
}
|
|
.scoll-slot {
|
display: inline-block;
|
// 超出窗口长度时,显示滚动条
|
white-space: nowrap;
|
}
|
}
|
}
|
</style>
|