seatonwan9
2025-08-18 357a4c941549c9d65d74629c38b989683f5db0b4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<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>