上一篇文章介绍了搭建验证码服务端API,这篇文章介绍前端代码的搭建,在实际应用中,我有写过两种类型的前端,一个是vue3的,一个是纯HTML+JQuery的。至于vue2,大家根据vue3稍作改动即可。


前端代码

  • Vue3前端代码实现
  • HTML代码
  • CSS代码
  • JS代码
  • 组件的调用
  • HTML+JQuery实现
  • HTML代码
  • CSS代码
  • Javascript代码(Jquery)


Vue3前端代码实现

vue3中,把滑动验证码写成了一个组件,在业务场景,直接调用组件即可,在一般使用过程中,只有在发送短信验证码的时候,我才调用滑动验证码来进行行为认证。

HTML代码

<template>
  <div
    class="captcha"
    style="margin-right: auto; margin-left: auto"
    :style="captchaWrapperStyle"
  >
    <div class="captcha__main" :style="imgWrapperStyle">
      <img
        v-if="src"
        ref="backgroundRef"
        alt="background"
        class="captcha_background"
        :src="src"
      />
      <img
        v-show="sliderSrc"
        ref="sliderRef"
        alt="slider"
        class="captcha_slider"
        :class="{ goFirst: isOk, goKeep: isKeep }"
        :src="sliderSrc"
      />
      <div v-if="showVerifyTip" class="captcha_message">
        <div class="captcha_message__icon">
          <svg
            v-if="isPassing"
            height="28"
            viewBox="0 0 28 28"
            width="28"
            xmlns="http://www.w3.org/2000/svg"
          >
            <g
              fill="none"
              fill-rule="evenodd"
              stroke="#fff"
              stroke-linecap="round"
              stroke-linejoin="round"
              stroke-width="1.5"
            >
              <path
                d="M22.776 4.073A13.2 13.2 0 0 0 14 .75C6.682.75.75 6.682.75 14S6.682 27.25 14 27.25 27.25 21.318 27.25 14c0-.284-.009-.566-.027-.845"
              />
              <path d="M7 12.5l7 7 13-13" />
            </g>
          </svg>
          <svg
            v-else
            height="28"
            viewBox="0 0 28 28"
            width="28"
            xmlns="http://www.w3.org/2000/svg"
          >
            <g fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="1.5">
              <circle cx="14" cy="14" r="13.25" />
              <path
                d="M8.75 8.75l10.5 10.5M19.25 8.75l-10.5 10.5"
                stroke-linecap="round"
                stroke-linejoin="round"
              />
            </g>
          </svg>
        </div>
        <div class="captcha_message__text">
          {{ isPassing ? successTip : failTip }}
        </div>
      </div>
      <div v-if="showGenerateLoadding" class="captcha_message loadding">
        <div
          class="captcha_message__icon captcha_message__icon--loadding"
        ></div>
        <div class="captcha_message__text">加载中...</div>
      </div>
      <div v-if="showVerifyLoadding" class="captcha_message">
        <div
          class="captcha_message__icon captcha_message__icon--loadding"
        ></div>
        <div class="captcha_message__text"></div>
      </div>
    </div>
    <div ref="dragVerifyRef" class="captcha__bar" :style="dragVerifyStyle">
      <div
        ref="progressBarRef"
        class="captcha_progress_bar"
        :class="{ goFirst2: isOk }"
        :style="progressBarStyle"
      ></div>
      <div class="captcha_progress_bar__text" :style="textStyle">
        {{ text }}
      </div>
      <div
        ref="handlerRef"
        class="captcha_handler"
        :class="{ goFirst: isOk }"
        :style="handlerStyle"
        @mousedown="handleDragStart"
        @touchstart="handleDragStart"
      >
        <svg
          p-id="819"
          :style="handlerSvgStyle"
          version="1.1"
          viewBox="0 0 1024 1024"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            d="M500.864 545.728a47.744 47.744 0 0 0 6.72-48.896 24.704 24.704 0 0 0-4.48-8.384L240.256 193.088a34.24 34.24 0 0 0-28.608-17.408 34.24 34.24 0 0 0-25.856 12.864 46.592 46.592 0 0 0 0 59.52l238.08 264.512-238.08 264.512a46.592 46.592 0 0 0-1.088 59.52 32 32 0 0 0 50.56 0l265.6-290.88z"
            p-id="820"
          />
          <path
            d="M523.84 248.064l236.992 264.512-238.08 264.512a46.592 46.592 0 0 0 0 59.52 32 32 0 0 0 50.56 0l265.6-292.608a47.744 47.744 0 0 0 6.72-48.832 24.704 24.704 0 0 0-4.48-8.448L578.304 191.36a34.24 34.24 0 0 0-55.552-2.816 46.592 46.592 0 0 0 1.088 59.52z"
            p-id="821"
          />
        </svg>
      </div>
    </div>
    <div v-if="showRefresh" class="captcha__actions">
      <a
        class="captcha__action"
        :style="refreshTextColorStyle"
        @click="handleRefresh"
      >
        <svg
          :fill="refreshColorStyle"
          height="20px"
          version="1.1"
          viewBox="0 0 20 20"
          width="20px"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            d="M10,4 C12.0559549,4 13.9131832,5.04358655 15.0015086,6.68322231 L15,5.5 C15,5.22385763 15.2238576,5 15.5,5 C15.7761424,5 16,5.22385763 16,5.5 L16,8.5 C16,8.77614237 15.7761424,9 15.5,9 L12.5,9 C12.2238576,9 12,8.77614237 12,8.5 C12,8.22385763 12.2238576,8 12.5,8 L14.5842317,8.00000341 C13.7999308,6.20218044 12.0143541,5 10,5 C7.23857625,5 5,7.23857625 5,10 C5,12.7614237 7.23857625,15 10,15 C11.749756,15 13.3431487,14.0944653 14.2500463,12.6352662 C14.3958113,12.4007302 14.7041063,12.328767 14.9386423,12.4745321 C15.1731784,12.6202971 15.2451415,12.9285921 15.0993765,13.1631281 C14.0118542,14.9129524 12.0990688,16 10,16 C6.6862915,16 4,13.3137085 4,10 C4,6.6862915 6.6862915,4 10,4 Z"
            fill-rule="nonzero"
          />
        </svg>
        <!-- <span class="captcha__action__text">刷新</span> -->
      </a>
    </div>
  </div>
