JAVA后端生成图片滑块验证码 springboot+js完整案例

前言

现在大部分网部都是图片滑块验证码,这个得要与后端联动起来才是确保接口安全性
通过我们系统在发送手机短息时都会选进行滑块验证,但是我们要保证发送短息接口的全安,具体路思如下
在这里插入图片描述
那么这个滑块的必须是与后端交互才能保证安全性,而不是前端简单的交互。我们一起来学习一下这个案例怎么实吧
1、验证通过效果如图
在这里插入图片描述
2、验证失败效果如图
在这里插入图片描述

案例开始

1、我们使用java新建一个springboot工程,并准备几张图片,尺寸是390*180,如下图
添加依赖

<dependency>
	<groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.11</version>
</dependency>

在这里插入图片描述
2、新建三个响应的类
2.1 WebReturn类如下

@Data
public class WebReturn {

    RetCode code;
    Object data;

    public WebReturn(RetCode code, Object data) {
        this.code = code;
        this.data = data;
    }
}

2.2 RetCode类如下


public enum  RetCode {
    IMAGE_REQ_SUCCESS(1,"图片请求成功"),
    IMAGE_REQ_FAIL(2,"图片请求失败"),
    VERIFI_REQ_SUCCESS(3,"图片验证成功"),
    VERIFI_REQ_FAIL(4,"图片验证失败");

    int code;
    String message;

    RetCode(int code, String message) {
        this.code = code;
        this.message = message;
    }



    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

2.3 ImageResult类如下

@Data
public class ImageResult {

    int xpos;//滑块的坐标x轴
    int ypos;//滑块的坐标y轴
    int cutImageWidth;//滑块的宽
    int cutImageHeight;//滑块的高

    String  cutImage;//滑块图片
    String  oriImage;//背景图(初扣掉滑块的图)

}

3、新建一个图片滑块生成的工具类

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Slf4j
public class ImgUtil {

    //图片的路径
    private  String classpath = "classpath*:img/slider/*.*";
    //图片的最大大小  (可以根据实际需要进行高调整,对应的图片尺寸也得是一致)
    private  static int IMAGE_MAX_WIDTH = 380;
    private  static int IMAGE_MAX_HEIGHT = 190;
    //抠图上面的半径
    private   static int RADIUS = IMAGE_MAX_WIDTH/38;
    //抠图区域的高度
    private   static int CUT_HEIGHT = IMAGE_MAX_WIDTH/7;
    //抠图区域的宽度
    private   static int CUT_WIDTH = IMAGE_MAX_WIDTH/7;
    //被扣地方填充的颜色
    private   static int FLAG = 0xffffff;
    //抠图部分凸起的方向
    private  Location location;
    ImageResult imageResult = new ImageResult();
    private  String ORI_IMAGE_KEY = "ORI_IMAGE_KEY";
    private  String CUT_IMAGE_KEY = "CUT_IMAGE_KEY";

    private  int XPOS;
    private  int YPOS;

    @Data
    private  class ImageMessage{
        private int xpos;
        private int ypos;
        private int cutImageWidth;
        private int cutImageHeight;
    }

    ImageMessage imageMessage = new ImageMessage();


    /**
     *功能描述 获取抠图区的坐标原点
     */
    public  void createXYPos(BufferedImage oriImage){

        int height = oriImage.getHeight();
        int width = oriImage.getWidth();


        XPOS = new Random().nextInt(width-CUT_WIDTH-RADIUS);
        YPOS = new Random().nextInt(height-CUT_HEIGHT-RADIUS-RADIUS)+RADIUS;

        //确保横坐标位于2/4--3/4
        int div = (IMAGE_MAX_WIDTH/4);

        if(XPOS/div ==  0 ){
            XPOS = XPOS + div*2;
        }
        else if(XPOS/div ==  1 ){
            XPOS = XPOS + div;
        }
        else if(XPOS/div ==  3 ){
            XPOS = XPOS - div;
        }

    }
    /**
     *功能描述 对外提供的接口
     */
    public  ImageResult imageResult() throws IOException{
        return imageResult(getRandomImage());
    }

