jq+canvas:实现图片上传+裁剪+保存等功能

效果图

上传图片之前:
在这里插入图片描述
上传图片之后,点击放大/缩小后的效果:
在这里插入图片描述
裁剪之后的效果:
在这里插入图片描述

代码实现如下:

1.html部分

<input type="file" id="fileInput" accept="image/png, image/gif, image/jpeg"/>
<br />
<div id="controls" style="display: none">
 <button id="zoomIn">放大</button>
 <button id="zoomOut">缩小</button>
 <br />
 <button id="crop">裁剪</button>
 <button id="save">保存</button>
 <br />
 裁剪框大小:
 <input
   type="number"
   id="cropWidth"
   placeholder="宽度"
   value="200"
   onchange="updateCropBoxSize()"
 />
 x
 <input
   type="number"
   id="cropHeight"
   placeholder="高度"
   value="200"
   onchange="updateCropBoxSize()"
 />
</div>
<div id="canvasContainer">
 <canvas id="canvas"></canvas>
 <div id="cropBox"></div>
</div>
<div id="cropBoxInfo"></div>

2.script部分代码

<script src="./script/jquery-1.8.3.js" type="text/javascript"></script>
<script>
  // 创建一个img对象
  let img = new Image();
  // 获取canvas元素
  let canvas = document.getElementById('canvas');
  // 获取2d绘图上下文
  let ctx = canvas.getContext('2d');
  // 缩放比例
  let scale = 1;
  // 获取裁剪框元素
  let cropBox = document.getElementById('cropBox');
  // 裁剪框的偏移量
  let cropOffsetX, cropOffsetY;

  // 当文件输入框选择文件时执行
  document
    .getElementById('fileInput')
    .addEventListener('change', function (event) {
      // 获取选择的文件
      const file = event.target.files[0];
      const fileInput = document.getElementById('fileInput');
      const filePath = fileInput.value;
      const allowedExtensions = /(\.png|\.gif|\.jpe?g)$/i; // 正则表达式匹配指定扩展名
      if (!allowedExtensions.exec(filePath)) {
        alert('请选择 PNG、GIF 或 JPE 格式的图片文件!');
        fileInput.value = ''; // 清空文件选择器中的值,防止非法文件的上传
        return false;
      }
      // 验证文件类型为图像类型
      if (file.type && !file.type.startsWith('image/')) {
        alert('请选择图片文件!');
        return false;
      }

      // 创建一个文件读取器
      const reader = new FileReader();

      // 当读取完成后执行
      reader.onload = function (event) {
        // 当图片加载完成后执行
        img.onload = function () {
          // 显示控制器
          document.getElementById('controls').style.display = 'block';
          // 重新绘制
          redraw();
          // 显示裁剪框
          cropBox.style.display = 'block';
          // 调整裁剪框大小
          updateCropBoxSize();
          // 中心裁剪框
          centerCropBox();
        };

        // 设置图片源
        img.src = event.target.result;
      };

      // 读取文件为数据URL
      reader.readAsDataURL(file);
    });

  // 点击缩放增加按钮时执行
  document.getElementById('zoomIn').addEventListener('click', function () {
    // 如果缩放比例小于2,则增加0.1
    if (scale < 2) {
      scale = parseFloat((scale + 0.05).toFixed(2));
      // 重新绘制
      redraw();
      // 调整裁剪框位置
      adjustCropBoxPosition();
    }
  });

  // 点击缩放减少按钮时执行
  document.getElementById('zoomOut').addEventListener('click', function () {
    // 如果缩放比例大于0.2,则减少0.1
    if (scale > 0.1) {
      scale = parseFloat((scale - 0.05).toFixed(2));
      // 重新绘制
      redraw();
      // 调整裁剪框位置
      adjustCropBoxPosition();
    }
  });

  // 点击裁剪按钮时执行
  document.getElementById('crop').addEventListener('click', function () {
    // 获取裁剪框的宽度和高度
    const cropWidth = parseInt(document.getElementById('cropWidth').value);
    const cropHeight = parseInt(
      document.getElementById('cropHeight').value
    );

    // 计算裁剪框在图片上的位置和尺寸,根据缩放比例调整
    const cropX = parseInt(cropBox.style.left);
    const cropY = parseInt(cropBox.style.top);

    // 绘制图片(img是已加载的图片)
    ctx.drawImage(img, 0, 0);

    // 创建一个临时画布
    const tempCanvas = document.createElement('canvas');
    const tempCtx = tempCanvas.getContext('2d');

    // 在临时画布上进行裁剪
    tempCanvas.width = cropWidth;
    tempCanvas.height = cropHeight;
    tempCtx.drawImage(
      img,
      cropX * (1 / scale),
      cropY * (1 / scale),
      cropWidth * (1 / scale),
      cropHeight * (1 / scale),
      0,
      0,
      cropWidth,
      cropHeight
    );

    // 清空主画布,设置为缩小后的尺寸
    canvas.width = cropWidth;
    canvas.height = cropHeight;

    // 缩放并绘制到主画布
    ctx.drawImage(
      tempCanvas,
      0,
      0,
      cropWidth,
      cropHeight,
      0,
      0,
      cropWidth,
      cropHeight
    );

    // 中心裁剪框
    centerCropBox();

    // 获取裁剪后的图像数据(Base64 格式)
    const croppedImageData = canvas.toDataURL('image/png'); // 可根据需要修改格式

    // 将 Base64 数据转换为 Blob 对象
    const base64Data = croppedImageData.split(',')[1]; // 去除前缀信息
    const byteArray = atob(base64Data);
    const byteNumbers = new Array(byteArray.length);
    for (let i = 0; i < byteArray.length; i++) {
      byteNumbers[i] = byteArray.charCodeAt(i);
    }
    const file = new Blob([new Uint8Array(byteNumbers)], {
      type: 'image/png',
    }); // 根据图像类型修改 type

    // // 创建一个 FormData 对象并添加文件
    // const formData = new FormData();
    // formData.append('image', file, 'cropped_image.png'); // 这里的 'image' 是表单字段的名称,'cropped_image.png' 是文件名
    //
    // // 创建一个 XHR 对象并发送 FormData
    // const xhr = new XMLHttpRequest();
    // xhr.open('POST', 'your_upload_url', true);
    // xhr.onload = function() {
    //     // 处理上传完成后的逻辑
    // };
    // xhr.send(formData);
  });

  // 点击保存按钮时执行
  document.getElementById('save').addEventListener('click', function () {
    // 将canvas转为数据URL
    const croppedImage = canvas.toDataURL('image/png', 1.0);

    // 创建下载链接
    const downloadLink = document.createElement('a');
    downloadLink.setAttribute('download', 'cropped_image.png');
    downloadLink.setAttribute('href', croppedImage);
    downloadLink.click();
  });

  // 重新绘制函数
  function redraw() {
    // 设置canvas的宽度和高度为图片的缩放尺寸
    canvas.width = img.width * scale;
    canvas.height = img.height * scale;
    ctx.imageSmoothingEnabled = true;

    // 对图像进行了缩放和处理:图像质量
    createImageBitmap(img, {
      resizeWidth: img.width,
      resizeHeight: img.height,
      resizeQuality: 'high',
    }).then(function (bitmap) {
      // 在canvas上绘制图片
      ctx.drawImage(
        bitmap,
        0,
        0,
        img.width,
        img.height,
        0,
        0,
        canvas.width,
        canvas.height
      );
    });

    // 在canvas上绘制图片
    // ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);

    // 根据缩放比例调整裁剪框位置和大小
    // cropBox.style.width = `${parseInt(cropBox.style.width) * scale}px`;
    // cropBox.style.height = `${parseInt(cropBox.style.height) * scale}px`;
    cropBox.style.left = `${parseInt(cropBox.style.left) * scale}px`;
    cropBox.style.top = `${parseInt(cropBox.style.top) * scale}px`;
    // 中心裁剪框
    centerCropBox();
  }
  // 调整裁剪框位置函数
  function adjustCropBoxPosition() {
    const left = parseInt(cropBox.style.left);
    const top = parseInt(cropBox.style.top);

    // 设置裁剪框的左、上位置
    cropBox.style.left =
      Math.min(
        Math.max(left, 0),
        canvas.width - parseInt(cropBox.style.width)
      ) + 'px';
    cropBox.style.top =
      Math.min(
        Math.max(top, 0),
        canvas.height - parseInt(cropBox.style.height)
      ) + 'px';
    setCropBoxInfo();
  }

  // 更新裁剪框大小函数
  function updateCropBoxSize() {
    const cropWidth = parseInt(document.getElementById('cropWidth').value);
    const cropHeight = parseInt(
      document.getElementById('cropHeight').value
    );

    // 设置裁剪框的宽度和高度
    cropBox.style.width = cropWidth + 'px';
    cropBox.style.height = cropHeight + 'px';
  }

  // 中心裁剪框函数
  function centerCropBox() {
    // 设置裁剪框的左、上位置为居中
    cropBox.style.left =
      (canvas.width - parseInt(cropBox.style.width)) / 2 + 'px';
    cropBox.style.top =
      (canvas.height - parseInt(cropBox.style.height)) / 2 + 'px';
  }

  // 是否正在拖拽裁剪框的标志
  let isDragging = false;

  // 当裁剪框被拖动时执行
  cropBox.addEventListener('mousedown', function (e) {
    isDragging = true;
    // 记录鼠标点击位置与裁剪框左上角距离
    cropOffsetX = e.clientX - parseInt(cropBox.style.left);
    cropOffsetY = e.clientY - parseInt(cropBox.style.top);
  });

  // 当鼠标移动时执行
  document.addEventListener('mousemove', function (e) {
    if (isDragging) {
      // 计算裁剪框的新位置
      const left = e.clientX - cropOffsetX;
      const top = e.clientY - cropOffsetY;

      // 设置裁剪框的左、上位置
      cropBox.style.left = left + 'px';
      cropBox.style.top = top + 'px';

      // 调整裁剪框位置
      adjustCropBoxPosition();
    }
  });

  // 当鼠标释放时执行
  document.addEventListener('mouseup', function () {
    isDragging = false;
  });

  function setCropBoxInfo() {
    $('#cropBoxInfo').html(
      cropBox.style.left +
        ',' +
        cropBox.style.top +
        ',' +
        cropBox.style.width +
        ',' +
        cropBox.style.height +
        '<br>' +
        scale +
        ',' +
        canvas.width +
        ',' +
        canvas.height
    );
  }