</template>

html代码中,一些必要的图片使用svg的形式表示,代码中的图片包括有正确图标、错误图标、滑块图片、刷新图片。在此基础上,还可以扩展自己需要的图片。

CSS代码

<style scoped>
  .captcha {
    user-select: none;
  }

  .captcha__main {
    background: rgb(244, 245, 246);
  }

  .captcha_background {
    width: 100%;
  }

  .captcha_slider {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    height: 100%;
  }

  .captcha_message {
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 999999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background-color: rgba(34, 34, 34, 0.85);
    -webkit-box-pack: center;
    -webkit-box-align: center;
  }

  .captcha_message__icon {
    width: 28px;
    height: 28px;
    margin: 0px auto;
  }

  .captcha_message__icon--loadding {
    width: 24px;
    height: 24px;
    background-image: url(data:image/png;base64,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);
    background-repeat: no-repeat;
    background-position: center center;
    background-size: contain;
    border-radius: 50%;
    animation: 1s linear 0s infinite normal none running turn;
  }

  .captcha_message.loadding {
    background-color: rgb(244 245 246);
  }

  .captcha_message__text {
    display: inline-block;
    max-width: 200px;
    padding: 10px;
    font-size: 14px;
    color: rgb(255, 255, 255);
    text-align: center;
  }

  .captcha_message.loadding .captcha_message__text {
    color: rgb(202, 202, 202);
  }

  .captcha__bar {
    position: relative;
    width: 100%;
    margin-top: 5px;
    overflow: hidden;
    text-align: center;
  }

  .captcha_progress_bar {
    position: absolute;
    width: 0;
  }

  .captcha_progress_bar__text {
    position: absolute;
    top: 0px;
    width: 100%;
    font-size: 12px;
    color: transparent;
    -moz-user-select: none;
    -webkit-user-select: none;
    -o-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background: -webkit-gradient(
      linear,
      left top,
      right top,
      color-stop(0, var(--textColor)),
      color-stop(0.4, var(--textColor)),
      color-stop(0.5, #fff),
      color-stop(0.6, var(--textColor)),
      color-stop(1, var(--textColor))
    );
    -webkit-background-clip: text;
    animation: slidetounlock 3s infinite;
    -webkit-text-fill-color: transparent;
    -webkit-text-size-adjust: none;
  }

  .captcha_handler {
    position: absolute;
    top: 0px;
    left: 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 1px;
    cursor: move;
    background: rgb(255, 255, 255);
  }

  .captcha__actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 20px;
    padding: 16px 20px 20px 0px;
    line-height: 20px;
    color: rgb(80, 80, 80);
    -webkit-box-pack: justify;
    -webkit-box-align: center;
  }

  .captcha__action__text {
    font-size: 14px !important;
    color: rgb(80, 80, 80);
  }

  .captcha__action {
    display: flex;
    align-items: center;
    text-decoration: none;
    cursor: pointer;
  }

  .goFirst {
    left: 0px !important;
    transition: left 0.5s;
  }
  .goKeep {
    transition: left 0.2s;
  }
  .goFirst2 {
    width: 0px !important;
    transition: width 0.5s;
  }
