前言

这周练习了几个js的小练习,全是关于验证码的。其中图片验证码和数字验证码需要用到canvas。在这里想先写一点关于验证码知识。

  • 验证码的作用之一是减少服务器的压力。试想一下倘若很多个客户端需要将登陆或者注册信息同时发给服务器,服务器就会处于很繁忙的状态。我们可以使用验证码将这很多个用户同时发信息的状态变成很多个用户不同时刻发送信息的状态。这样服务器的压力便会减轻。因此验证码是很有必要使用的小效果。
  • 在写图片验证码的时候,需要注意:加载一张图片image就需要发送一次HTTP请求。加载多张就需要发送多次。验证码本就是为了减轻服务器的压力,但若果有了图片的HTTP这无疑起到了相反的作用。因此需要对图片的性能进行优化。方式有如下几种:
  • 减少HTTP请求。将几十张小图整合放在一张大图中。
  • Base64
  • 字体图标。将图标以字体的形式呈现。可以通过更改css样式来改变整张图片的一些样式比如背景色等。

三种验证码总结思路和代码如下:

一.简单验证码

1.效果如下:

java图形验证码扭曲 js图形验证码_ide


2.思路分析:

这样的一个验证码效果,有这些功能:

  • 滑块滑到一半鼠标松开滑块回到起始位置
  • 滑块滑到最后框中文字显示验证成功并且滑块图案改变成对勾。
  • 在从最初位置,鼠标按下,滑到最后验证成功的这一整个过程中,鼠标始终在滑块上,滑块随着鼠标移动,并且背景色绿色也随之增加。成功之后再移动或者松开鼠标,效果不发生改变。

那么要如何实现呢?

  • 需要一个表示滑块的div元素是相对定位,一个表示背景色的div元素是相对定位,一个表示框中文字的div元素。这三个div元素被一个父div元素包裹是绝对定位。
  • mousedown需要做的事是:在滑块元素中绑定鼠标按下事件mousedown。一旦触发这一事件先标记此时的e.clientX也就是鼠标按下的位置。
  • mousemove需要做的事是:
  • 在滑块元素中绑定鼠标移动事件mousemove,同样记录下此时鼠标位置e.clientX
  • 计算差值。这里要注意的是必须在鼠标按下之后触发的鼠标移动才会有效果。如果不先按下直接进行鼠标的移动这样是没有效果的。之所以要标记两次e.clientX的原因是要根据这两个之差moveX计算出背景色绿色的宽度。要知道,一次鼠标按下之后,鼠标移动是不停在触发的。那么这个差值也是在不断变的。如下图所示:
  • java图形验证码扭曲 js图形验证码_ide_02

  • 这个差值moveX既是滑块相对于父元素移动的像素值,同时也是背景色的宽度值。
  • 判断moveX。只有当moveX的取值大于0时,根据moveX导致背景色的宽度以及滑块的left值的变化。进一步判断倘若滑块滑到最后,取消鼠标按下和移动事件。并且框内文字改变滑块中的图案改变。倘若不在这一范围,鼠标移动事件不产生任何效果。如何判断滑块滑到最后,如下图所示:
  • java图形验证码扭曲 js图形验证码_验证码_03

  • mouseup需要做的事是:在滑块元素上绑定鼠标移除事件,首先我们需要知道的是,倘若滑块已经移到最后,那么鼠标移除事件是不起作用的。可以添加一个标志位,当验证成功时为true。判断标志位为false时,鼠标移除事件才会起作用。鼠标移除事件做的是使滑块能够移动至最初位置的效果。
    并且在移除事件的最后要进行鼠标移动事件的清除。执行这一步的原因是当第一次按下滑块移动至框中间再松开滑块移至最初位置后,若鼠标移动事件仍然存在,那么不需要再次按下便可以移动滑块。但我们需要实现的效果是移至最初位置后需要鼠标再次按下之后移动滑块生效。

3.源代码:
html:

<body>
    <div class="box">
       <div class="word">拖动滑块验证</div>
       <div class="square"></div>
       <div class="bg"></div>
    </div>
</body>

css:

*{
    margin:0;
    padding:0;
}
.box{
    position: relative;
   width: 400px;
   height:40px;
   margin-left:400px;
   margin-top:20px;
   background-color:#DCDCDC;
}
.word{
   width:100%;
   height:100%;
   line-height:40px;
   text-align:center;
   -webkit-user-select:none;
}
.square{
    position: absolute;
    top:0;
    left:0;
    width:40px;
    height:40px;
    box-sizing: border-box;
    background-image:url("滑块 (1).png");
    background-size:100% 100%;
    background-repeat:no-repeat;
    background-color:#fff;
    border:1px solid #dcdcdc;
}
.bg{
    position: absolute;
    top:0;
    left:0;
    height:40px;
    width:0px;
    background-color:green;
    opacity:0.4;
}