</script>

3.css样式代码

<style>
  #canvasContainer {
    position: relative;
  }
  #cropBox {
    position: absolute;
    border: 1px dashed red;
    pointer-events: all;
    box-sizing: border-box;
  }
</style>

完成!!!

文件上传的关键代码如下:

// 当文件输入框选择文件时执行
document
  .getElementById('fileInput')
  .addEventListener('change', function (event) {
    // 获取选择的文件
    const file = event.target.files[0];
    const fileInput = document.getElementById('fileInput');
    const filePath = fileInput.value;
    const allowedExtensions = /(\.png|\.gif|\.jpe?g)$/i; // 正则表达式匹配指定扩展名
    if (!allowedExtensions.exec(filePath)) {
      alert('请选择 PNG、GIF 或 JPE 格式的图片文件!');
      fileInput.value = ''; // 清空文件选择器中的值,防止非法文件的上传
      return false;
    }
    // 验证文件类型为图像类型
    if (file.type && !file.type.startsWith('image/')) {
      alert('请选择图片文件!');
      return false;
    }
    // 创建一个文件读取器
    const reader = new FileReader();
    // 当读取完成后执行
    reader.onload = function (event) {
      // 当图片加载完成后执行
      img.onload = function () {
        // 显示控制器
        document.getElementById('controls').style.display = 'block';
        // 重新绘制
        redraw();
        // 显示裁剪框
        cropBox.style.display = 'block';
        // 调整裁剪框大小
        updateCropBoxSize();
        // 中心裁剪框
        centerCropBox();
      };
      // 设置图片源
      img.src = event.target.result;
    };
    // 读取文件为数据URL
    reader.readAsDataURL(file);
  });

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

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