</style>
<style>
  @keyframes slidetounlock {
    0% {
      background-position: var(--pwidth) 0;
    }
    100% {
      background-position: var(--width) 0;
    }
  }
  @keyframes slidetounlock2 {
    0% {
      background-position: var(--pwidth) 0;
    }
    100% {
      background-position: var(--pwidth) 0;
    }
  }

  @keyframes turn {
    0% {
      -webkit-transform: rotate(0deg);
    }
    25% {
      -webkit-transform: rotate(90deg);
    }
    50% {
      -webkit-transform: rotate(180deg);
    }
    75% {
      -webkit-transform: rotate(270deg);
    }
    100% {
      -webkit-transform: rotate(360deg);
    }
  }
</style>

JS代码

<script>
  export default defineComponent({
    name: 'SlideCaptcha',
    props: {
      width: {
        type: Number,
        default: 340,
      },
      height: {
        type: Number,
        default: 212,
      },
      barHeight: {
        type: Number,
        default: 40,
      },
      handlerIconWidth: {
        type: Number,
        default: 16,
      },
      handlerIconHeigth: {
        type: Number,
        default: 16,
      },
      background: {
        type: String,
        default: '#eee',
      },
      circle: {
        type: Boolean,
        default: false,
      },
      radius: {
        type: String,
        default: '4px',
      },
      text: {
        type: String,
        default: '按住滑块拖动',
      },
      progressBarBg: {
        type: String,
        default: '#76c61d',
      },
      successTip: {
        type: String,
        default: '验证通过,超过80%用户',
      },
      failTip: {
        type: String,
        default: '验证未通过,拖动滑块将悬浮图像正确合并',
      },
      showRefresh: {
        type: Boolean,
        default: false,
      },
      refreshColor: {
        type: String,
        default: '#505050',
      },
    },
    emits: ['finish', 'refresh'],
    setup(props, context) {
      const state = reactive({
        isMoving: false,
        x: 0,
        y: 0,
        isOk: false,
        isKeep: false,
        isFinish: false,
        tracks: [],
        startSlidingTime: undefined,
        showVerifyTip: false,
        showVerifyLoadding: false,
        showGenerateLoadding: false,
        src: '',
        sliderSrc: '',
        isPassing: false,
      })
      const imgWrapperStyle = computed(() => {
        return {
          width: props.width + 'px',
          height: props.height + 'px',
          position: 'relative',
          overflow: 'hidden',
        }
      })
      const captchaWrapperStyle = computed(() => {
        return {
          width: props.width + 'px',
        }
      })
      const dragVerifyStyle = computed(() => {
        return {
          width: props.width + 'px',
          height: props.barHeight + 'px',
          lineHeight: props.barHeight + 'px',
          background: props.background,
          borderRadius: props.circle
            ? props.barHeight / 2 + 'px'
            : props.radius,
        }
      })
      const progressBarStyle = computed(() => {
        return {
          background: props.progressBarBg,
          height: props.barHeight + 'px',
          borderRadius: props.circle
            ? props.barHeight / 2 + 'px 0 0 ' + props.barHeight / 2 + 'px'
            : props.radius,
        }
      })
      const textStyle = computed(() => {
        return {
          height: props.barHeight + 'px',
          width: props.width + 'px',
          //fontSize: this.textSize,
        }
      })
      const handlerStyle = computed(() => {
        return {
          width: props.barHeight + 'px',
          height: props.barHeight - 2 + 'px',
          //background: this.handlerBg,
        }
      })
      const handlerSvgStyle = computed(() => {
        return {
          width: props.handlerIconWidth + 'px',
          height: props.handlerIconHeigth + 'px',
        }
      })
      const refreshColorStyle = computed(() => {
        return props.refreshColor
      })
      const refreshTextColorStyle = computed(() => {
        return {
          color: props.refreshColor,
        }
      })

      const dragVerifyRef = ref(null)
      const progressBarRef = ref(null)
      const backgroundRef = ref(null)
      const sliderRef = ref(null)
      const handlerRef = ref(null)
      onMounted(() => {
        const dragEl = dragVerifyRef
        dragEl.value.style.setProperty('--textColor', '#333')
        dragEl.value.style.setProperty(
          '--width',
          Math.floor(props.width / 2) + 'px'
        )
        dragEl.value.style.setProperty(
          '--pwidth',
          -Math.floor(props.width / 2) + 'px'
        )
      })
      // 开始请求生成图片时调用
      const startRequestGenerate = () => {
        reset()
        state.showGenerateLoadding = true
      }
      // 结束请求生成图片时调用
      const endRequestGenerate = (src, sliderSrc) => {
        state.showGenerateLoadding = false
        state.src = src
        state.sliderSrc = sliderSrc
      }
      // 开始请求校验时调用
      const startRequestVerify = () => {
        state.showVerifyLoadding = true
      }
      // 结束请求校验时调用
      const endRequestVerify = (isPassing) => {
        state.isPassing = isPassing
        state.showVerifyLoadding = false
        state.showVerifyTip = true
      }
      const reset = () => {
        state.x = 0
        state.y = 0
        state.tracks = []
        state.isMoving = false
        state.isFinish = false
        state.showGenerateLoadding = false
        state.showVerifyLoadding = false
        state.showVerifyTip = false
        state.isPassing = false
        if (progressBarRef) progressBarRef.value.style.width = 0
        if (sliderRef) sliderRef.value.style.left = 0
        if (handlerRef) handlerRef.value.style.left = 0
      }
      const removeEventListeners = () => {
        window.removeEventListener('touchmove', handleDragMoving)
        window.removeEventListener('touchend', handleDragFinish)
        window.removeEventListener('mousemove', handleDragMoving)
        window.removeEventListener('mouseup', handleDragFinish)
      }
      const handleDragStart = (e) => {
        if (
          !state.isPassing &&
          state.src &&
          state.sliderSrc &&
          !state.isFinish
        ) {
          window.addEventListener('touchmove', handleDragMoving)
          window.addEventListener('touchend', handleDragFinish)
          window.addEventListener('mousemove', handleDragMoving)
          window.addEventListener('mouseup', handleDragFinish)

          state.isMoving = true
          state.startSlidingTime = new Date()
          state.x = e.pageX || e.touches[0].pageX
          state.y = e.pageY || e.touches[0].pageY
        }
      }
      const handleDragMoving = (e) => {
        if (
          state.isMoving &&
          !state.isPassing &&
          state.src &&
          state.sliderSrc &&
          !state.isFinish
        ) {
          const _x = (e.pageX || e.touches[0].pageX) - state.x
          const _y = (e.pageY || e.touches[0].pageY) - state.y

          handlerRef.value.style.left = _x + 'px'
          progressBarRef.value.style.width = _x + props.barHeight / 2 + 'px'
          sliderRef.value.style.left = _x + 'px'

          state.tracks.push({
            x: Math.round(_x),
            y: Math.round(_y),
            t: new Date().getTime() - state.startSlidingTime.getTime(),
          })
        }
      }
      const handleDragFinish = () => {
        if (
          state.isMoving &&
          !state.isPassing &&
          state.src &&
          state.sliderSrc &&
          !state.isFinish
        ) {
          state.isMoving = false
          state.isFinish = true
          removeEventListeners()
          context.emit('finish', {
            backgroundImageWidth: backgroundRef.value.offsetWidth,
            backgroundImageHeight: backgroundRef.value.offsetHeight,
            sliderImageWidth: sliderRef.value.offsetWidth,
            sliderImageHeight: sliderRef.value.offsetHeight,
            startTime: state.startSlidingTime,
            endTime: new Date(),
            tracks: state.tracks,
          })
        }
      }
      const handleRefresh = () => {
        reset()
        context.emit('refresh')
      }

      onUnmounted(() => {
        removeEventListeners()
      })
      onDeactivated(() => {
        removeEventListeners()
      })
      return {
        ...toRefs(state),
        imgWrapperStyle,
        captchaWrapperStyle,
        dragVerifyStyle,
        progressBarStyle,
        textStyle,
        handlerStyle,
        handlerSvgStyle,
        refreshColorStyle,
        refreshTextColorStyle,
        dragVerifyRef,
        progressBarRef,
        backgroundRef,
        sliderRef,
        handlerRef,
        startRequestGenerate,
        endRequestGenerate,
        startRequestVerify,
        endRequestVerify,
        reset,
        removeEventListeners,
        handleDragStart,
        handleDragMoving,
        handleDragFinish,
        handleRefresh,
      }
    },
  })