js:

var btn={
    word:document.getElementsByClassName('word')[0],
    square:document.getElementsByClassName('square')[0],
    bg:document.getElementsByClassName('bg')[0],
    box:document.getElementsByClassName('box')[0],
    flag:false,
    down:function(){
       this.square.onmousedown=(e)=>{
           const downX=e.clientX;
           this.square.onmousemove=(e)=>{
                const nowX=e.clientX;
                let moveX=nowX-downX;
               if(moveX>0){
                this.square.style.left=moveX+'px';
                this.bg.style.width=moveX+'px';
                if(moveX>=this.box.offsetWidth-this.square.offsetWidth){
                    this.square.onmousedown=null;
                    this.square.onmousemove=null;
                    this.word.innerText="验证成功";
                    this.word.style.color="#fff";
                    this.flag=true;
                    this.square.style.backgroundImage="url('验证成功 (1).png')";
                }
               }
           }
       }
    },
    up:function(){
         this.square.onmouseup=()=>{
             if(this.flag) return;
             this.square.style.left=0;
             this.bg.style.width=0;
             this.square.onmousemove=null;
         }
    }
}
btn.down();
btn.up();
二.滑块验证码

1.效果如下:

java图形验证码扭曲 js图形验证码_js_04


2.源代码:

html:

<body>
    <div class="wrapper">
        <div class="cap"></div>
    </div>
</body>

css:

*{
    margin:0;
    padding:0;
}
.wrapper{
    width:310px;
    margin:100px auto;
}
.cap{
    position: relative;
}
.block{
    position: absolute;
    top:0;
    left:0;
}
.sliderContainer{
  position: relative;
   width: 310px;
   height:40px;
   margin-top:15px;
   background-color:#DCDCDC;
}
.word{
   width:100%;
   height:100%;
   line-height:40px;
   text-align:center;
   -webkit-user-select:none;
}
.square{
    position: absolute;
    top:0;
    left:0;
    width:40px;
    height:40px;
    box-sizing: border-box;
    background-color:#fff;
    border:1px solid #dcdcdc;
    transition: background .2s linear;
}
.bg{
    position: absolute;
    top:0;
    left:0;
    box-sizing: border-box;
    height:40px;
    width:0px;
}
.bgActive{
    background-color:#D1E9FE;
    border:1px solid #1991FA;
}
.squareActive{
    border-width:0px;
}
.square:hover{
    border-width:0px;
    background-color:#1991FA;
}
.squareFail{
    background-color:#f57a7a!important;
}
.bgFail{
    border: 1px solid #f57a7a;
    background-color: #fce1e1;
}
.squareSuccess{
    background-color:#52CCBA!important;
}
.bgSuccess{
    border: 1px solid #52CCBA;
    background-color: #D2F4EF;
}
.refreshIcon{
    position: absolute;
    top:0;
    right:0;
    width:20px;
    height:20px;
    background-image:url("更新.png");
    background-size:100% 100%;
    background-repeat:no-repeat;
}

js:

