.NET 6 实现滑动验证码(十)、大结局:前端代码实战,vue3与HTML+JQuery

上一篇文章介绍了搭建验证码服务端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>
        
      a>
    div>
  div>
template>

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

CSS代码




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

下载方式:
点击下方公众号卡片,关注我,回复captcha 免费领取!

你可能感兴趣的:(滑动验证码,前端,html,jquery,vue)