</script>

代码定义了两个emit,一个是刷新,一个是拖动结束。代码还是比较简单。

组件的调用

<template>
  <div class="login-container">
    <div class="captcha_box">
      <slide-captcha
        ref="captchaRef"
        :fail-tip="failTip"
        :height="height"
        refresh-color="#FFFFFF"
        :show-refresh="true"
        :success-tip="successTip"
        :width="width"
        @finish="handleFinish"
         @refresh="generate"
      />
    </div>
  </div>
</template>

<script>
 
  import SlideCaptcha from './components/SlideCaptcha.vue'
  import { checkCaptcha, captcha } from '@/api/user'

  export default defineComponent({
    name: 'Captcha',
    components: { SlideCaptcha },
    setup() {
      const captchaRef = ref(null)
      const state = reactive({
        width: 340,
        height: 212,
        failTip: '',
        successTip: '',
        requestId: undefined,
      })

      
      const captchaShow = async () => {
        await generate()
      }
      //生成验证码
      const generate = async () => {
        nextTick(async () => {
          captchaRef.value.startRequestGenerate()
          //封装了一个方法,其实就是使用axios发送了一个get请求。
          await captcha().then((result) => {
            if (result.success) {
              state.requestId = result.data.Id
              captchaRef.value.endRequestGenerate(
                result.data.BackgroundImage,
                result.data.SliderImage
              )
            } else {
              captchaRef.value.endRequestGenerate(null, null)
            }
          })
        })
      }
      //验证码校验
      const handleFinish = async (data) => {
        nextTick(async () => {
          captchaRef.value.startRequestVerify()
          //封装了一个方法,其实就是使用axios发送了一个get请求。
          await checkCaptcha(state.requestId, data).then(async (result) => {
            if (result.success) {
              state.successTip = '验证通过'
              captchaRef.value.endRequestVerify(result.success)
              
            } else {
              state.failTip = '验证未通过'
              await generate()
            }
          })
        })
      }
      return {
        ...toRefs(state),
        captchaShow,
        handleFinish,
        generate,
        captchaRef,
      }
    },
  })