function getNumberRange(start,end){
    return Math.floor(Math.random()*(end-start)+start);
}
function getImageUrl(){
    return 'https://picsum.photos/300/150/?image=' + getNumberRange(0, 100);
}
function createImage(loading){
    let img=document.createElement('img');
    //允许跨域
    img.crossOrigin="Anonymous";
    img.onload=loading;
    img.onerror=function(){
        img.src=getImageUrl();
    }
    img.src=getImageUrl();
    return img;
}
function addClass(tag,name){
    tag.classList.add(name);
}
var obj={
    l : 42, // 滑块边长
    r :10, // 滑块半径
    w :310, // canvas宽度
    h : 155, // canvas高度
    PI :Math.PI,
    init(el){
      this.cap=el;
      this. L =this.l + this.r * 2;//滑块实际边长
      this.y=getNumberRange(this.r*2-2,this.h-this.l);
      this.x=getNumberRange(this.L,this.w-this.L+2);
      this.initDOM();
      this.initImage();
      this.draw(); 
      this.move();
      this.refresh();
    },
    initDOM:function(){
       let canvas=document.createElement('canvas');
       canvas.setAttribute('class','ctx');
       canvas.width=this.w;
       canvas.height=this.h;
       let refreshIcon=document.createElement('div');
       refreshIcon.setAttribute('class','refreshIcon');
       let block=document.createElement('canvas');
       block.setAttribute('class','block');
       block.height=this.h;
       this.cap.appendChild(canvas);
       this.cap.appendChild(refreshIcon);
       this.cap.appendChild(block);
       let sliderContainer=document.createElement('div');
       sliderContainer.setAttribute('class','sliderContainer');
       let word=document.createElement('div');
       word.setAttribute('class','word');
       word.innerText="向右滑动滑块填充拼图"
       let square=document.createElement('square');
       square.setAttribute('class','square');
       let bg=document.createElement('bg');
       bg.setAttribute('class','bg');
       sliderContainer.appendChild(word);
       sliderContainer.appendChild(square);
       sliderContainer.appendChild(bg);
       this.cap.appendChild(sliderContainer);
       Object.assign(this,{
           canvasctx:canvas.getContext('2d'),
           refreshIcon,
           block,
           canvas,
           blockctx:block.getContext('2d'),
           sliderContainer,
           word,
           square,
           bg
       })
    },
    initImage:function(){
        let self=this;
        let img=createImage(function(){
            self.canvasctx.drawImage(img,0,0,self.w,self.h);
            self.blockctx.drawImage(img,0,0,self.w,self.h);
            const y=self.y-2*self.r+2;
            let imageData=self.blockctx.getImageData(self.x,y,self.L,self.L);
            self.block.width=self.L;
            self.blockctx.putImageData(imageData,0,y);
        });
    },
    draw(){
        let self=this;
        self.drawing(self.canvasctx,'fill',self.x,self.y);
        self.drawing(self.blockctx,'clip',self.x,self.y);
    },
    drawing(ctx,ope,x,y){
        ctx.beginPath();
        ctx.moveTo(x,y);
        ctx.lineTo(x+this.l/2,y);
        ctx.arc(x+this.l/2,y-this.r+2,this.r,0*this.PI,2*this.PI);
        ctx.lineTo(x+this.l/2,y);
        ctx.lineTo(x+this.l,y);
        ctx.lineTo(x+this.l,y+this.l/2);
        ctx.arc(x+this.l+this.r-2,y+this.l/2,this.r,0*this.PI,2*this.PI);
        ctx.lineTo(x+this.l,y+this.l/2);
        ctx.lineTo(x+this.l,y+this.l);
        ctx.lineTo(x,y+this.l);
        ctx.lineTo(x,y);
        ctx.fillStyle="#fff";
        ctx[ope]();
        ctx.beginPath();
        ctx.arc(x,y+this.l/2,this.r,1.5*this.PI,0.5*this.PI);
        ctx.globalCompositeOperation = "xor";
        ctx.fill();
    },
    clean:function(){
        this.blockctx.clearRect(0,0,this.w,this.h);
        this.canvasctx.clearRect(0,0,this.w,this.h);
        //!!!不加这句话不显示this.block画布,如果写这句话就会显示this.block画布
        this.block.width = this.w;
    },
    reset:function(){
        this.clean();
        this.square.style.left=0+'px';
        this.bg.style.width=0+'px';
        this.block.style.left=0+'px';
        this.square.className='square';
        this.bg.className='bg';
        this.word.innerText="向右滑动滑块填充拼图";
        this.y=getNumberRange(this.r*2-2,this.h-this.l);
        this.x=getNumberRange(this.L,this.w-this.L+2);
        this.initImage();
        this.draw();
        this.move();
    },
    refresh:function(){
       this.refreshIcon.onclick=()=>{
           this.reset();
       }
    },
    move:function(){
        let downX;
        this.square.onmousedown=(e)=>{
            downX=e.clientX;
            this.square.onmousemove=(e)=>{
                addClass(this.bg,'bgActive');
                addClass(this.square,'squareActive');
                this.word.innerText='';
                const nowX=e.clientX;
                let moveX=nowX-downX;
                if(moveX>0&&moveX<=this.sliderContainer.offsetWidth-this.square.offsetWidth){
                    this.square.style.left=moveX+'px';
                    this.bg.style.width=moveX+'px';
                    this.block.style.left=moveX+'px';
                }
        }
        this.square.onmouseup=(e)=>{
            const nowX=e.clientX;
            let moveX=nowX-downX;
            if(Math.abs(moveX-this.x)<=8){
                alert('验证成功');
                addClass(this.square,'squareSuccess');
                addClass(this.bg,'bgSuccess');
                this.square.onmousedown=null;
                this.square.onmousemove=null;
                this.word.style.color="#fff";
            }else{
                alert('验证失败');
                this.square.onmousemove=null;
                addClass(this.square,'squareFail');
                addClass(this.bg,'bgFail');
                setTimeout(()=>{
                    this.reset();
                },1500)
            }
       }
    }
}
}
obj.init(document.getElementsByClassName('cap')[0]);
三.图形验证码

1.效果如下:

java图形验证码扭曲 js图形验证码_java图形验证码扭曲_05

2.源代码:
html:

<body>
    <div class="picWrapper">
        <input type="text" class="inputText" value=''>
        <div id="pic"></div>
    </div>