相关文章

【Bootloader学习理解学习--加强版】

笔者在接着聊一下bootloader&#xff0c;主要针对MCU的Bootloader。 笔者之前介绍过一篇Bootloader文章&#xff0c;主要是其概念、一些升级包的格式和升级流程&#xff0c;本次接着来说一下。 1、MCU代码运行方式 之前文章也介绍过&#xff0c;MCU的代码运行方式有两种&…

Nacos源码本地搭建流程及目录结构解读

下载地址 https://github.com/alibaba/nacos 目录结构 本地单机启动 首先maven编译完成之后在console下面找到Nacos 这个就是主启动类 然后再vm中配置参数-Dnacos.standalonetrue表示单机启动 当控制台没有报错 访问 http://localhost:8848/nacos 控制台界面登录进来之后显…

27.0/多态/对象向上转型/向下转型/抽象类/抽象方法。

目录 27.1为什么使用多态? 27.1.2什么是多态 27.1.3对象多态 27.1.4多态的使用前提 27.2 向上转型 27.3向下转型 (面试题) 27.4抽象类和抽象方法 特点(面试题): 27.1为什么使用多态? 需求1&#xff1a;动物园让我们实现一个功能&#xff1a; 创建一个狗类 &#xff0c;狗…

cpu飙高问题,案例分析(二)——批处理数据过大引起的应用服务CPU飙高

上接cpu飙高问题&#xff0c;案例分析&#xff08;一&#xff09; 一、批处理数据过大引起的应用服务CPU飙高 1.1 问题场景 某定时任务job 收到cpu连续&#xff08;配置的时间是180s&#xff09;使用超过90%的报警; 1.2 问题定位 观察报警中的jvm监控&#xff0c;发现周期…

软件测试测试文档编写

在软件测试中的流程中&#xff0c;测试文档也是一个重要的流程&#xff0c;所以测试人员也需要学习测试文档的编写和阅读。 一、定义&#xff1a;   测试文档&#xff08;Testing Documentation&#xff09;记录和描述了整个测试流程&#xff0c;它是整个测试活动中非常重要…

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 上一节把数据库与相关基础数据字典准备好&#xff0c;下面就来实现相应的功能&#xff0c;目前先针对自定义…