    public  ImageResult imageResult(BufferedImage oriBufferedImage) throws IOException {

        //检测图片大小
        oriBufferedImage = checkImage(oriBufferedImage);

        //初始化原点坐标
        createXYPos(oriBufferedImage);
        //获取被扣图像的标志图
        int[][] blockData = getBlockData(oriBufferedImage);
        //printBlockData(blockData);
        //计算抠图区域的信息
        createImageMessage();

        //获取扣了图的原图和被扣部分的图
        Map<String,BufferedImage> imageMap =  cutByTemplate(oriBufferedImage,blockData);

        imageResult.setOriImage(ImageBase64(imageMap.get(ORI_IMAGE_KEY)));
        imageResult.setCutImage(ImageBase64(imageMap.get(CUT_IMAGE_KEY)));

        imageResult.setXpos(imageMessage.getXpos());
        imageResult.setYpos(imageMessage.getYpos());
        imageResult.setCutImageWidth(imageMessage.getCutImageWidth());
        imageResult.setCutImageHeight(imageMessage.getCutImageHeight());

        return imageResult;
    }


    /**
     *功能描述
     * @Description 计算抠图的相关参数
     */
    private  void createImageMessage(){
        int x = 0,y = 0;
        int w = 0, h = 0;

        if(location == Location.UP){
            x = XPOS;
            y = YPOS - RADIUS;
            w = CUT_WIDTH;
            h = CUT_HEIGHT + RADIUS;
        }else if(location == Location.LEFT){
            x = XPOS-RADIUS;
            y = YPOS;
            w = CUT_WIDTH + RADIUS;
            h = CUT_HEIGHT;
        }else if(location == Location.DOWN){
            x = XPOS;
            y = YPOS;
            w = CUT_WIDTH;
            h = CUT_HEIGHT + RADIUS;
        }else if(location == Location.RIGHT){
            x = XPOS;
            y = YPOS;
            w = CUT_WIDTH + RADIUS;
            h = CUT_HEIGHT;
        }

        imageMessage.setXpos(x);
        imageMessage.setYpos(y);
        imageMessage.setCutImageHeight(h);
        imageMessage.setCutImageWidth(w);
    }
    /**
     *功能描述
     * @Description  检测图片大小是否符合要求
     */
    private  BufferedImage checkImage(BufferedImage image) throws IOException {
        if((image.getWidth()  == IMAGE_MAX_WIDTH) || (image.getHeight()  == IMAGE_MAX_HEIGHT)){
            return image;
        }else if((image.getWidth()  < IMAGE_MAX_WIDTH) || (image.getHeight()  < IMAGE_MAX_HEIGHT)){
            log.info("图片太小.不符合要求w*h[380*190]");
            throw  new IllegalArgumentException("图片太小.不符合要求w*h[380*190]");
        } else {
            log.info("压缩图片");
            return compressImage(image,IMAGE_MAX_WIDTH,IMAGE_MAX_HEIGHT);
        }
    }


    private Color color(int rgb){
        int b = (0xff & rgb);
        int g = (0xff & (rgb >> 8));
        int r = (0xff & (rgb >> 16));
        return new Color(r, g, b);
    }