</script>

<style lang="scss" scoped>
  
  .captcha_box {
    position: relative;
    max-width: 100%;
    padding: 4.5vh;
    margin: calc((100vh - 555px) / 2) 5vw 5vw;
    overflow: hidden;
    
  }
</style>

HTML+JQuery实现

一般在管理系统中,使用vue来实现又快又好,但在实际使用过程中,甲方爸爸要求不能用vue来写前端,因为要考虑到搜索引擎优化(哎,此处省略好多字…)。这时就需要传统html代码来实现了。

HTML代码

<div class="captcha" style="width:365px;margin:0 auto;margin-top:20vh;">
    <div class="captcha__main" style="position:relative; overflow:hidden;width:365px; height:228px;">
        <img src="" class="captcha_background" id="captchaSrc" />
        <img src="" class="captcha_slider" id="sliderSrc" />
        <div class="captcha_message" id="showVerifyTip">
            <div class="captcha_message__icon">
                <svg style="display:none;" id="isPassing" height="28" width="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
                    <g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
                        <path d="M22.776 4.073A13.2 13.2 0 0 0 14 .75C6.682.75.75 6.682.75 14S6.682 27.25 14 27.25 27.25 21.318 27.25 14c0-.284-.009-.566-.027-.845" />
                        <path d="M7 12.5l7 7 13-13" />
                    </g>
                </svg>
                <svg style="display:none;" id="isFail" height="28" width="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
                    <g fill="none" fill-rule="evenodd" stroke="#fff" stroke-width="1.5">
                        <circle cx="14" cy="14" r="13.25" />
                        <path d="M8.75 8.75l10.5 10.5M19.25 8.75l-10.5 10.5" stroke-linecap="round" stroke-linejoin="round" />
                    </g>
                </svg>
            </div>
            <div class="captcha_message__text" id="resultTipMessage"></div>
        </div>
        <div class="captcha_message loadding" id="showGenerateLoadding">
            <div class="captcha_message__icon captcha_message__icon--loadding"></div>
            <div class="captcha_message__text">加载中...</div>
        </div>
        <div class="captcha_message" id="showVerifyLoadding">
            <div class="captcha_message__icon captcha_message__icon--loadding"></div>
            <div class="captcha_message__text">请稍等...</div>
        </div>
    </div>
    <div class="captcha__bar" style="width:365px;height:40px; line-height:40px;background:#eee;border-radius:4px;">
        <div class="captcha_progress_bar" style="background:#76c61d;height:40px;border-radius:4px;"></div>
        <div class="captcha_progress_bar__text" style="width:365px;height:40px;">按住滑块拖动</div>
        <div class="captcha_handler" style="width:40px;height:38px;" onmousedown="captcha.handleDragStart(event)" ontouchstart="captcha.handleDragStart(event)">
            <svg p-id="819" style="width:16px;height:16px;" version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
                <path d="M500.864 545.728a47.744 47.744 0 0 0 6.72-48.896 24.704 24.704 0 0 0-4.48-8.384L240.256 193.088a34.24 34.24 0 0 0-28.608-17.408 34.24 34.24 0 0 0-25.856 12.864 46.592 46.592 0 0 0 0 59.52l238.08 264.512-238.08 264.512a46.592 46.592 0 0 0-1.088 59.52 32 32 0 0 0 50.56 0l265.6-290.88z"
                      p-id="820" />
                <path d="M523.84 248.064l236.992 264.512-238.08 264.512a46.592 46.592 0 0 0 0 59.52 32 32 0 0 0 50.56 0l265.6-292.608a47.744 47.744 0 0 0 6.72-48.832 24.704 24.704 0 0 0-4.48-8.448L578.304 191.36a34.24 34.24 0 0 0-55.552-2.816 46.592 46.592 0 0 0 1.088 59.52z"
                      p-id="821" />
            </svg>
        </div>
    </div>
    <div class="captcha__actions">
        <a class="captcha__action closeCaptcha" style="color:#505050;">
            <svg t="1663301405680" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2376" width="15" height="15"><path d="M511.232 438.8352L112.9984 40.6016A51.2 51.2 0 0 0 40.6016 112.9984L438.784 511.232 40.6016 909.4656a51.2 51.2 0 1 0 72.3968 72.448l398.2336-398.2848 398.2336 398.2848a51.2 51.2 0 1 0 72.448-72.448l-398.2848-398.2336 398.2848-398.2336A51.2 51.2 0 0 0 909.4656 40.6016L511.232 438.784z" p-id="2377" fill="#505050"></path></svg>
        </a>
        <a class="captcha__action refreshCaptcha" style="color:#505050;">
            <svg style="color:#505050;"
                 height="25px"
                 version="1.1"
                 viewBox="0 0 20 20"
                 width="25px"
                 xmlns="http://www.w3.org/2000/svg">
                <path d="M10,4 C12.0559549,4 13.9131832,5.04358655 15.0015086,6.68322231 L15,5.5 C15,5.22385763 15.2238576,5 15.5,5 C15.7761424,5 16,5.22385763 16,5.5 L16,8.5 C16,8.77614237 15.7761424,9 15.5,9 L12.5,9 C12.2238576,9 12,8.77614237 12,8.5 C12,8.22385763 12.2238576,8 12.5,8 L14.5842317,8.00000341 C13.7999308,6.20218044 12.0143541,5 10,5 C7.23857625,5 5,7.23857625 5,10 C5,12.7614237 7.23857625,15 10,15 C11.749756,15 13.3431487,14.0944653 14.2500463,12.6352662 C14.3958113,12.4007302 14.7041063,12.328767 14.9386423,12.4745321 C15.1731784,12.6202971 15.2451415,12.9285921 15.0993765,13.1631281 C14.0118542,14.9129524 12.0990688,16 10,16 C6.6862915,16 4,13.3137085 4,10 C4,6.6862915 6.6862915,4 10,4 Z"
                      fill-rule="nonzero" />
            </svg>
        </a>
    </div>
