代码编织梦想

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

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,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAAAXNSR0IArs4c6QAADw5JREFUeAHlnHts1WcZx8/vd0rpHSjtBAcMGCABgY1xKXeqYpQwgzHjDxPnXAzRaTRZTPxjMbBETabxL40mBLPExGVhcXMxME1wEOQy7hBGgFG5jCKutKWlLb33+Pk+57w/f6ftgdNy2rWHN3n7vLff+z7P9/c8z3s5769eZBhDLBbzjxw5MtP3/RnEKQw9BTqpq6uryPO8AtL5PT09UcpbFaPR6D2e+YT0Dcqrqa9asmTJZdr2UDYswRvqUQ4fPlyKoCsQaiFjfZ5YhNAeZZ4oeQ/hfVFCRAABROQ+bVqoO0v7k+3t7fvXrl17m/SQhSEB6Pz587ktLS0VcL2OuIjoS3qEFzFgHEBkI93d3VFRgv4YWKkAUhsFUdooc4L0e7W1tfs2btzYTjqjIaMAXb16Ne/27dsb4PBZmB8PDYQJpyU8eT9BA1Akd1iD0KRwGwNFHfJcACJNnCbWU/UG8S+Y4T36z0jICEAwHD127NhXYG4zAhaHmKYo/rbhNkiHAaKtgRJvlqxB1A0EIAOQce4SX0eD36ysrOx6WJQeGqDjx49/Dia+AzDTnEb0B4oYpVxvXiHQArV1GqFyYlg77guQ2isk+gvSysPPFfD95dKlS0+TH3QYNEAIlcOM9E2Y2CDO8Cliwql7mJr/UR1Bwnfy7AXiFYS4OWbMmOqxY8fe4Y235uTkaPaKjBs3Lv/OnTsF5EuZ4aZRNJX2c6GL6COPdBjEYKxE/waY+iEo/SY8/gaz67SSAf4ZFECHDh16LDc39yWc60yYshkHYZKccII50w7aNJH/F22OAsrlwTJ74sSJMQg7r6OjoxL6ZYAqpW8DhLRHWaBxyouHRP0F0j9ZtmzZDeiAwoAB+uCDD+bBxw9hoICRgrcnDTJu4NfNSoBxCVD+tnjx4jPUdQ+Iswc03rdvX05xcXEFGvY8fT8tXuArAMvxlgBIpIX4Y17O0Qd0nVQ9IIBOnTq1pLOz83uAo2lZZhVzjFBmb49iOd2PAGYXzFxMGm2IMqdPn34KoL4PD0uJTnPMDB1/ogAo8/4pmvT3dFlJGyBmqbUM8C20I1jTAILA0MARGNQCr5GB34CBw+kykMl2+MSv8tJeBoRyeOljbgJPL5WwHR53pTN2WgAdPXp0CR1vpWMfQNSvPUeZ3pKCB0AfAtjv0ZqMrUHiXQ/s78GDB4tx/K+hyWvEl56G5/gMQqFwS/T4MjPce4l0SvJAgHCMcxngR/SgQdSeYcwxmq9RzwD1j927d+/avn37sO2RNG6qoBfJ8uNl6l9QG9g1gJSUBYjSpovZ80X84xG1SRXuC9CBAwfKma1eYYA8BwodmdYozyDdkD/xJg6lGuDTLEfzNzP+q2hNLryaCG4CoVz5Zuq+htZ/nIpPZyJ96jVL5OXlfZdO8qik/3hwDcmp81+NVHDE5/Lly/8KIC/A6x3ksOWIKAFi+RKs43faO6qwv5ASoIKCgq/zwOOAYMt1qNmu8nTeDv0tyP+7v05HUtnKlStPw+8PAAKsuvEG0Qi8WwQ4WcGCpqamV1Lx3C9A+J0n6WStHqIDwQ3BsG1iiMnn/Hk0gOOE1nYDGX4mYBBDwmhRq9lYTSDet/FZy1z7MO0D0K5du6I8vIVZSZ3FSAuQHoFDlBPe+yDHFh5gpKSZ1t9Gjj8KGHgykJw2iSLqL+RWevPbB6CZM2eu5YHPJACRZxNIFlHR88xW7/TuZLTkn3nmmdfg9QDRZmPkkjJEkSsHgOYWFRW92FuWJIAuX748lgc28EBgVoBFUY/yjWjV6yNlKu8tSDp5ZOgBCC1ZahNaQ1F8IldCdWfPni0M95UEUH19/SoqdS4cmJWeU574Dg7PdtvhDkZbuqKi4i4y/RwwTHZoHKH48qWUY9znwzIFAGmnTON1RJmVXL5AibH3irAWusSUqaPNrAg47XeR7ShapJW1RQQzs4O+hC/S0sZCABC5BTSWernZyvyPfBGd7Yk3z56/yPVr5LL9GjrhcBBI5fiiTU5SV6HN5hKBIZNSpIFF0lUgfsU9kC0UmY4hm44+nOaERdviMgYQzrmEjeYMzEoW1gNYMTlnZaD7XONso8j7B0R0u35N8dqzKa5ndT1J8hpADQ0NC2hoO11pkcAhaG3QXFVVdUkNszG0tra+j7x1aJJz1LaQRFaPI+BnJbMzsSeVARAtBM20SMsXndqyZUtGTwI1zkgJiV893paoCYykJJqchMs68emzrlHldKLAEUgCSOuebhqeUzqbAzLuFiiYm07SdN4lPATQGuX9zZs3TyYxlmiaQ4WSAqvz3r17N7IZHMnGjHUSedsACVzM1Jw2jWN/ttAHOTmjwKyEjgJtr2Xih7eRDvD8+fM7EPd4HBsZjrkjo/xZ4DNjlQkQVEu1gQ8CuOqRLlym+EN7ziC/mRh9SmlsjwYsc3xmrIkUSmNoYzOXrYMoH9JbE5kSLhP9AEQV0dZDoshu50X0PUdz/3gQNMccHoyGteF8NqdRDt05koj2x8kKLk/kYGI6rzWAwkDx62Wza5jtFI3RJS235HFrIdHxOSCXS3QABVjk5+dn/K5N0PkIS7AhbwYD550NIGWJxZr3TYPIxHBOUiYLHC49MgAx1evugPNBbpqXU9YWLEfXvuydYmIj7N0OKzvag1lweEB9mVgbKpPvKh09efLkWNKf6q+kjpehps3NzcXgEDho8HBDNudgVh3k+gDEIZl+K3okAAKbMch6wKHiKOWf5GBWrSSKXKGo1kOU6fCsIVyerWnkvQcOr/aWj7IWrYMaaFAerqQixvRfStnNcHm2pjmnlpPuo0GS18dJ1wmQcFQF6yAB9EgETjRsBkNYRwO5fU7x61kH6HBe07wO6y2yeHosaJXliW3btnkc1NteDAetE0alLY/i+DVOe/A7tg+D6pfUx9Uoy7GxReGOHTv89evXR/bv328zmaPIHotS2XLz5s3FgKJFIyS+qqbSr66uvr5z507dO87aILmnTZsWbWtri3BfyLt165bH73+SV2DZUaPOoG9Ii2hsB/Vojo4/uimzo9isRQfBNm3aFH3uuee8c+fORWtqanzFt956y0dxTFls6Qwe1wWKEAtFqd9corXJRpCQzWMy0n1vf/r06RH2n97s2bMjHNj7W7dutSNoE567QJcFTCi48+k87vxNz0ZwJBO+Jsp62Kurq/NmzZrlycT41sTAovr/AHHs2IxJXde+DLOyqy7ol67X9QDeU9kKEFuMXC5PyQd5aFFUaX7m8qdMmWKTleQOzAc/dCG8m6fOzA3gJqNFn802kJjWczCnKDdiPQFTXl6uOwjeokWLIkxOwU9dWklboFEV2365b335F2N9ZD8k6vICqreU8nfjLbPmbx4apIWy7UwnTJhgdN68eaIBQDbvO5FRM033K5QXSKJoUESmx9bjnyzJ5atGfdA9KJxzITOW+RlkiyGj7nrHcNZtaFZwFhaYmKRG3c4BUBtJ23qIChxpESZYsWfPHh2BjOqgH0pxxIVojydQFMvKykxRACeGs+4IC5gEEBczOwHpREJ7DCQA0xYkAkgFJSUlX9TUGO5gNKXFO+ueYnyNyS3Ki/f4jd7y+J4OyRuWqY+wdOKfOXPmGzxU6sxMWqSHtAyggw8BUtdGRl24ePFiMVM6y518LYQNCE1MEqS0tLQT7WnqDVCSBqkhDbRZPShQBJCCyqVFCtjpfD6Jmm2ZUfSHG3QFd+/eLcQK9LuXh6WY7EpLjMbGRp2LJWmPyvsApEI05BbkvMARUALHaRNUG7YV+qhObUdDkO9krzWeaNftkMGo450vCtrkXlw+TPsFSA2uXbumO3z246GOQhQBykxNqBO/wPdjSQdt4Y5HSlozFlebJ7KEkUM2eXEfpjUsbXTkHFu4cGHKo+WUAOleEOb0PrbaGTY3Z3YAlAtoG9CkEbuh5ZZYEVP5JNZ0vrSHXUGEb2ERwZdj9uSLuMHS2J9puRfcx0m7Cke1imah+CXAUsdmowka/I5G2wuskXTdv48Nu36Gk+IZPD4ALOXlFhNj+B1ITwxTijFTaeErYCIzZsyoC695+uPxgQDpIT55nM7Cap3SDG7eWv4JLbKZTYChTf9h8CNoXtI6Qs8MZ9CnFJiUPjrOF48ChiuGdqVZwMgaBBQA1adz7zstgCQg317NgVRoTOU1uNMkUa1CGVwzwRkc3sdqM9yB2bWEl1bGy/IBSCzGRLkgb1SHYiwKu8k3VFZWpnX3IG2AJCxO+QkGX8VbsecAJNCmxDLAQCNdA2PnAWpYbohoCoe9MsbVN/UxpnDtJbX5NMoL7BY40ia+yb8NXymdcu+XOiCA9DC2PYkB18CMbubbEkBMobbu2rDMrYc3KX9UC1NVTLM1mf7GgyE9tLpYGkNan0+Y+YgXpeHP+BHPOGe7dABINemYlZ5xYcAA6UHNDgy2ijhB5gUzcnyQbu3ZxKABJGZprnw7plddWFh4i91yA2kzU8dEulT7KI5HC3gB4/CJ4+PDxO9zkw4AUn+0CbQbntrR9k9SrXXuN/6gAFKHcoaTJ09+CmFnCRQJLaoAaMHbg/H4EhygBKbytK2HNmACLdAWhO0g3cWNEmvLvQAdXumsZgxvPw//lkeTAvovJK01mPXPs3anW2PKrKQ1ogpKq572jWvWrKllzEHNsIMGyKGO/ZfB8NPY/DjKkrRJgMCgbVlU54CTAEqrTMEksZTNjCY8GtnDTBMTVZUDpXdas5LKAFCr/W5RgcSzHcxm/506depDfaH00AAJKPjz8U2zYHYOgOgigHi24IBQO95ioPauXI36AwhAJHSglakAcuUChb2WAO1Ce2pXrFihD3kHpTXi1YXgRNEVDIbCiLThI2S9guOcAdOzYDLpS2IAERbmo5RQnmeUtPNfUcC1etF0AqDYgR7mqeZdmGMdZqp/tDQoH9ffmBnRoN4dyz9xED6Z8ikwO1HCq400JdG2j7mpjYLTCGhKDUJT4435izNuotv61atXa8vg+k8M8/BkSAAKs3WVf9vFSnYSQkzEZDTrmSd3QEhSmZuoNAdt0B7QNKk/EwME7XmaiHfps2EwM1OYvwelhxygMANg4O3du7eEXxC0TChA2AKEzAMU3QeMAoh+p8I6sU9mNbSjEyDaaNcKtX8bqJ03+Yf2LWG+7pf+H1cNxp97QPvbAAAAAElFTkSuQmCC);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个月的时间,滑动验证码所有部分都完成了。感谢大家的关注!文笔不好,写的内容都是属于直接上代码,看懂看不懂靠自己的那种-_-! 其实,对于新手而言,仔细琢磨一下,对于以后也是很有帮助的。