    /**
     *功能描述 获取抠完图的原图和被扣出来的图
     */
    public  Map<String,BufferedImage> cutByTemplate(BufferedImage oriImage,  int[][] blockData){
        Map<String,BufferedImage> imgMap = new HashMap<>();
        BufferedImage cutImage = new BufferedImage(imageMessage.cutImageWidth,imageMessage.cutImageHeight,oriImage.getType());
        // 获取Graphics2D
        Graphics2D g2d = cutImage.createGraphics();

        //透明化整张图
        cutImage = g2d.getDeviceConfiguration()
                .createCompatibleImage(imageMessage.cutImageWidth,imageMessage.cutImageHeight, Transparency.BITMASK);
        g2d.dispose();
        g2d = cutImage.createGraphics();
        // 背景透明代码结束
        int xmax = imageMessage.xpos + imageMessage.cutImageWidth;
        int ymax = imageMessage.ypos + imageMessage.cutImageHeight;

        for(int x = imageMessage.xpos; x< xmax && x>=0; x++){
            for(int y = imageMessage.ypos; y < ymax && y>=0; y++){
                int oriRgb = oriImage.getRGB(x,y);
                if(blockData[x][y] == FLAG){
                    oriImage.setRGB(x,y,FLAG);
                    //描边   判断是否为边界,如果是边界则填充为白色
                    if(blockData[x-1][y] != FLAG || blockData[x+1][y] != FLAG || blockData[x][y+1] != FLAG || blockData[x][y-1] != FLAG){
                        g2d.setColor(color(0xffffff));
                    }else{
                        g2d.setColor(color(oriRgb));
                    }
                    g2d.setStroke(new BasicStroke(1f));
                    g2d.fillRect(x-imageMessage.xpos, y-imageMessage.ypos, 1, 1);
                }
            }
        }

        // 释放对象
        g2d.dispose();
        imgMap.put(ORI_IMAGE_KEY,oriImage);
        imgMap.put(CUT_IMAGE_KEY,cutImage);
        return imgMap;
    }
    /**
     *功能描述
     * @Description 获取抠图数据,被扣的像素点将使用FLAG进行标记
     * @return:  int[][]
     */
    public  int[][] getBlockData(BufferedImage oriImage){

        int height = oriImage.getHeight();
        int width = oriImage.getWidth();
        int[][] blockData =new int[width][height];

        Location locations[] = {Location.UP,Location.LEFT,Location.DOWN,Location.RIGHT};

        //矩形
        //此处还可以优化,进行区域扫描
        for(int x = 0; x< width && x>=0; x++){
            for(int y = 0; y < height && y>=0; y++){
                blockData[x][y] = 0;
                if ( (x > XPOS) && (x < (XPOS+CUT_WIDTH))
                        && (y > YPOS) && (y < (YPOS+CUT_HEIGHT))){
                    blockData[x][y] = FLAG;
                }
            }
        }

        //圆形突出区域
        //突出圆形的原点坐标(x,y)
        int xBulgeCenter=0,yBulgeCenter=0;
        int xConcaveCenter=0,yConcaveCenter=0;

        //位于矩形的哪一边,0123--上下左右
        location = locations[new Random().nextInt(4)];
        if(location == Location.UP){
            //上 凸起
            xBulgeCenter = XPOS +  CUT_WIDTH/2;
            yBulgeCenter = YPOS;
            //左 凹陷
            xConcaveCenter = XPOS ;
            yConcaveCenter = YPOS + CUT_HEIGHT/2;
        }else if(location == Location.DOWN){
            //下 凸起
            xBulgeCenter = XPOS +  CUT_WIDTH/2;
            yBulgeCenter = YPOS + CUT_HEIGHT;
            //右 凹陷
            xConcaveCenter = XPOS +  CUT_WIDTH;
            yConcaveCenter = YPOS + CUT_HEIGHT/2;
        }else if(location == Location.LEFT){
            //左 凸起
            xBulgeCenter = XPOS ;
            yBulgeCenter = YPOS + CUT_HEIGHT/2;
            //下 凹陷
            xConcaveCenter = XPOS +  CUT_WIDTH/2;
            yConcaveCenter = YPOS + CUT_HEIGHT;
        }else {
            //Location.RIGHT
            //右 凸起
            xBulgeCenter = XPOS +  CUT_WIDTH;
            yBulgeCenter = YPOS + CUT_HEIGHT/2;
            //上 凹陷
            xConcaveCenter = XPOS +  CUT_WIDTH/2;
            yConcaveCenter = YPOS;

        }

        //半径的平方
        int RADIUS_POW2 = RADIUS * RADIUS;

        //凸起部分
        for(int x = xBulgeCenter-RADIUS; x< xBulgeCenter+RADIUS && x>=0; x++){
            for(int y = yBulgeCenter-RADIUS; y < yBulgeCenter+RADIUS && y>=0; y++){
                if(Math.pow((x-xBulgeCenter),2) + Math.pow((y-yBulgeCenter),2) < RADIUS_POW2){
                    blockData[x][y] = FLAG;
                }
            }
        }

        //凹陷部分
        for(int x = xConcaveCenter-RADIUS; x< xConcaveCenter+RADIUS && x>=0; x++){
            for(int y = yConcaveCenter-RADIUS; y < yConcaveCenter+RADIUS && y>=0; y++){
                if(Math.pow((x-xConcaveCenter),2) + Math.pow((y-yConcaveCenter),2) <= RADIUS_POW2){
                    blockData[x][y] = 0;
                }
            }
        }
        return blockData;
    }




