效果:
代码 slidercontainer.vue:
<template>
<div class="slider-container" ref="containerRef">
<div class="slider-track">
<div class="slider-fill" :style="{ width: `${percentage}%` }"></div>
</div>
<div
class="slider-handle"
:class="{ success: isSuccess, error: isError }"
:style="{ left: `${percentage}%` }"
@mousedown="startDrag"
@touchstart="startDrag"
>
<template v-if="isSuccess">
<icon-check class="icon-check" />
</template>
<template v-else-if="isError">
<icon-close class="icon-close" />
</template>
<template v-else>
<icon-right />
</template>
</div>
<div class="slider-text" :class="{ success: isSuccess, error: isError }">
{{ sliderText }}
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted } from "vue";
import { IconCheck, IconClose, IconRight } from "@arco-design/web-vue/es/icon";
export default defineComponent({
name: "SlideContainer",
components: { IconCheck, IconClose, IconRight },
emits: ["success"],
setup(_, { emit }) {
const containerRef = ref<HTMLElement | null>(null);
const percentage = ref(0);
const isDragging = ref(false);
const startX = ref(0);
const sliderText = ref("向右滑动验证");
const isSuccess = ref(false);
const isError = ref(false);
const startDrag = (event: MouseEvent | TouchEvent) => {
event.preventDefault();
if (isSuccess.value) return;
isDragging.value = true;
startX.value =
"touches" in event ? event.touches[0].clientX : event.clientX;
document.addEventListener("mousemove", onDrag);
document.addEventListener("touchmove", onDrag);
document.addEventListener("mouseup", stopDrag);
document.addEventListener("touchend", stopDrag);
};
const onDrag = (event: MouseEvent | TouchEvent) => {
if (!isDragging.value || !containerRef.value) return;
const clientX =
"touches" in event ? event.touches[0].clientX : event.clientX;
const containerRect = containerRef.value.getBoundingClientRect();
const deltaX = clientX - startX.value;
const containerWidth = containerRect.width;
percentage.value = Math.min(
Math.max((deltaX / containerWidth) * 100, 0),
100
);
if (percentage.value >= 90) {
isSuccess.value = true;
isError.value = false;
sliderText.value = "验证通过";
emit("success");
stopDrag();
}
};
const stopDrag = () => {
isDragging.value = false;
document.removeEventListener("mousemove", onDrag);
document.removeEventListener("touchmove", onDrag);
document.removeEventListener("mouseup", stopDrag);
document.removeEventListener("touchend", stopDrag);
if (!isSuccess.value) {
isError.value = true;
sliderText.value = "验证失败,请重试";
setTimeout(() => {
percentage.value = 0;
isError.value = false;
sliderText.value = "向右滑动验证";
}, 1000);
}
};
onMounted(() => {
window.addEventListener("resize", stopDrag);
});
onUnmounted(() => {
window.removeEventListener("resize", stopDrag);
});
return {
containerRef,
percentage,
startDrag,
sliderText,
isSuccess,
isError,
};
},
});
</script>
<style scoped>
.slider-handle {
position: absolute;
top: 0;
width: 40px;
height: 40px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 16px;
transition: all 0.3s ease;
}
.slider-handle :deep(svg) {
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3));
}
.slider-container {
position: relative;
width: 100%;
height: 40px;
background-color: var(--color-fill-2);
border-radius: 4px;
overflow: hidden;
}
.slider-track {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.slider-fill {
height: 100%;
background-color: var(--color-primary-light-1);
transition: width 0.3s ease;
}
.slider-handle.success {
background-color: var(--color-success-light-1);
}
.slider-handle.error {
background-color: var(--color-danger-light-1);
}
.icon-check,
.icon-close {
color: #ffffff;
font-size: 20px;
}
.slider-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--color-text-2);
font-size: 14px;
font-weight: 500;
pointer-events: none;
transition: color 0.3s ease;
}
</style>
使用
import SlideContainer from "@/components/SlideContainer.vue";
<SlideContainer @success="onVerifySuccess" />