下载地址(包括验证码源码、服务端API示例、前端代码有HTML+JQyeryt、vue3两个示例)

https://pan.baidu.com/s/19mx24FXrnqz9u2mmFqlr6g?pwd=7636

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sd2208464/article/details/127993825

前端的学习路线_wit1997的博客-爱代码爱编程

前端越发展越复杂,知识点越来越庞杂。 前几年号称要一统前端江湖的backbone,估计新入行的童鞋都没听说过。前年很火的Angular也逐渐被React和Vue赶超和碾压。 Backbone.js React,

前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)持续更新-爱代码爱编程

文档描述 本文是关注微信小程序的开发和面试问题, 由基础到困难循序渐进, 适合面试和开发小程序。 并有热点框架(vue react node.js 全栈)前端资源以及后端视频资源和源码 并基于前端进阶和面试的需求 总结了常用插件和js算法 以及html/css 和js热点面试题 因为csdn不可以有外链 所以答案链接在文章底部!!! Vue面试题 生命周期函数面试题

html5网页设计制作基础大二dreamweaver作业、使用html+css技术制作博客网站(5个页面)_html5网页设计的博客-爱代码爱编程

🎉精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (1000套) 】 🧡 程

【vue.js】使用高德地图选择省市区后,再点击确认当前选择的位置_萌村村花杨小花的博客-爱代码爱编程

成品展示 前期准备 先去高德开放平台申请一个web端的key。 2022年后申请的key,必须和它生成的secret一起使用。 可使用服务选择web端 在vue项目中,可以通过直接引入js文件,也可以安装vu

iframe通信_camillezj的博客-爱代码爱编程

 跨域的种类 一般有两种形式的跨域问题:  ①使用XmlHttpRequest(XHR)或者使用AJAX发送的POST或者GET请求。这种形式的跨域是:前端页面与后端进行的跨域请求。 ②父子页面之间进行的DOM操作(父子窗口之间的document操作)。这种形式的跨域是:前端页面与前端页面之间的通信或者相互操作的形成跨域。(本文主要讲这种) ifr

itext7高级教程之html2pdf——5.自定义标签和css应用_xmlworker 和 html2pdf-爱代码爱编程

作者:CuteXiaoKe 微信公众号:CuteXiaoKe   在本章中,我们将更改pdfHTML插件的两个最重要的内部机制。 我们将覆盖将HTML标签与iText对象匹配的默认功能,更具体地说是Defaul