</body>

css:

*{
            margin:0;
            padding:0;
        }
        .picWrapper{
            width:400px;
            margin:100px auto;
        }
        .inputText{
            box-sizing: border-box;
            width:200px;
            height:30px;
            font-size:22px;
            float:left;
            margin-right:10px;
        }

js:

function randomNum(min,max){
    return Math.floor(Math.random()*(max-min)+min);
}
function randomColor(min,max){
    var a=randomNum(min,max);
    var b=randomNum(min,max);
    var c=randomNum(min,max);
    return 'rgb('+a+','+b+','+c+')';
}
function numArr(){
    return "0,1,2,3,4,5,6,7,8,9".split(',');
}
function letterArr(){
    return "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z".split(',');
}
var obj={
    el: document.getElementsByClassName('inputText')[0],
    init:function(options){
       this.GVerify(options);
       this.initCanvas();
       this.refresh();
       this.fun();
    },
    //处理参数
    GVerify:function(options){
        this.options = { //默认options参数值
            id: "", //容器Id
            canvasId: "verifyCanvas", //canvas的ID
            width: "100", //默认canvas宽度
            height: "30", //默认canvas高度
            type: "blend", //图形验证码默认类型blend:数字字母混合类型、number:纯数字、letter:纯字母
            code: ""
        }
        //判断传入的参数
        if(Object.prototype.toString.call(options)=='[object Object]'){
            for(var i in options){
                this.options[i]=options[i];
            }
        }else{
            this.options.id=options;
        }
    },
    initCanvas:function(){
        var canvas=document.createElement('canvas');
        var parent=document.getElementById('pic');
        canvas.id=this.options.canvasId;
        canvas.width=this.options.width;
        canvas.height=this.options.height;
        canvas.style.cursor="pointer";
        canvas.onclick=()=>{
            this.refresh();
        }
        parent.appendChild(canvas);
    },
    refresh:function(){
        var self=this;
        self.options.code='';
        self.el.value='';
        var canvas=document.getElementById(self.options.canvasId);
        if(canvas.getContext){
            var ctx=canvas.getContext('2d');
            var ctxText;
            ctx.clearRect(0,0,self.options.width,self.options.height);
            //1.画图片
            ctx.fillStyle=randomColor(180,240);
            ctx.fillRect(0,0,self.options.width,self.options.height);
            ctx.textBaseline='middle';
            //2.画数字
            if(self.options.type=='blend'){
               ctxText=numArr().concat(letterArr());
            }else if(self.options.type=='number'){
               ctxText=numArr();
            }else{
               ctxText=letterArr();
            }
            for(var i=1;i<=4;++i){
               var num=ctxText[randomNum(0,ctxText.length-1)];
               var x=self.options.width/5*i;
               var y=self.options.height/2;
               var deg=randomNum(-30,30);
               ctx.font=randomNum(self.options.height/2,self.options.height)+'px SimHei';
               ctx.fillStyle = randomColor(50, 160); 
               ctx.shadowBlur=randomNum(-3,3);
               ctx.shadowColor='rgba(0,0,0,0.3)';
               ctx.translate(x,y);
               ctx.rotate(deg*Math.PI/180);
               ctx.fillText(num,0,0);
               self.options.code+=num;
               ctx.rotate(-deg*Math.PI/180);
               ctx.translate(-x,-y);
            }
            //3.画干扰线
            for(i=0;i<4;++i){
                var self=this;
                ctx.strokeStyle=randomColor(40,180);
                ctx.beginPath();
                ctx.moveTo(randomNum(0,self.options.width/2),randomNum(0,self.options.height/2));
                ctx.lineTo(randomNum(0,self.options.width/2),randomNum(0,self.options.height));
                ctx.stroke();
            }
            //4.画干扰点
            for(i=0;i<this.options.width/4;++i){
                var self=this;
                ctx.fillStyle=randomColor(0, 255);
                ctx.beginPath();
                ctx.arc(randomNum(0,self.options.width),randomNum(0,self.options.height),1,0,Math.PI*2);
                ctx.fill();
            }
        }
    },
    //判断输入的值与验证码的值是否一致
    judge:function(code){
        var code=code.toLowerCase();
        var options=this.options.code.toLowerCase();
        if(code==options){
            return true;
        }else{
            return false;
        }
    },
    //与input结合
    fun:function(){
        this.el.onmouseover=()=>{
            this.el.onmouseout=()=>{
                var code=this.el.value;
                if(code!=''){
                    var flag=this.judge(code);
                    if(!flag){
                        alert('验证码输入错误');
                        this.el.value='';
                        this.refresh();
                    }else{
                        alert('验证码输入正确');
                    }
                }
             }
        }
    }
}
obj.init("verifyCanvas");