123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- <template>
- <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: '#f0f0f0'
- },
- //默认文字颜色
- dColor: {
- type: String,
- default: '#8a8a8a'
- },
- //成功文字颜色
- sColor: {
- type: String,
- default: '#FFFFFF'
- },
- // 按钮文本
- btnType: {
- type: String,
- default: 'start'
- },
- sliderText: {
- type: Object,
- default() {
- return {
- successText: '验证通过',
- startText: '滑动开始服务',
- successColor: '#72c13f',
- btnText: '开始'
- }
- }
- },
- },
- 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);';
- }
- },
- 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)
- }
- }
- }).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;
- 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;
- }
- .slider-bg {
- 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;
- }
- .movable-btn {
- 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 {
- 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>
|