</div>

没有了vue的双向绑定,需要定义一堆id来确保dom操作。确实比较麻烦。

CSS代码

其实把vue的css代码拷贝了一下。

.captcha {user-select:none;background:#ffffff;padding:5px;border-radius:3px;}
.captcha__main {background:rgb(244,245,246);}
.captcha_background {width:100%;}
.captcha_slider {position:absolute;top:0;left:0;display:block;height:100%;}
.captcha_message_box {width:100%;height:100%;position:absolute;top:0px;left:0px;z-index:999999}
.captcha_message {position:absolute;top:0px;left:0px;z-index:999998;display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%;background-color:rgba(34,34,34,0.85);-webkit-box-pack:center;-webkit-box-align:center;}
.captcha_message__icon {width:28px;height:28px;margin:0px auto;}
.captcha_message__icon--loadding {width:24px;height:24px;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center center;background-size:contain;border-radius:50%;animation:1s linear 0s infinite normal none running turn;}
.captcha_message.loadding {background-color:rgb(244 245 246);}
.captcha_message__text {display:inline-block;max-width:200px;padding:10px;font-size:14px;color:rgb(255,255,255);text-align:center;}
.captcha_message.loadding .captcha_message__text {color:rgb(202,202,202);}
.captcha__bar {position:relative;width:100%;margin-top:5px;overflow:hidden;text-align:center;}
.captcha_progress_bar {position:absolute;width:0;}
.captcha_progress_bar__text {position:absolute;top:0px;width:100%;font-size:12px;color:transparent;-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;-ms-user-select:none;user-select:none;background:-webkit-gradient( linear,left top,right top,color-stop(0,var(--textColor)),color-stop(0.4,var(--textColor)),color-stop(0.5,#fff),color-stop(0.6,var(--textColor)),color-stop(1,var(--textColor)) );-webkit-background-clip:text;animation:slidetounlock 3s infinite;-webkit-animation:slidetounlock 3s infinite;-webkit-text-fill-color:transparent;-webkit-text-size-adjust:none;}
.captcha_handler {position:absolute;top:0px;left:0px;display:flex;align-items:center;justify-content:center;margin:1px;cursor:move;background:rgb(255,255,255);}
.captcha__actions {display:flex;align-items:center;justify-content:flex-start;min-height:20px;padding:16px 20px 20px 0px;line-height:20px;color:rgb(80,80,80);-webkit-box-pack:justify;-webkit-box-align:center;}
.captcha__action__text {font-size:14px !important;color:rgb(80,80,80);}
.captcha__action {display:flex;align-items:center;text-decoration:none;cursor:pointer;margin-left:5px;margin-right:5px;}
.goFirst {left:0px !important;transition:left 0.5s;}
.goKeep {transition:left 0.2s;}
.goFirst2 {width:0px !important;transition:width 0.5s;}
@keyframes slidetounlock {0% {background-position:var(--pwidth) 0;}
100% {background-position:var(--width) 0;}
}
@-webkit-keyframes slidetounlock {0% {background-position:var(--pwidth) 0;}
100% {background-position:var(--width) 0;}
}
@keyframes slidetounlock2 {0% {background-position:var(--pwidth) 0;}
100% {background-position:var(--pwidth) 0;}
}
@keyframes turn {0% {-webkit-transform:rotate(0deg);}
25% {-webkit-transform:rotate(90deg);}
50% {-webkit-transform:rotate(180deg);}
75% {-webkit-transform:rotate(270deg);}
100% {-webkit-transform:rotate(360deg);}
}

Javascript代码(Jquery)

因为最开始学习前端的时候,接触的最多的就是Jquery。所以就使用Jquery来吧。

<script type="text/javascript">
    //定义captcha
    const captcha = {
        x: 0,
        y: 0,
        tracks: [],
        isPassing: false,
        isFinish: false,
        isMoving: false,
        src: "",
        sliderSrc: "",
        startSlidingTime: undefined,
        init: function () {
            let captchaBar = document.querySelector('.captcha__bar');
            captchaBar.style.setProperty('--textColor', '#333')
            captchaBar.style.setProperty('--width', Math.floor($('.captcha').width() / 2) + 'px')
            captchaBar.style.setProperty('--pwidth', -Math.floor($('.captcha').width() / 2) + 'px')
        },
        startRequestGenerate: function () {
            this.reset();
            $("#showGenerateLoadding").show();
        },
        endRequestGenerate: function (src, sliderSrc) {
            $("#showGenerateLoadding").hide();
            this.src = src;
            this.sliderSrc = sliderSrc;
            $("#captchaSrc").attr('src', src);
            $("#sliderSrc").attr('src', sliderSrc);
        },
        startRequestVerify: function () {
            $("#showVerifyLoadding").show();
        },
        handleDragStart: function (e) {
            let that = this;
            if (!that.isPassing && that.src && that.sliderSrc && !that.isFinish) {
                window.addEventListener('touchmove', that.handleDragMoving)
                window.addEventListener('touchend', that.handleDragFinish)
                window.addEventListener('mousemove', that.handleDragMoving)
                window.addEventListener('mouseup', that.handleDragFinish)
                that.isMoving = true;
                that.startSlidingTime = new Date();
                that.x = e.pageX || e.touches[0].pageX;
                that.y = e.pageY || e.touches[0].pageY;
            }
        },
        endRequestVerify: function (isPassing) {
            this.isPassing = isPassing;
            $("#showVerifyLoadding").hide();
            $("#showVerifyTip").show();
            if (isPassing) {
                $("#isPassing").show();
            } else {
                $("#isFail").show();
            }
        },
        handleDragMoving: function (e) {
            if (captcha.isMoving && !captcha.isPassing && captcha.src && captcha.sliderSrc && !captcha.isFinish) {
                const _x = (e.pageX || e.touches[0].pageX) - captcha.x
                const _y = (e.pageY || e.touches[0].pageY) - captcha.y
                $('.captcha_handler').css('left', _x + 'px');
                $('.captcha_progress_bar').css('width', _x + 20 + 'px');
                $('#sliderSrc').css('left', _x + 'px');
                captcha.tracks.push({
                    x: Math.round(_x),
                    y: Math.round(_y),
                    t: new Date().getTime() - captcha.startSlidingTime.getTime()
                })
            }
        },
        handleDragFinish: function () {
            if (captcha.isMoving && !captcha.isPassing && captcha.src && captcha.sliderSrc && !captcha.isFinish) {
                captcha.isMoving = false;
                captcha.isFinish = true;
                captcha.removeEventListeners();
                handleFinish({
                    backgroundImageWidth: $("#captchaSrc").get(0).offsetWidth,
                    backgroundImageHeight: $("#captchaSrc").get(0).offsetHeight,
                    sliderImageWidth: $("#sliderSrc").get(0).offsetWidth,
                    sliderImageHeight: $("#sliderSrc").get(0).offsetHeight,
                    startTime: captcha.startSlidingTime,
                    endTime: new Date(),
                    tracks: captcha.tracks
                })
            }
        },
        reset: function () {
            this.x = 0;
            this.y = 0;
            this.tracks = [];
            this.isPassing = false;
            this.isFinish = false;
            this.isMoving = false;
            $("#showGenerateLoadding").hide();
            $("#showVerifyLoadding").hide();
            $("#showVerifyTip").hide();
            $("#isPassing").hide();
            $("#isFail").hide();
            $(".captcha_progress_bar").css('width', "0");
            $(".captcha_slider").css('left', 0);
            $(".captcha_handler").css('left', 0);
        },
        removeEventListeners: function () {
            let that = this;
            window.removeEventListener('touchmove', that.handleDragMoving)
            window.removeEventListener('touchend', that.handleDragFinish)
            window.removeEventListener('mousemove', that.handleDragMoving)
            window.removeEventListener('mouseup', that.handleDragFinish)
        }
    }
    //生成验证码
    function generate() {
        captcha.init();
        captcha.startRequestGenerate();
        $.get('/home/captcha', function (result) {
            if (result.success) {
                requestId = result.data.id;
                captcha.endRequestGenerate(result.data.backgroundImage, result.data.sliderImage);
            } else {
                captcha.endRequestGenerate(null, null)
            }
        })
    }
    //验证码校验
    function handleFinish(data) {
        captcha.startRequestVerify();
        let times = data.endTime - data.startTime;
        let seconds = Math.floor(((times / 1000) % 60) * 100) / 100;
        $.ajax({
            type: "post",
            url: "/home/validate",
            dataType: "json",
            data: {
                "id": requestId,
                "track": data
            },
            success: function (result) {
                if (result.success) {
                    $("#resultTipMessage").html("验证通过,用时" + seconds + "秒");
                    captcha.endRequestVerify(result.success);


                } else {
                    $("#resultTipMessage").html("验证未通过,拖动滑块将悬浮图像正确合并");
                    generate();
                }
            }
        })


    }
    //使用
    $(document).ready(function () {
        generate();
    })
    //点击关闭按钮
    $(".closeCaptcha").on('click', function () {
       console.log("关闭验证码框");
       captcha.endRequestGenerate(null, null)
        $('.captcha').hide();
    })
    //点击刷新按钮
    $(".refreshCaptcha").on('click', function () {
        generate();
    })
</script>

内容不算复杂,经过快1个月的时间,滑动验证码所有部分都完成了。感谢大家的关注!文笔不好,写的内容都是属于直接上代码,看懂看不懂靠自己的那种-_-! 其实,对于新手而言,仔细琢磨一下,对于以后也是很有帮助的。