    /**
     *功能描述 将图片转为base64存储
     */
    private  String ImageBase64(BufferedImage bufferedImage) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, "png", out);
        //转成byte数组
        byte[] bytes = out.toByteArray();
        Base64.Encoder encoder = Base64.getEncoder();
        //生成BASE64编码
        return encoder.encodeToString(bytes);
    }

    /**
     * 随机获取一个图片文件
     * @return
     * @throws Exception
     */
    private  BufferedImage getRandomImage() throws IOException {
        try {
            //使用resource获取resource文件【注意:即使打成jar包也有效】
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resourcePatternResolver.getResources(classpath);

            if (resources.length <= 0) {
                throw new IOException("该文件夹内没有文件!");
            } else {
                int index = new Random().nextInt(resources.length);
                InputStream inputStream = resources[index].getInputStream();
                BufferedImage marioBufferedImage = ImageIO.read(inputStream);
                return marioBufferedImage;
            }
        } catch (IOException e) {
            log.info("读取文件失败:{}", e);
            throw new IOException("读取文件失败!");
        }
    }

    /**
     *功能描述  压缩图片
     * @author lgj
     * @Description
     * @date 3/30/20
     * @param:
     * @return:  java.awt.image.BufferedImage
     *
     */
    private  BufferedImage compressImage(BufferedImage image,int width,int height) throws IOException{
        return  Thumbnails.of(image)
                .forceSize(width,height)
                //.width(width).height(height)
                .asBufferedImage();
    }
    /**
     *功能描述
     * @Description  抠图部分凸起的区域
     */
    private enum Location {
        UP,
        LEFT,
        DOWN,
        RIGHT;
    }
}

4、新建一个controller


@Slf4j
@RestController
@RequestMapping("/slider")
public class SliderController {

    private  int xPosCache = 0;//生产环境请把这个值存入redis中

    @RequestMapping("/image")
    public WebReturn image(){
        ImageResult imageResult = null;
        try{
            imageResult = new ImgUtil().imageResult();//生成图片
            xPosCache = imageResult.getXpos();//生产环境请把这个值存入redis中
            imageResult.setXpos(0);//清空x值
            return new WebReturn(RetCode.IMAGE_REQ_SUCCESS,imageResult);
        }catch(Exception ex){
            log.error(ex.getMessage());
            ex.printStackTrace();
            return new WebReturn(RetCode.IMAGE_REQ_FAIL,null);
        }
    }

    @RequestMapping("/verification")
    public WebReturn verification(@RequestParam("moveX") int moveX){
        log.info("/slider/verification/{}",moveX);
        int MOVE_CHECK_ERROR = 2;//允许的误差范围,这里设置为2个像素
        //xPosCache   生产请从redis中读取,使用完并立即请除
        if(( moveX < ( xPosCache + MOVE_CHECK_ERROR)) && ( moveX >  (xPosCache - MOVE_CHECK_ERROR))){
            log.info("验证正确");
            //生产这个可以返回临时授权码
            return new WebReturn(RetCode.VERIFI_REQ_SUCCESS,true);
        }
        return new WebReturn(RetCode.VERIFI_REQ_FAIL,false);
    }
}

