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

news2024/11/17 11:23:26

效果图

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

代码实现如下:

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);
  });

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GDOUCTF2023-Reverse WP

文章目录 [GDOUCTF 2023]Check_Your_Luck[GDOUCTF 2023]Tea[GDOUCTF 2023]easy_pyc[GDOUCTF 2023]doublegame[GDOUCTF 2023]L&#xff01;s&#xff01;[GDOUCTF 2023]润&#xff01;附 [GDOUCTF 2023]Check_Your_Luck 根据 if 使用z3约束求解器。 EXP&#xff1a; from z3 i…

OkHttp的配置

一、拦截器 1.添加拦截器的作用&#xff1a; 每次在请求过程中就会回调一次intercept方法 2.拦截器的回调方法里我们可以做那些事情&#xff1a; 当前的请求还没有发给服务器&#xff0c;比如我们在与服务器通信的时候&#xff0c;一个应用中很多地方都会跟服务器发起通信。…

WGS84转CGCS2000操作步骤

1、使用一个转换软件实现不同椭球之间转换七参数的求取。打开坐标转换软件如下&#xff1a; 2、点击设置-地图投影&#xff0c; 如下&#xff1a; 3、设置需要投影到的坐标系&#xff0c;如下&#xff1a; 4、【选择目标坐标系】中选择CGCS2000&#xff0c;如下&#xff1a; 5、…

智能优化算法应用:基于正余弦算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于正余弦算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于正余弦算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.正余弦算法4.实验参数设定5.算法结果6.参考文献7.…

深入Rust的模式匹配与枚举类型

今天&#xff0c;我们将深入探讨Rust语言中的两个强大特性&#xff1a;模式匹配&#xff08;Pattern Matching&#xff09;和枚举类型&#xff08;Enums&#xff09;。这两个特性是Rust提供的核心工具之一&#xff0c;它们在处理多种类型的数据和复杂的逻辑控制中发挥着关键作用…

灰度发布专题---2、Dubbo灰度发布

通过上面描述&#xff0c;我们理解了什么是灰度发布&#xff0c;接下来我们基于Dubbo实现灰度发布。Dubbo的灰度发布常见的方式有版本控制灰度发布、路由灰度发布、基于Apollo实现灰度发布&#xff0c;我们把这每种灰度发布都实现一次。 在学习Dubbo灰度发布之前&#xff0c;我…

2019年11月7日 Go生态洞察:Go Modules v2及更高版本

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

vue3 setup语法糖,常用的几个:defineProps、defineEmits、defineExpose、

vue3和vue2组件之间传参的不同 <script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。 <script setup> 中的代码会在每次组件实例被创建的时候执行。 任何在 <script setup> 声明的顶层的绑定 (包括变量&#xff0c;函数声明&#xff0…

食材管家,轻松搞定!商户选择生鲜配送系统的原因

随着消费者对生鲜食品的需求不断增加&#xff0c;生鲜市场逐渐成为了电商领域中的热门行业。而生鲜配送系统&#xff0c;则是生鲜电商发展中不可或缺的一部分。本文将探讨商户选择生鲜配送系统的几个原因。 1. 提高效率 生鲜配送系统通过智能化的订单处理、路线规划和配送优化…

2023.11.27 关于 Mybatis 增删改操作

目录 引言 增加用户操作 删除用户操作 修改用户操作 阅读下述文章之间 建议点击下方链接先了解 MyBatis 的创建与使用 MyBatis 的创建与使用 建议点击下方链接先了解 单元测试 的创建与使用 Spring Boot 单元测试的创建与使用 引言 为了方便下文实现增、删、改操作我们先…

java多线程-扩展知识三:乐观锁与悲观锁

1、悲观锁 悲观锁有点像是一位比较悲观&#xff08;也可以说是未雨绸缪&#xff09;的人&#xff0c;总是会假设最坏的情况&#xff0c;避免出现问题。 悲观锁总是假设最坏的情况&#xff0c;认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改)&#xff0c;所以每次…

Appium 2 和 Appium Inspector 环境部署

前言 自 2022 年 1 月 1 日起&#xff0c;Appium 核心团队不再维护 Appium 1.x。官方支持的平台驱动程序的所有最新版本均不兼容 Appium 1.x&#xff0c;需要 Appium 2 才能运行。 Appium 2是一个自动化移动应用程序的开源工具&#xff0c;它带来了以下重要改进&#xff1a;  …

SpringBootWeb案例_01

Web后端开发_04 SpringBootWeb案例_01 原型展示 成品展示 准备工作 需求&环境搭建 需求说明&#xff1a; 完成tlias智能学习辅助系统的部门管理&#xff0c;员工管理 环境搭建 准备数据库表&#xff08;dept、emp&#xff09;创建springboot工程&#xff0c;引入对应…

神经网络核心组件和流程梳理

文章目录 神经网络核心组件和流程梳理组件流程 神经网络核心组件和流程梳理 组件 层&#xff1a;神经网络的基本结构&#xff0c;将输入张量转换为输出张量。模型&#xff1a;由层构成的网络。损失函数&#xff1a;参数学习的目标函数&#xff0c;通过最小化损失函数来学习各…

HCIP-十一、BGP反射器和联盟

十一、BGP反射器和联盟 实验拓扑实验需求及解法1.配置各设备的接口 IP 地址。2.BGPAS 规划3.BGP 反射器4.BGP 联盟5.ebgp 邻居6.bgp 路由汇总 实验拓扑 实验需求及解法 本实验模拟 BGP 综合网络拓扑&#xff0c;完成以下需求&#xff1a; 1.配置各设备的接口 IP 地址。 所有…

这些汽车托运套路你肯定不知道

这些汽车托运套路你肯定不知道 这些套路你肯定不知道.. 学会这三招 汽车托运不怕吃亏 1 看营业执照 首先确定选择的托运公司是否有保障 要求公司出示营业执照和道路运输经营许可证 如果都没有 那就很有可能是无牌照的小作坊!! 这种出问题就肯定没保障 2 保险跟合同 一车一合同 …

安卓系统修图软件(三)

在之前的推送里面&#xff0c;博主分享过两期关于安卓手机的优质修图软件&#xff0c;今天&#xff0c;博主将带来第三期的分享&#xff0c;这也将是该栏目的最后一期。 之前的8款软件&#xff0c;都是以美化、滤镜的风格为主&#xff0c;今天博主带来的这3款&#xff0c;则是以…

数据结构与算法的精髓是什么?复杂度分析【数据结构与算法】

代码跑一遍存在什么问题&#xff1f;什么是 O 复杂度表示法&#xff1f;如何分析时间复杂度&#xff1f;常见时间复杂度量级有哪些&#xff1f;O(1)O(logn)O(n)O(nlogn)O(mn)O(m*n)O(n^2)O(2^n)O(n!)不同时间复杂度差距有多大&#xff1f;时间复杂度分析总结 如何分析空间复杂度…

2021年06月 Scratch图形化(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共10题,每题2分,共20分) 第1题 执行下列程序,输出的结果为? A:12 B:24 C:8 D:30 答案:B 第2题 执行下列程序,角色说出的内容是? A:2 B:3 C:4 D:5 答案:A 第3题 执行下列程序,输出结果为?

Spring Security 6.x 系列(5)—— Servlet 认证体系结构介绍

一、前言 本章主要学习Spring Security中基于Servlet 的认证体系结构&#xff0c;为后续认证执行流程源码分析打好基础。 二、身份认证机制 Spring Security提供个多种认证方式登录系统&#xff0c;包括&#xff1a; Username and Password&#xff1a;使用用户名/密码 方式…