用户想要左右可以支持手动滑动的图例
实现方法一:
<template>
<div class="color_slider">
<div class="line"></div>
<div
v-if="!disabled"
class="dot dot_left"
:style="{ left: positionArr[0] + '%' }"
@mousedown="mouseDown('left', $event)"
/>
<div
v-if="!disabled"
class="dot"
:style="{ left: positionArr[1] + '%' }"
@mousedown="mouseDown('right', $event)"
/>
<div
v-for="(color, index) in getImageDataArray(colorConfig.imageData)"
:key="color"
class="color-item"
:style="{ background: color }"
>
<!-- 合并框颠簸特殊处理 -->
<template
v-if="colorConfig.specialLabels && colorConfig.specialLabels.length"
>
<div
v-if="colorConfig.specialLabels.some((item: any) => item.startIndex === index)"
class="item-special"
:style="{
width: colorConfig.specialLabels.filter((item: any) => item.startIndex === index)[0].cols * 100 + '%'
}"
>
{{
colorConfig.specialLabels.filter(
(item: any) => item.startIndex === index
)[0].name
}}
</div>
</template>
<!-- label下标显示 -->
<div
v-else-if="colorConfig.labels && colorConfig.labels.length"
class="item-label"
>
{{ colorConfig.labels[index - 1] }}
</div>
<!-- 显示成替换内容 -->
<div
v-else-if="colorConfig.otherLevels && colorConfig.otherLevels.length"
class="item-value"
>
{{ colorConfig.otherLevels[index - 1] }}
</div>
<!-- 自定义显示哪些数字 -->
<div
v-else-if="colorConfig.defineLevels && colorConfig.defineLevels.length"
class="item-value"
>
{{ showDefineLevels(index) }}
</div>
<div v-else class="item-value">
{{ ifShowColorLabel(index) ? colorConfig.levels[index - 1] : "" }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { getImageDataArray } from "@/utils/common";
import { ref, computed, onMounted, reactive } from "vue";
const direction = ref<"left" | "right">("left");
const leftStartX = ref(0); // 开始 clientY
const rightStartX = ref(0); // 开始 clientY
const leftStartPosition = ref(0);
const rightStartPosition = ref(0);
const positionArr = reactive([0, 100]);
const stepArr = ref<Array<number>>([]);
const colorConfig = reactive({
imageData: [
170, 0, 170, 255, 130, 0, 122, 255, 77, 1, 115, 255, 2, 36, 116, 255, 2, 76,
165, 255, 64, 111, 209, 255, 2, 166, 203, 255, 109, 225, 248, 255, 173, 229,
243, 255, 254, 255, 196, 255, 213, 255, 172, 255, 171, 254, 89, 255, 255,
253, 1, 255, 246, 202, 64, 255, 254, 126, 67, 255, 250, 63, 97, 255, 253, 0,
2, 255, 175, 0, 0, 255,
],
levels: [
-32, -28, -24, -20, -16, -12, -8, -4, 0, 4, 8, 12, 16, 20, 24, 28, 32,
],
colorStep: 2,
});
const emit = defineEmits(["change"]);
const positionIndex = computed(() => {
if (stepArr.value.length) {
return [
stepArr.value.findIndex((i) => i === positionArr[0]),
stepArr.value.findIndex((i) => i === positionArr[1]),
];
} else {
return [0, getImageDataArray(colorConfig.imageData).length - 1];
}
});
const defineLevelsArray = computed(() => {
if (colorConfig.defineLevels && colorConfig.defineLevels.length) {
const array: Array<{ index: number; value: number }> = [];
colorConfig.defineLevels!.forEach((num) => {
const arr = colorConfig.levels.map((n) => Math.abs(n - num));
const index = arr.findIndex((n) => n === Math.min(...arr));
const obj = {
index,
value: num,
};
array.push(obj);
});
return array;
} else {
return [];
}
});
onMounted(() => {
const step = 100 / getImageDataArray(colorConfig.imageData).length;
for (let i = 0; i <= getImageDataArray(colorConfig.imageData).length; i++) {
stepArr.value.push(i * step);
}
setTimeout(() => {
console.log("1113", colorConfig);
}, 1000);
});
const ifShowColorLabel = (index: number) => {
let step;
if (colorConfig.colorStep) {
step = colorConfig.colorStep;
} else {
const showLen = 10;
const len = colorConfig.levels.length;
step = Math.ceil(len / showLen);
}
if (step === 1) {
return index !== 0;
} else {
return index % step === 1;
}
};
const showDefineLevels = (index: number) => {
const newIndex = index - 1;
const findIndex = defineLevelsArray.value.findIndex(
(obj) => obj.index === newIndex
);
if (findIndex === -1) {
return "";
} else {
return defineLevelsArray.value[findIndex].value;
}
};
const mouseDown = (d: "left" | "right", event: MouseEvent) => {
if (d === "left") {
leftStartX.value = event.clientX;
leftStartPosition.value = positionArr[0];
} else {
rightStartX.value = event.clientX;
rightStartPosition.value = positionArr[1];
}
direction.value = d;
window.addEventListener("mousemove", onDragging);
window.addEventListener("mouseup", () => {
window.removeEventListener("mousemove", onDragging);
});
};
const onDragging = (event: MouseEvent) => {
const d = direction.value;
const startX = d === "left" ? leftStartX.value : rightStartX.value;
const startPosition =
d === "left" ? leftStartPosition.value : rightStartPosition.value;
const currentX = event.clientX;
const clientWidth =
document.getElementsByClassName("color_slider")[0].clientWidth;
const diff = ((currentX - startX) / clientWidth) * 100;
const newPosition = startPosition + diff;
setPosition(d, newPosition);
};
const setPosition = (d: "left" | "right", position: number) => {
let newPosition = 0;
if (d === "left") {
if (position < 0) {
newPosition = 0;
} else if (position >= stepArr.value[positionIndex.value[1] - 1]) {
newPosition = stepArr.value[positionIndex.value[1] - 1];
} else {
newPosition = position;
}
} else {
if (position > 100) {
newPosition = 100;
} else if (position <= stepArr.value[positionIndex.value[0] + 1]) {
newPosition = stepArr.value[positionIndex.value[0] + 1];
} else {
newPosition = position;
}
}
const arr = stepArr.value.map((num) => Math.abs(num - newPosition));
const index = arr.findIndex((num) => num === Math.min(...arr));
if (d === "left") {
if (positionArr[0] !== stepArr.value[index]) {
positionArr[0] = stepArr.value[index];
emit("change", positionIndex.value);
}
} else {
if (positionArr[1] !== stepArr.value[index]) {
positionArr[1] = stepArr.value[index];
emit("change", positionIndex.value);
}
}
};
</script>
<style scoped lang="scss">
.color_slider {
height: 10px;
width: 99%;
margin-left: 2px;
position: relative;
display: flex;
align-items: center;
.color-item {
flex: 1;
height: 100%;
position: relative;
.item-label {
position: absolute;
bottom: -20px;
width: 100%;
// color: #fff;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
.item-value {
position: absolute;
// color: #fff;
font-size: 12px;
bottom: -20px;
transform: translateX(-50%);
z-index: 9999;
}
.item-special {
position: absolute;
// color: #fff;
font-size: 12px;
bottom: -20px;
z-index: 9999;
text-align: center;
border-right: 1px dashed #ccc;
}
&:nth-child(5) {
.item-special {
border-left: 1px dashed #ccc;
}
}
}
.line {
position: absolute;
top: 50%;
transform: translateY(-50%);
height: 1px;
background-color: #fff;
width: 100%;
}
.dot {
position: absolute;
z-index: 99;
top: 50%;
transform: translate(-95%, -50%);
cursor: pointer;
border-width: 7px;
border-style: dashed solid dashed dashed;
border-color: transparent #b3b3b3 transparent transparent;
&_left {
transform: translate(0, -50%);
border-style: dashed dashed dashed solid;
border-color: transparent transparent transparent #b3b3b3;
}
}
}
</style>
getImageDataArray方法:
// 将 imgaData 分成颜色数组 可以加透明度 0-1 之间
export function getImageDataArray(arr: number[], alpha = 1) {
const newArr: string[] = [];
let data: number[] = [];
arr.forEach((num, i) => {
const result = i % 4;
if (result != 3) {
data.push(num);
if (result == 2) {
const str =
alpha != 1
? `rgba(${data.join(",")},${alpha})`
: `rgb(${data.join(",")})`;
newArr.push(str);
}
} else {
data = [];
}
});
if (arr[3] == 0) {
// 如果是0,第一个就是透明色
newArr[0] = "rgba(0,0,0,0)";
}
if (arr[arr.length - 1] == 0) {
// 如果是0,第一个就是透明色
newArr[newArr.length - 1] = "rgba(0,0,0,0)";
}
return newArr;
}
实现方法二:【这个还需要完善】
<template>
<el-card
class="slider-block"
v-if="grades.values.length > 0"
:body-style="getBodyStyle"
style="z-index: 600"
>
<el-space direction="vertical" :size="vertical ? 20 : 8" alignment="center">
<!-- <el-text type="info">{{ grades.desc }}({{ grades.unit }})</el-text> -->
<!-- 实时显示当前范围 -->
<el-text type="primary" size="small">{{ currentRangeLabel }}</el-text>
<div class="slider-wrapper" :style="wrapperStyle">
<!-- 纯色块色带 -->
<div class="color-bar">
<div
v-for="(color, index) in grades.colors"
:key="index"
class="color-segment"
:style="getSegmentStyle(index)"
/>
</div>
<!-- 滑动条 -->
<el-slider
v-model="sliderValue"
:range="true"
:marks="marks"
v-bind="options"
@change="onChange"
@input="updateCurrentRange"
class="sliderLegend"
/>
</div>
</el-space>
</el-card>
</template>
<script setup>
import { ref, computed, reactive, defineProps, defineExpose } from "vue";
const props = defineProps({
gradesValues: { type: Array, default: () => [0, 0.1, 1.5, 7, 15, 40, 50] },
gradesColors: {
type: Array,
default: () => [
"#dfe8ff",
"#a6f28f",
"#3dba3d",
"#61b8ff",
"#0000e1",
"#fa00fa",
"#800040",
],
},
vertical: { type: Boolean, default: false },
width: { type: Number, default: 300 },
});
const emits = defineEmits(["update:modeValue"]);
const sliderValue = ref([0, 100]);
const currentRangeLabel = ref("");
const grades = reactive({
values: props.gradesValues,
colors: props.gradesColors,
desc: "",
unit: "",
});
const getBodyStyle = computed(() => {
return props.vertical
? { padding: "10px 0 0", width: `${props.width}px` }
: { padding: "10px", height: `${props.width}px` };
});
const wrapperStyle = computed(() => ({
position: "relative",
width: props.vertical ? "10px" : `${props.width}px`,
height: props.vertical ? `${props.width}px` : "10px",
}));
const marks = computed(() => {
const result = {};
const step = 100 / (grades.values.length - 1);
grades.values.forEach((v, i) => {
result[Math.round(step * i)] = `${v}`;
});
return result;
});
const options = computed(() => ({
min: 0,
max: 100,
step: 100 / (grades.values.length - 1),
showTooltip: false,
vertical: props.vertical,
size: "small",
style: {
margin: props.vertical ? "0" : "10px 0 0",
width: props.vertical ? undefined : `${props.width}px`,
height: props.vertical ? `${props.width}px` : undefined,
},
}));
function updateCurrentRange([min, max]) {
const step = 100 / (grades.values.length - 1);
const indexMin = Math.round(min / step);
const indexMax = Math.round(max / step);
const v1 = grades.values[indexMin];
const v2 = grades.values[indexMax];
currentRangeLabel.value = `${v1} ~ ${v2} ${grades.unit}`;
}
function onChange([min, max]) {
const step = 100 / (grades.values.length - 1);
const indexMin = Math.round(min / step);
const indexMax = Math.round(max / step);
emits("update:modeValue", [grades.values[indexMin], grades.values[indexMax]]);
}
function initControl(data) {
Object.assign(grades, data);
sliderValue.value = [0, 100];
updateCurrentRange([0, 100]);
}
defineExpose({ initControl });
// 计算每一段的样式
const getSegmentStyle = (index) => {
const step = 100 / grades.colors.length;
const left = step * index;
const width = step;
const [start, end] = sliderValue.value;
const isSelected = left + width <= start;
return {
position: "absolute",
top: 0,
bottom: 0,
left: `${left}%`,
width: `${width}%`,
backgroundColor: isSelected ? "#ccc" : grades.colors[index],
transition: "background-color 0.3s",
};
};
</script>
<style scoped lang="scss">
.sliderLegend {
::v-deep(.el-slider__runway) {
background: transparent;
}
::v-deep(.el-slider__bar) {
background: transparent;
}
}
.color-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 4px;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.color-segment {
position: absolute;
height: 100%;
}
.el-slider--small {
margin-top: -6px;
}
</style>