|
@@ -1,192 +1,373 @@
|
|
|
<template>
|
|
|
- <view class="slider-verify" :style="'background-color:' + sBgColor" @touchend="touchend">
|
|
|
- <view class="slider-prompt" :style="success ? 'color:' + sColor : 'color:' + dColor">{{ success ? sliderText.successText : sliderText.startText }}</view>
|
|
|
- <view class="slider-bg" :style="{ 'transform': 'translateX(' + oldx + 'px)', backgroundColor: dBgColor }"></view>
|
|
|
- <movable-area class="slider-area" :animation="true">
|
|
|
- <movable-view
|
|
|
- :style="`background-color: ${sliderText.successColor};border-color: ${success ? sBgColor : dBgColor} `"
|
|
|
- :class="{ 'movable-btn': true, 'movable-success': success }" :x="x" direction="horizontal" @change="onMove"
|
|
|
- :disabled="isDisable">
|
|
|
- {{sliderText.btnText}}
|
|
|
- </movable-view>
|
|
|
- </movable-area>
|
|
|
+ <view class="slider-verify" :style="'background-color:' + sBgColor" @touchend="touchend">
|
|
|
+ <view class="slider-prompt" :style="success ? 'color:' + sColor : 'color:' + dColor">
|
|
|
+ <view class="arrow-group left">
|
|
|
+ <text class="arrow" v-for="(item, index) in leftArrows" :key="`left-${index}`"
|
|
|
+ :style="{opacity: item.opacity, transform: `scale(${item.scale})`, color: arrowColor}">›</text>
|
|
|
+ </view>
|
|
|
+ {{ success ? sliderText.successText : sliderText.startText }}
|
|
|
+ <view class="arrow-group right">
|
|
|
+ <text class="arrow" v-for="(item, index) in rightArrows" :key="`right-${index}`"
|
|
|
+ :style="{opacity: item.opacity, transform: `scale(${item.scale})`, color: arrowColor}">›</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
+ <view class="slider-bg" :style="{ 'transform': 'translateX(' + oldx + 'px)', backgroundColor: dBgColor }"></view>
|
|
|
+ <movable-area class="slider-area" :animation="true">
|
|
|
+ <movable-view :class="{ 'movable-btn': true, 'movable-success': success }" :style="sliderStyle" :x="x"
|
|
|
+ direction="horizontal" @change="onMove" :disabled="isDisable">
|
|
|
+ {{ sliderText.btnText }}
|
|
|
+ </movable-view>
|
|
|
+ </movable-area>
|
|
|
+ </view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
export default {
|
|
|
- props: {
|
|
|
- //是否禁止拖动
|
|
|
- disabled: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
- },
|
|
|
- //偏移量
|
|
|
- offset: {
|
|
|
- type: Number,
|
|
|
- default: 3
|
|
|
- },
|
|
|
- //滑动轨道默认背景色
|
|
|
- dBgColor: {
|
|
|
- type: String,
|
|
|
- default: '#f0f0f0'
|
|
|
- },
|
|
|
- //滑动轨道滑过背景色
|
|
|
- sBgColor: {
|
|
|
- type: String,
|
|
|
- default: '#b1d7ff'
|
|
|
- },
|
|
|
- //默认文字颜色
|
|
|
- dColor: {
|
|
|
- type: String,
|
|
|
- default: '#8a8a8a'
|
|
|
- },
|
|
|
- //成功文字颜色
|
|
|
- sColor: {
|
|
|
- type: String,
|
|
|
- default: '#FFFFFF'
|
|
|
- },
|
|
|
- sliderText: {
|
|
|
- type: Object,
|
|
|
- default: {
|
|
|
- successText:'验证通过',
|
|
|
- startText:'拖动滑块验证',
|
|
|
- successColor: '#72c13f',
|
|
|
- btnText: 'GO'
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
+ props: {
|
|
|
+ //是否禁止拖动
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ //偏移量
|
|
|
+ offset: {
|
|
|
+ type: Number,
|
|
|
+ default: 3
|
|
|
+ },
|
|
|
+ //滑动轨道默认背景色
|
|
|
+ dBgColor: {
|
|
|
+ type: String,
|
|
|
+ default: '#f0f0f0'
|
|
|
+ },
|
|
|
+ //滑动轨道滑过背景色
|
|
|
+ sBgColor: {
|
|
|
+ type: String,
|
|
|
+ default: '#f0f0f0'
|
|
|
+ },
|
|
|
+ //默认文字颜色
|
|
|
+ dColor: {
|
|
|
+ type: String,
|
|
|
+ default: '#8a8a8a'
|
|
|
},
|
|
|
- data() {
|
|
|
+ //成功文字颜色
|
|
|
+ sColor: {
|
|
|
+ type: String,
|
|
|
+ default: '#FFFFFF'
|
|
|
+ },
|
|
|
+ // 按钮文本
|
|
|
+ btnType: {
|
|
|
+ type: String,
|
|
|
+ default: 'start'
|
|
|
+ },
|
|
|
+ sliderText: {
|
|
|
+ type: Object,
|
|
|
+ default() {
|
|
|
return {
|
|
|
- x: 0,
|
|
|
- oldx: 0,
|
|
|
- success: false, //是否验证成功
|
|
|
- verification: 0, //验证次数
|
|
|
- isDisable: this.disabled,
|
|
|
- screenWidth: 0
|
|
|
- };
|
|
|
+ successText: '验证通过',
|
|
|
+ startText: '滑动开始服务',
|
|
|
+ successColor: '#72c13f',
|
|
|
+ btnText: '开始'
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
- mounted() {
|
|
|
- const systemInfo = uni.getSystemInfoSync()
|
|
|
- this.screenWidth = systemInfo.screenWidth
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ x: 0,
|
|
|
+ oldx: 0,
|
|
|
+ success: false, //是否验证成功
|
|
|
+ verification: 0, //验证次数
|
|
|
+ isDisable: this.disabled,
|
|
|
+ screenWidth: 0,
|
|
|
+ leftArrows: [
|
|
|
+ { opacity: 0.4, scale: 0.8 },
|
|
|
+ { opacity: 0.4, scale: 0.8 },
|
|
|
+ { opacity: 0.4, scale: 0.8 }
|
|
|
+ ],
|
|
|
+ rightArrows: [
|
|
|
+ { opacity: 0.4, scale: 0.8 },
|
|
|
+ { opacity: 0.4, scale: 0.8 },
|
|
|
+ { opacity: 0.4, scale: 0.8 }
|
|
|
+ ],
|
|
|
+ arrowAnimationTimer: null
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ sliderStyle() {
|
|
|
+ if (this.success) {
|
|
|
+ // 成功状态下根据按钮类型显示不同颜色,然后去使用相同的渐变和阴影效果
|
|
|
+ return this.btnType === 'upload'
|
|
|
+ ? 'background: linear-gradient(134deg, #FFD4CB 15%, #FF6E51 79%); box-shadow: 4rpx 4rpx 8rpx 2rpx rgba(0, 0, 0, 0.25), inset 4rpx 4rpx 4rpx 4rpx rgba(193, 193, 193, 0.25);'
|
|
|
+ : 'background: linear-gradient(134deg, #DAFFCB 15%, #22C55D 79%); box-shadow: 4rpx 4rpx 8rpx 2rpx rgba(0, 0, 0, 0.25), inset 4rpx 4rpx 4rpx 4rpx rgba(193, 193, 193, 0.25);';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.btnType === 'upload') {
|
|
|
+ return 'background: linear-gradient(134deg, #FFD4CB 15%, #FF6E51 79%); box-shadow: 4rpx 4rpx 8rpx 2rpx rgba(0, 0, 0, 0.25), inset 4rpx 4rpx 4rpx 4rpx rgba(193, 193, 193, 0.25);';
|
|
|
+ } else {
|
|
|
+ return 'background: linear-gradient(134deg, #DAFFCB 15%, #22C55D 79%); box-shadow: 4rpx 4rpx 8rpx 2rpx rgba(0, 0, 0, 0.25), inset 4rpx 4rpx 4rpx 4rpx rgba(193, 193, 193, 0.25);';
|
|
|
+ }
|
|
|
},
|
|
|
- methods: {
|
|
|
- onMove(e) {
|
|
|
- this.oldx = e.detail.x
|
|
|
- },
|
|
|
- touchend() {
|
|
|
- if (this.success || (this.oldx < 1 && this.oldx != 0.1)) return
|
|
|
- this.x = this.oldx
|
|
|
- var promptW = 0
|
|
|
- var onTrackW = 0
|
|
|
- uni.createSelectorQuery().in(this).select(".slider-prompt").boundingClientRect(data => {
|
|
|
- if (data.width > 0) {
|
|
|
- promptW = data.width
|
|
|
- uni.createSelectorQuery().in(this).select(".movable-btn").boundingClientRect(data => {
|
|
|
- if (data.width > 0) {
|
|
|
- onTrackW = data.width
|
|
|
- if (this.oldx != 0.1) this.verification++
|
|
|
- if (this.oldx > (promptW - onTrackW - this.offset)) {
|
|
|
- this.success = true
|
|
|
- this.isDisable = true
|
|
|
- this.verificationSuccess(true)
|
|
|
- } else {
|
|
|
- this.$nextTick(() => {
|
|
|
- this.x = 0
|
|
|
- this.oldx = 0
|
|
|
- })
|
|
|
- this.verificationSuccess(false)
|
|
|
- }
|
|
|
- }
|
|
|
- }).exec()
|
|
|
- }
|
|
|
- }).exec()
|
|
|
- },
|
|
|
- verificationSuccess(state) {
|
|
|
- let obj = {
|
|
|
- state: state,
|
|
|
- verification: this.verification
|
|
|
+ arrowColor() {
|
|
|
+ return this.btnType === 'upload' ? '#FF6E51' : '#22C55D';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ const systemInfo = uni.getSystemInfoSync()
|
|
|
+ this.screenWidth = systemInfo.screenWidth
|
|
|
+
|
|
|
+ if (this.btnType === 'upload') {
|
|
|
+ this.sliderText.btnText = '上传照片';
|
|
|
+ this.sliderText.startText = '滑动结束服务';
|
|
|
+ } else {
|
|
|
+ this.sliderText.btnText = '开始';
|
|
|
+ this.sliderText.startText = '滑动开始服务';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 启动箭头动画
|
|
|
+ this.startArrowAnimation();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onMove(e) {
|
|
|
+ this.oldx = e.detail.x
|
|
|
+ // 更新背景的滑动动画
|
|
|
+ const percent = Math.min(e.detail.x / (this.screenWidth - 152), 1) // 152为按钮宽度
|
|
|
+ if (percent > 0.9) {
|
|
|
+ this.$emit('nearSuccess', true)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ touchend() {
|
|
|
+ if (this.success || (this.oldx < 1 && this.oldx != 0.1)) return
|
|
|
+ this.x = this.oldx
|
|
|
+ var promptW = 0
|
|
|
+ var onTrackW = 0
|
|
|
+ uni.createSelectorQuery().in(this).select(".slider-prompt").boundingClientRect(data => {
|
|
|
+ if (data.width > 0) {
|
|
|
+ promptW = data.width
|
|
|
+ uni.createSelectorQuery().in(this).select(".movable-btn").boundingClientRect(data => {
|
|
|
+ if (data.width > 0) {
|
|
|
+ onTrackW = data.width
|
|
|
+ if (this.oldx != 0.1) this.verification++
|
|
|
+ if (this.oldx > (promptW - onTrackW - this.offset)) {
|
|
|
+ this.success = true
|
|
|
+ this.isDisable = true
|
|
|
+ this.verificationSuccess(true)
|
|
|
+ } else {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.x = 0
|
|
|
+ this.oldx = 0
|
|
|
+ })
|
|
|
+ this.verificationSuccess(false)
|
|
|
+ }
|
|
|
}
|
|
|
- this.$emit("change", obj)
|
|
|
- },
|
|
|
- //重置初始化状态
|
|
|
- initialization() {
|
|
|
- this.x = 0
|
|
|
- this.oldx = 0.1
|
|
|
- this.verification = 0
|
|
|
- this.success = false
|
|
|
- this.isDisable = false
|
|
|
- this.touchend()
|
|
|
+ }).exec()
|
|
|
+ }
|
|
|
+ }).exec()
|
|
|
+ },
|
|
|
+ // 箭头动画
|
|
|
+ startArrowAnimation() {
|
|
|
+ if (this.arrowAnimationTimer) {
|
|
|
+ clearInterval(this.arrowAnimationTimer);
|
|
|
+ }
|
|
|
+
|
|
|
+ let step = 0;
|
|
|
+ this.arrowAnimationTimer = setInterval(() => {
|
|
|
+ if (this.success) {
|
|
|
+ clearInterval(this.arrowAnimationTimer);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新左侧箭头动画
|
|
|
+ this.leftArrows.forEach((arrow, index) => {
|
|
|
+ const delay = index * 3;
|
|
|
+ const phase = (step + delay) % 15;
|
|
|
+
|
|
|
+ if (phase < 7) {
|
|
|
+ arrow.opacity = 0.4 + (phase / 7) * 0.6;
|
|
|
+ arrow.scale = 0.8 + (phase / 7) * 0.4;
|
|
|
+ } else {
|
|
|
+ arrow.opacity = 1 - ((phase - 7) / 8) * 0.6;
|
|
|
+ arrow.scale = 1.2 - ((phase - 7) / 8) * 0.4;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新右侧箭头动画
|
|
|
+ this.rightArrows.forEach((arrow, index) => {
|
|
|
+ const delay = (2 - index) * 3;
|
|
|
+ const phase = (step + delay) % 15;
|
|
|
+
|
|
|
+ if (phase < 7) {
|
|
|
+ arrow.opacity = 0.4 + (phase / 7) * 0.6;
|
|
|
+ arrow.scale = 0.8 + (phase / 7) * 0.4;
|
|
|
+ } else {
|
|
|
+ arrow.opacity = 1 - ((phase - 7) / 8) * 0.6;
|
|
|
+ arrow.scale = 1.2 - ((phase - 7) / 8) * 0.4;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ step = (step + 1) % 15;
|
|
|
+ }, 100);
|
|
|
+ },
|
|
|
+ verificationSuccess(state) {
|
|
|
+ let obj = {
|
|
|
+ state: state,
|
|
|
+ verification: this.verification
|
|
|
+ }
|
|
|
+ this.$emit("change", obj)
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+ // 成功后停止动画
|
|
|
+ if (this.arrowAnimationTimer) {
|
|
|
+ clearInterval(this.arrowAnimationTimer);
|
|
|
+ this.arrowAnimationTimer = null;
|
|
|
}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ //重置初始化状态
|
|
|
+ initialization() {
|
|
|
+ this.x = 0
|
|
|
+ this.oldx = 0.1
|
|
|
+ this.verification = 0
|
|
|
+ this.success = false
|
|
|
+ this.isDisable = false
|
|
|
+ this.startArrowAnimation(); // 重新启动动画
|
|
|
+ this.touchend()
|
|
|
}
|
|
|
+ }
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.slider-verify {
|
|
|
- position: relative;
|
|
|
- width: 100%;
|
|
|
- height: 100rpx;
|
|
|
- overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ border-radius: 50rpx;
|
|
|
}
|
|
|
|
|
|
.slider-prompt {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- top: 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 32rpx;
|
|
|
- z-index: 99;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ z-index: 99;
|
|
|
}
|
|
|
|
|
|
.slider-bg {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ transition: transform 0.3s ease-out;
|
|
|
}
|
|
|
|
|
|
.slider-area {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- height: 100%;
|
|
|
- width: 100%;
|
|
|
- z-index: 999;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ z-index: 999;
|
|
|
}
|
|
|
|
|
|
.movable-btn {
|
|
|
- width: 150rpx;
|
|
|
- height: 100%;
|
|
|
- border: solid 1px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 17px;
|
|
|
- color: #fff;
|
|
|
- /* width: 100rpx;
|
|
|
- height: 100%;
|
|
|
- box-sizing: border-box;
|
|
|
- background-color: #FFFFFF;
|
|
|
- border: solid 1px;
|
|
|
- background-image: url(/static/img/go-right.png);
|
|
|
- background-position: center;
|
|
|
- background-size: 34rpx auto;
|
|
|
- background-repeat: no-repeat;
|
|
|
- display: flex;
|
|
|
- align-content: center;
|
|
|
- justify-content: center; */
|
|
|
+ width: 152rpx;
|
|
|
+ height: 100rpx;
|
|
|
+ border: none;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: bold;
|
|
|
+ border-radius: 50rpx;
|
|
|
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
|
+ animation: pulse 2s infinite;
|
|
|
}
|
|
|
|
|
|
.movable-success {
|
|
|
- /* border: solid 1px;
|
|
|
- background-image: url(/static/img/success.png);
|
|
|
- background-size: 40rpx auto; */
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ animation: none;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.left {
|
|
|
+ margin-right: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.right {
|
|
|
+ margin-left: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow {
|
|
|
+ font-size: 32rpx;
|
|
|
+ margin: 0 4rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ transition: all 0.1s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.left .arrow-img:nth-child(1) {
|
|
|
+ animation-delay: 0s;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.left .arrow-img:nth-child(2) {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.left .arrow-img:nth-child(3) {
|
|
|
+ animation-delay: 0.4s;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.right .arrow-img:nth-child(1) {
|
|
|
+ animation-delay: 0.4s;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.right .arrow-img:nth-child(2) {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-group.right .arrow-img:nth-child(3) {
|
|
|
+ animation-delay: 0s;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes breathe {
|
|
|
+ 0% {
|
|
|
+ opacity: 0.4;
|
|
|
+ transform: scale(0.8) translateX(0);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1.2) translateX(0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ opacity: 0.4;
|
|
|
+ transform: scale(0.8) translateX(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes pulse {
|
|
|
+ 0% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(34, 197, 93, 0.4);
|
|
|
+ }
|
|
|
+ 70% {
|
|
|
+ box-shadow: 0 0 0 10rpx rgba(34, 197, 93, 0);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ box-shadow: 0 0 0 0 rgba(34, 197, 93, 0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.go-right {
|
|
|
+ display: none;
|
|
|
}
|
|
|
</style>
|