大学招聘平台既然存在逻辑漏

找到一个学校的就业信息网&#xff0c; 随便点击一个招聘会&#xff0c;并且抓包查看返回包 注意返回包中的dwmc参数&#xff0c;这个是公司名称&#xff0c;zplxr参数这个是招聘人员姓名&#xff0c;lxdh参数是电话号码&#xff0c;这几个参数后面有用 在第一张图点击单位登…

Python 爬虫 案例 之 豆瓣Top250电影数据

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 课程亮点&#xff1a; 1、动态数据抓包演示 2、csv文件保存 3、requests模块的使用 4、parsel解析数据的使用 环境介绍&#xff1a; python 3.8 pycharm 模块…

一种方便、优美的使用Python调用fofa API的方法

免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c; 文章作者不为此承担任何责任。 学习网络安全的过程中&#xff0c;绕不开fofa搜索&#xff0c;我的需求是使用fofa获取互联网所…

C++不同平台下的RTTI实现

给定一个含有虚函数的对象的地址&#xff0c;找到对应的类名&#xff0c;不同平台下方法也不同&#xff0c;这是由于RTTI实现并没有统一的标准。 Linux&#xff1a; #include <iostream> #include <typeinfo>class Person { public:virtual void func(){std::cout…

[算法总结] - 蓄水池采样算法

问题描述 在长度为N的数组中&#xff0c;随机等概率选取K个元素&#xff0c;如何实现这个随机算法。 思路很简单&#xff0c;生成一个[0, N]的随机数index&#xff0c;然后返回index上的数值即可。 但是&#xff0c;如果输入是一个长度未知的数组比如stream&#xff0c;先遍历…

【Jmeter】什么是BeanShell?

一、什么是BeanShell&#xff1f; BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器&#xff0c;JMeter性能测试工具也充分接纳了BeanShell解释器&#xff0c;封装成了可配置的BeanShell前置和后置处理器&#xff0c;分别是 BeanShell Prep…

中科大蒋彬课题组开发 FIREANN,分析原子对外界场的响应

内容一览&#xff1a; 使用传统方法分析化学系统与外场的相互作用&#xff0c;具有效率低、成本高等劣势。中国科学技术大学的蒋彬课题组&#xff0c;在原子环境的描述中引入了场相关特征&#xff0c;开发了 FIREANN&#xff0c;借助机器学习对系统的场相关性进行了很好的描述。…

盘点72个Android系统源码安卓爱好者不容错过

盘点72个Android系统源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1qiWeLjF2i4dlgmTYgPPSvw?pwd8888 提取码&#xff1a;8888 项目名称 A keyboardlisten…

【链接MySQL】教你用VBA链接MySQL数据库

hi&#xff0c;大家好呀&#xff01; 之前呢&#xff0c;给大家分享过一个自制链接表管理器的文章&#xff0c;文章中有链接SQL Server数据库的代码&#xff0c;大家对这一段代码比较有兴趣&#xff0c;既然大家有兴趣&#xff0c;那我们今天就来讲一下链接数据库的代码。 这…

Vue路由嵌套和携带参数的几种方法

1、路由嵌套 路由嵌套逻辑&#xff1a; router.index.js中使用children嵌套子路由 //该文件专门用于创建整个文件的路由器 import VueRouter from vue-routerimport About from "/pages/About"; import Home from "/pages/Home"; import News from "…

基础课12——深度学习

深度学习技术是机器学习领域中的一个新的研究方向&#xff0c;它被引入机器学习使其更接近于最初的目标——人工智能。深度学习的最终目标是让机器能够像人一样具有分析学习能力&#xff0c;能够识别文字、图像和声音等数据。 深度学习的核心思想是通过学习样本数据的内在规律…

控价是什么意思

对价格进行控制&#xff0c;使其在一个目标范围内的行为被称为控价&#xff0c;那为什么要做控价&#xff0c;控价的前提是价格乱了&#xff0c;而品牌会对渠道中的低价进行控制&#xff0c;这就是品牌进行控价的目标&#xff0c;控制低价。 品牌可以选择自己去控价&#xff0c…

python计算概率分布

目录 1、泊松分布 2、卡方分布 3、正态分布 4、t分布 5、F分布 1、泊松分布 泊松分布是一种离散概率分布&#xff0c;描述了在固定时间或空间范围内&#xff0c;某个事件发生的次数的概率分布。该分布以法国数学家西蒙德尼泊松的名字命名&#xff0c;他在19世纪早期对这种…

深入了解MySQL数据库管理与应用

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 当涉及MySQL数据库管理与应用时&#xff0c;深…