5、最后新建一个页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>

    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

    <style >
        html body {
            height: 100%;
            width: 100%;
        }

        #captchaContainer{
            position: absolute;
            top: 50px;
            left: 40%;
          /*  background-color: #f57a7a;*/
            height: 275px;
            width: 260px;
        }
        .header{

            position: absolute;
            top: 0px;
            left: 0px;
            background-color: rgb(245, 236, 236);
            height: 40px;
            width: 380px;
        }
        .headerText{
            position: absolute;
            top: 13px;
            left: 140px;
            height: 40px;

            color:#66c523;
            font:18px/14px Georgia, "Times New Roman", Times, serif;


        }

        #captchaImg{
            position: absolute;
            left: 0;
            top: 40px;
            height: 190px;
            width: 380px;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            border: none


        }
        #oriImg{
            position: absolute;
            left: 0px;
            top: 0px;
            width: 380px;
            height: 190px;

            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            border: none

        }
        #cutImg{
            position: absolute;
            border: none;
            left: 0px;
        }
        .sliderContainer {
            position: absolute;
            bottom: 0;
            left: 0px;
            text-align: center;
            width: 380px;
            height: 40px;
            line-height: 40px;
            background: #f7f9fa;
            color: #45494c;
            border: 1px solid #e4e7eb;

        }

        .sliderContainer_success{
            border: 1px solid hsl(125, 93%, 44%);
        }
        .sliderContainer_fail{
            border: 1px solid #ec3655;
        }

        .slider {
            position: absolute;
            top: 0;
            left: 0px;
            width: 40px;
            height: 40px;
            background: #fff;
            box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
            transition: background .2s linear;
            cursor: pointer;
            cursor: grab;
        }
        .slider_success{
            border: 1px solid hsl(125, 93%, 44%);
        }
        .slider_fail{
            border: 1px solid #ec3655;
        }



        .sliderContainer_active .slider {
            height: 38px;
            top: -1px;
            border: 1px solid #1991FA;
        }

        .sliderContainer_active .sliderMask {
            height: 38px;
            border-width: 1px;
        }

        .sliderContainer_success .slider {
            height: 38px;
            top: -1px;
            margin-left: -1px;
            border: 1px solid #52CCBA;
            background-color: #52CCBA !important;
        }

        .sliderContainer_success .sliderMask {
            height: 38px;
            border: 1px solid #52CCBA;
            background-color: #D2F4EF;
        }

        .sliderContainer_success .sliderIcon {
            background-position: 0 0 !important;
        }

        .sliderContainer_fail .slider {
            height: 38px;
            top: -1px;
            border: 1px solid #f57a7a;
            background-color: #f57a7a !important;
        }

        .sliderContainer_fail .sliderMask {
            height: 38px;
            border: 1px solid #f57a7a;
            background-color: #fce1e1;
        }

        .sliderContainer_fail .sliderIcon {
            top: 14px;
            background-position: 0 -82px !important;
        }
        .sliderContainer_active .sliderText, .sliderContainer_success .sliderText, .sliderContainer_fail .sliderText {
            display: none;
        }

        .sliderMask {
            position: absolute;
            left: 0;
            top: 0;
            height: 40px;
            border: 0 solid #1991FA;
            background: #D1E9FE;
        }



        .slider:active {
            cursor: grabbing;
        }

        .slider:hover {
            background: #1991FA;
        }

        .slider:hover .sliderIcon {
            background-position: 0 -13px;
        }

        .sliderIcon {
            position: absolute;
            top: 15px;
            left: 13px;
            width: 14px;
            height: 12px;
            background: url(http://cstaticdun.126.net//2.6.3/images/icon_light.f13cff3.png) 0 -26px;
            background-size: 34px 471px;
        }

        .refreshIcon {
            position: absolute;
            right: 0;
            top: 0;
            width: 34px;
            height: 34px;
            cursor: pointer;
            background: url(http://cstaticdun.126.net//2.6.3/images/icon_light.f13cff3.png) 0 -437px;
            background-size: 34px 471px;
        }



    </style>

</head>
<body>
    
    <div id="captchaContainer">
        <!-- 标题栏 -->
        <div class="header">
            <span class="headerText">图片滑动验证</span>
            <span class="refreshIcon"/>
        </div> 

        <!-- 图片显示区域 -->
        <div id="captchaImg">
            <img id="oriImg" src="dd" alt="原图"/>
            <img id="cutImg" src="sa" alt="抠图"/>
        </div>
        <!--滑块显示区域-->
         <div class="sliderContainer">
            <div class="sliderMask">
                <div class="slider">
                    <span class="sliderIcon"></span>
                </div>
            </div>
            <span class="sliderText">向右滑动填充拼图</span>
            
        </div> 

        
    </div>


    
</body>

<script>

    //图片显示使用base64时的前缀,src=base64PrefixPath + imgBase64Value
    var base64PrefixPath="data:image/png;base64,";

    var IMAGE_WIDTH = 380;
    //初始化
    //滑块初始偏移量
    var sliderInitOffset = 0;
    //滑块移动的最值
    var MIN_MOVE = 0;
    var MAX_MOVE = 0;
    //鼠标按下标志
    var mousedownFlag=false;
    //滑块移动的距离
    var moveX;
    //滑块位置检测允许的误差,正负2
    var MOVE_CHECK_ERROR = 2;
    //滑块滑动使能
    var moveEnable = true;

    var ImageMsg = {
            //抠图的坐标
            xpos: 0,
            ypos: 0,
            //抠图的大小
            cutImageWidth: 0,
            cutImageHeight: 0,
            //原图的base64
            oriImageSrc: 0,
            //抠图的base64
            cutImageSrc: 0,
    }



   //加载页面时进行初始化
    function init(){
        console.log("init")

        moveEnable = true;
        mousedownFlag=false;

        $(".slider").css("left",0+"px");

        initClass();

        MAX_MOVE = IMAGE_WIDTH - ImageMsg.cutImageWidth;

        console.log("ImageMsg = " + ImageMsg)
        $("#cutImg").css("left",0+"px");
        $("#oriImg").attr("src",ImageMsg.oriImageSrc)
        $("#cutImg").attr("src",ImageMsg.cutImageSrc)
        $("#cutImg").css("width",ImageMsg.cutImageWidth)
        $("#cutImg").css("height",ImageMsg.cutImageHeight)
        $("#cutImg").css("top",ImageMsg.ypos)




    }
    //加载页面时
    $(function(){

        httpRequest.requestImage.request();
    })

    var httpRequest={
      //请求获取图片
      requestImage:{
        path: "slider/image",
        request:function(){
            $.get(httpRequest.requestImage.path,function(data,status){

                console.log(data)
                console.log(data.message);

                if(data.data != null){

                    ImageMsg.oriImageSrc = base64PrefixPath + data.data.oriImage;
                    ImageMsg.cutImageSrc = base64PrefixPath + data.data.cutImage;
                    ImageMsg.xpos = data.data.xpos;
                    ImageMsg.ypos = data.data.ypos;
                    ImageMsg.cutImageWidth = data.data.cutImageWidth;
                    ImageMsg.cutImageHeight = data.data.cutImageHeight;

                    init();
                }

          });
        },
      },
      //请求验证
      requestVerification:{
        path: "slider/verification",
        request:function(){
            $.get(httpRequest.requestVerification.path,{moveX:(moveX)},function(data,status){
                console.log(data)
                console.log(data.code);
                console.log(data.message);

                if(data.data == true){
                    checkSuccessHandle();
                }
                else{
                    checkFailHandle();
                }
          });
        },
      },
    }

    //刷新图片操作
    $(".refreshIcon").on("click",function(){
        httpRequest.requestImage.request();
    })

    //滑块鼠标按下
    $(".slider").mousedown(function(event){
        console.log("鼠标按下mousedown:"+event.clientX + " " + event.clientY);
        sliderInitOffset = event.clientX;  
        mousedownFlag = true;

        

        //滑块绑定鼠标滑动事件
        $(".slider").on("mousemove",function(event){
            if(mousedownFlag  == false){
              return;
            }
            if(moveEnable == false){
              return
            }

            moveX = event.clientX - sliderInitOffset;

            moveX<MIN_MOVE?moveX=MIN_MOVE:moveX=moveX;
            moveX>MAX_MOVE?moveX=MAX_MOVE:moveX=moveX;


            $(this).css("left",moveX+"px");
            $("#cutImg").css("left",moveX+"px");
        })
    })
    //滑块鼠标弹起操作
    $(".slider").mouseup(function(event){
        console.log("mouseup:"+event.clientX + " " + event.clientY);
        sliderInitOffset = 0;
        $(this).off("mousemove");
        mousedownFlag=false;
        console.log("moveX = " + moveX)
        checkLocation();
        
    })
    //检测滑块 位置是否正确
    function checkLocation(){

        moveEnable = false;

        //后端请求检测滑块位置
        httpRequest.requestVerification.request();
    }

    function checkSuccessHandle(){
      $(".sliderContainer").addClass("sliderContainer_success");
      $(".slider").addClass("slider_success");
    }
    function checkFailHandle(){
      $(".sliderContainer").addClass("sliderContainer_fail");
      $(".slider").addClass("slider_success");
    }

    function initClass(){
      $(".sliderContainer").removeClass("sliderContainer_success");
      $(".slider").removeClass("slider_success");
  
      $(".sliderContainer").removeClass("sliderContainer_fail");
      $(".slider").removeClass("slider_fail");
    }
    


</script>


</html>

最后

到这里我们的案例代码已经写好了,运程工程访问就可以了。
http://localhost:9005/home.html
在这里插入图片描述
本文章提供大家学习,欢迎你大家留言提供您的保贵意见
=如果本文对您有帮助,麻烦您给博主点个赞吧!=

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/914432.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第556题下一个更大元素III

题目&#xff1a; 题解&#xff1a; func nextGreaterElement(n int) int {x, cnt : n, 1for ; x > 10 && x/10%10 > x%10; x / 10 {cnt}x / 10if x 0 {return -1}targetDigit : x % 10x2, cnt2 : n, 0for ; x2%10 < targetDigit; x2 / 10 {cnt2}x x2%10 -…

ceph介绍和搭建

1 为什么要使用ceph存储 什么是对象存储&#xff1f; 对象存储并没有向文件系统那样划分为元数据区域和数据区域&#xff0c;而是按照不同的对象进行存储&#xff0c;而且每个对象内部维护着元数据和数据区域。因此每个对象都有自己独立的管理格式。 对象存储优点&#xff1a…

MFC图形函数学习07——画扇形函数

绘制扇形函数是MFC中绘图的基本函数&#xff0c;它绘制的仍是由椭圆弧与椭圆中心连线构成的椭圆扇形&#xff0c;特例是由圆弧与圆心连线构成的圆扇形。 一、绘制扇形函数 原型&#xff1a;BOOL Pie(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4); …

macOS15.1及以上系统bug:开发者证书无法打开,钥匙串访问无法打开一直出现图标后立马闪退

团队紧跟苹果最新系统发现bug:今日设备信息如下,希望能带给遇到这个问题的开发者一点帮助。 错误图如下: 点击证书文件后,先出现钥匙串访问图标,后立马闪退消失 中间试过很多方法,都是一样的表现,最后好在解决了,看网上也没有相关的帖子,这里直接写解决办法和导致原因…

Kotlin约束泛型参数必须继承自某个父类

Kotlin约束泛型参数必须继承自某个父类 open class SuperData { }class DataA : SuperData {constructor() {println("DataA constructor")} }class DataB : SuperData {constructor() {println("DataB constructor")} }fun <T : SuperData> myfun(p…

量子计算及其在密码学中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 量子计算及其在密码学中的应用 量子计算及其在密码学中的应用 量子计算及其在密码学中的应用 引言 量子计算概述 定义与原理 发展…

【大数据学习 | HBASE高级】storeFile文件的合并

Compaction 操作分成下面两种&#xff1a; Minor Compaction&#xff1a;是选取一些小的、相邻的StoreFile将他们合并成一个更大的StoreFile&#xff0c;对于删除、过期、多余版本的数据不进行清除。 Major Compaction&#xff1a;是指将所有的StoreFile合并成一个StoreFile&am…

任务中心全新升级,新增分享接口文档功能,MeterSphere开源持续测试工具v3.4版本发布

2024年11月5日&#xff0c;MeterSphere开源持续测试工具正式发布v3.4版本。 在这一版本中&#xff0c;系统设置方面&#xff0c;任务中心支持实时查看系统即时任务与系统后台任务&#xff1b;接口测试方面&#xff0c;新增接口文档分享功能、接口场景导入导出功能&#xff0c;…

RNN(循环神经网络)详解

1️⃣ RNN介绍 前馈神经网络&#xff08;CNN&#xff0c;全连接网络&#xff09;的流程是前向传播、反向传播和参数更新&#xff0c;存在以下不足&#xff1a; 无法处理时序数据&#xff1a;时序数据长度一般不固定&#xff0c;而前馈神经网络要求输入和输出的维度是固定的&a…

github加速下载zip

加速下载 1. 第一个网站 https://github.moeyy.xyz/把需要下载的链接复制进去&#xff0c;点下载https://github.com/dotnet/sdk/archive/refs/tags/v8.0.400.zip2. 第二个网站 https://gh-proxy.com/ 加速访问 2、查询github的ip地址 打开此网址&#xff1a;[IP 查询](http…

微服务电商平台课程三:搭建后台服务

前言 上节课,我们一起完成基础环境搭建,这节课, 我们利用上节课搭建我们电商平台.这节课我们采用开源代码进行搭建, 不论大家后续从事什么行业,都要学会站在巨人的肩膀上. 之前所说的,整个微服务平台的技术栈也是非常多的, 由于时间和效果的关系, 我们不可能从每个技术一步一…

模拟 [leecode 54] 螺旋矩阵

一、题解&#xff1a;上下左右四条线不断收紧 int l 0, r matrix[0].size() - 1;int high 0, low matrix.size() - 1;从左到右&#xff0c;顶部一层遍历完往下移一位&#xff0c;high&#xff1b;if(high>low) break;//遍历完了从上到下&#xff0c;遍历完右侧往左移一位…

丹摩征文活动|Llama3.1的部署与使用指南

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 丹摩征文 1. 初识Llama3.12. 部署流程创建实例登录实例部署LLama3.1 3. 实践使用教程4. 实践感想 前言&#xff1a;人工智能&#xff08;AI&…

人际交往中,想要有好人缘,需做到“三要”,做到一个,也是好事

人际交往中&#xff0c;想要有好人缘&#xff0c;需做到“三要”&#xff0c;做到一个&#xff0c;也是好事 在这个世上&#xff0c;每个人都是一座孤岛&#xff0c;但通过人际交往这座桥梁&#xff0c;我们能够彼此相连&#xff0c;共同编织出一张温暖的社会网络。 好人缘&a…

Sql server 备份还原方法

备份 方法1&#xff0c;选择对应的数据库名-------》右键 任务---------》备份 默认备份类型 完整 文件后缀 .bak 方法2,选择对应的数据库名-------》右键 任务----------》生成脚本 选择要编写的数据库对象(表&#xff0c;视图&#xff0c;存储过程等) 选择对应的 服…

NVR设备ONVIF接入平台EasyCVR私有化部署视频平台如何安装欧拉OpenEuler 20.3 MySQL

在当今数字化时代&#xff0c;安防视频监控系统已成为保障公共安全和个人财产安全的重要工具。NVR设备ONVIF接入平台EasyCVR作为一款功能强大的智能视频监控管理平台&#xff0c;它不仅提供了视频远程监控、录像、存储与回放等基础功能&#xff0c;还涵盖了视频转码、视频快照、…

【MySQL】数据库知识突破:数据类型全解析与详解

前言&#xff1a;本节内容讲述MySQL的数据类型&#xff0c; 我们在学习之前的建表的时候已经用过各种各样的数据类型。 比如int、varchar、char类型等等。其中它们是对表的结构的操作&#xff0c; 并没有对数据的内容进行操作&#xff0c;所以它叫做DDL。另外&#xff0c;还有…

管理 Elasticsearch 变得更容易了,非常容易!

作者&#xff1a;来自 Elastic Ken Exner Elasticsearch 用户&#xff0c;我们听到了你的心声。管理 Elasticsearch 有时会变得很复杂&#xff0c;面临的挑战包括性能调整、问题检测和资源优化。我们一直致力于简化你的体验。今天&#xff0c;我们宣布了自收购 Opster 以来的一…

Android Parcelable和Serializable的区别与联系

在Android开发中&#xff0c;Parcelable和Serializable是两种用来在组件之间传递数据的序列化机制。它们有不同的使用场景和性能特点。 以下是它们之间的关系和区别&#xff1a; 1. 什么是 Parcelable Parcelable 是 Android 特有的接口&#xff0c;用于高效地在进程间传递数…

Moonshine - 新型开源ASR(语音识别)模型,体积小,速度快,比OpenAI Whisper快五倍 本地一键整合包下载

Moonshine 是由 Useful Sensors 公司推出的一系列「语音到文本&#xff08;speech-to-text, STT&#xff09;转换模型」&#xff0c;旨在为资源受限设备提供快速而准确的「自动语音识别&#xff08;ASR&#xff09;服务」。Moonshine 的设计特别适合于需要即时响应的应用场景&a…