js canvas实现裁剪图片并下载

简历上给自己挖的坑,面试被拷打,早就该填了T.T
参考:【js canvas实现图片裁剪】 https://www.bilibili.com/video/BV1QK411d7n1/?share_source=copy_web&vd_source=bf743b20b76eab11028ba2fb05f056b4

效果

在这里插入图片描述

思路

组成:

上传文件区域
----------------------------------------------
原图canvas  |   裁剪出的图片canvas  | 下载图片按钮

上传文件区域

<input> 标签,type为file

原图canvas

在上面有一个半透黑的裁剪区域,可以鼠标拖拽移动,实际上是在原图canvas上画了一个正方形,需要注意不能移出原图的区域。
使用ctx.getImageData()可以得到指定区域的ImageData对象,是一个像素值的数组对象
在这里插入图片描述
在这里插入图片描述

裁剪出的图片canvas

使用clipCvx.putImageData(imageData)可以传入刚刚裁剪得到的ImageData对象,绘制到目标位图中

下载图片按钮

clipCvs.toDataUrl()获取canvas的转成图片的dataurl格式,用fetch()请求这个url资源,因为我们是在请求一个图片,为了解析正常,我们对响应执行 Body.blob 来设置相应的 MIME 类型。然后创建一个 Object URL,赋给<a download>标签的href作为下载链接

源码

注释写得very详细了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas裁剪图片</title>
</head>

<body>
    <div style="margin-bottom: 10px">
        上传图片:<input type="file" onchange="onChange(this.files[0])">
    </div>
    <canvas id="cvs"></canvas>
    <canvas id="clipCvs"></canvas>
    <button id="download">下载图片</button>
</body>
<script>
    const cvs = document.getElementById('cvs')  // 用于显示原图的canvas
    const clipCvs = document.getElementById('clipCvs')   // 用于拖动的裁剪范围框框 canvas
    const download = document.getElementById('download')
    const ctx = cvs.getContext('2d')
    const clipCtx = clipCvs.getContext('2d')
    const img = new Image()  // 创建image节点
    let size = 150  // 正方形裁剪框框的边长
    let maxW = 400  // 用于显示原图的canvs的最大宽度
    const p = {
        left: 0,
        top: 0,
        stepX: 0,
        stepY: 0
    }
    /**
     * 上传图片
     */ 
    const onChange = (file) => {
        onInit(URL.createObjectURL(file))
    }
    // 加载图片,并初始化裁剪功能
    const onInit = (src) => {
        clipCvs.width = clipCvs.height = size   // 设置正方形的裁剪框框的宽、高
        img.src = src
        img.onload = () => {
            let width = img.width
            let height = img.height
            // 在maxW的范围内设置的width和height
            if (width > maxW) {  
                
                height = maxW / width * height
                width = maxW
            }
            cvs.width = width
            cvs.height = height
            // 让clipcvs初始在图片最中间
            render(width / 2 - size / 2, height / 2 - size / 2)
        }
    }
    
    /**
     * 渲染裁剪前canvas
     * @param left clipcvs的left定位
     * @param top clipcvs的top定位
     */
    const render = (left = 0, top = 0) => {
        ctx.clearRect(0, 0, cvs.width, cvs.height)
        // 先把图片画在canvas里,左上角在目标画布上 X 轴坐标, 左上角在目标画布上 Y 轴坐标
        ctx.drawImage(img, 0, 0, cvs.width, cvs.height)
        // 为了之后画正方形裁剪框的时候 不超出图片范围
        if (left < 0) {
            left = 0
        }
        if (left > cvs.width - size) {
            left = cvs.width - size
        }
        if (top < 0) {
            top = 0
        }
        if (top > cvs.height - size) {
            top = cvs.height - size
        }
        // getImageData -- 从(left,top)坐标,裁剪出size*size大小的图片, 返回值是一个ImageData类型的对象,是一个包含像素值的数组对象
        // clipPic 裁剪出的image显示在原图右侧
        clipPic(ctx.getImageData(left, top, size, size))
        // 绘制裁剪框,即在原来从canvas上画一个半透明的正方形
        ctx.beginPath()
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'
        ctx.fillRect(left, top, size, size)  
        // p.left现在为裁剪框框的x轴偏移 ,p.top 为裁剪框框的y轴偏移
        p.left = left
        p.top = top
    }
    // 裁剪图片,并显示在右侧
    const clipPic = (data) => {
        clipCtx.clearRect(0, 0, clipCvs.width, clipCvs.height)
        clipCtx.putImageData(data, 0, 0)  // putImageData -- 传入一个IamgeData对象,绘制到位图中
    }
    
    let isMoving = false

    /**
     *  监听 可拖动裁剪框框的鼠标按下事件
     */
    cvs.onmousedown = (e) => {
        // e.pageX 鼠标指针相对于整个文档的 X 轴坐标
        // e.pageY 鼠标指针相对于整个文档的 Y 轴坐标
        p.stepX = e.pageX - p.left  // 在x轴方向移动的距离
        p.stepY = e.pageY - p.top   // 在y轴方向移动的距离
        isMoving = true
    }
    /**
     * 监听 可拖动裁剪框框的鼠标松开事件
     * 如果刚刚拖动了,则重新绘制canvas
     */
    cvs.onmousemove = (e) => {
        if (isMoving) {
            render(e.pageX - p.stepX, e.pageY - p.stepY)   // 其实算出来的是 p.left + e.pageX2 - e.pageX1, p.top + e.pageY2 - e.pageY1
        }
    }
    /**
     * 监听 可拖动裁剪框框的鼠标松开事件 把move状态置为false
     */ 
    document.onmouseup = () => {
        isMoving = false
    }
    /**
     * 下载裁剪好的图像
     */ 
    download.onclick = async () => {
        // toDataURL 返回一个包含图片展示的 data URI。可以使用 type 参数指定其类型,默认为 PNG 格式。图片的分辨率为 96dpi
        // data URI 的格式一般为:data:image/png;base64,iVBORw0KNdqsdZ05vRbvTcvXIyi2YxWpxtFA8a+9FJ/2mNFteBN1MP.....
        const res = await fetch(clipCvs.toDataURL('image/png'))
        // 可以使用 blob() 从 response 中读取一个Blob对象
        const blob = await res.blob()
        const a = document.createElement('a')
        a.setAttribute('download', 'clip.png') // download属性让浏览器将URL视为下载资源,可以不填值(会自动赋值),值为文件名
        a.href = URL.createObjectURL(blob)  // 置为a标签的href
        a.click()
    }
    /**
     * 页面初始化 初始运行
     */ 
    onInit('./images/boy.jpg')
</script>
</html>

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

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

相关文章

基于Springcloud可视化项目:智慧工地可视化大数据云平台源码

目录 技术架构 智慧工地系统在实际推行过程中遇到的问题 智慧工地接纳程度较低 基础设施条件有待完善 智慧工地整体生态尚未完善 智慧工地平台各功能模块 施工过程工信程息信管息理管模理块 人员管理模块 生产管理模块 技术管理模块 质量管理模块 安全管理模块 绿…

记录一个Kafka客户端Offset Explore连不上的问题

我昨天把集群重装了一下&#xff0c;再连这个工具就连不上了&#xff08;你先把zk和kafka在集群启起来&#xff09;&#xff0c;报错截图如下&#xff1a; 英文翻译过来大概就是说遍历zk指定路径不存在&#xff0c;我还以为zk的问题&#xff0c;回去又把zk的文档翻了一遍&#…

多线程代码案例之阻塞队列

目录 1.生产者消费者模型 2.使用标准库中的阻塞队列 3.模拟实现阻塞队列 在介绍阻塞队列之前&#xff0c;会先介绍一些前置知识&#xff0c;像队列&#xff1a;有普通队列、优先级队列、阻塞队列、和消息队列。前面两个是线程不安全的&#xff0c;而后面两个是线程安全的。本…

FFmpeg: 自实现ijkplayer播放器--03UI界面设计

文章目录 UI设计流程图UI设计界面点击播放功能实现 UI设计流程图 UI设计界面 主界面 控制条 播放列表 画面显示 标题栏 设置界面 提示框 点击播放功能实现 槽函数实现&#xff1a; connect(ui->ctrlBarWind, &CtrlBar::SigPlayOrPause, this, &Main…

软件杯 深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

InnoDB中高度为3的B+树最多可以存多少数据?

参考&#xff1a; &#x1f525;我说MySQL每张表最好不超过2000万数据&#xff0c;面试官让我回去等通知&#xff1f; - 掘金 考虑到磁盘IO是非常高昂的操作&#xff0c;计算机操作系统做了预读的优化&#xff0c;当一次IO时&#xff0c;不光把当前磁盘地址的数据&#xff0c;…

QtCreater 使用

QtCreater 创建项目 1.刚进入 QtCreater 的界面是这样的一个界面 ① 创建一个新的文件&#xff0c;那么我们就选择左上角的 “文件” ② 点击新建文件&#xff0c;或者也可以直接使用快捷键 CtrlN 此时就会弹出对话框&#xff0c;让我们选择想要创建的文件&#xff1a; Appli…

stm32f103---按键控制LED---代码学习

目录 一、总体代码 二、LED端口初始化分析 ​编辑 三、LED灭的控制 四、LED亮 五、按键初始化 ​ 六、按键控制LED的功能 一、总体代码 这里使用到了LED灯和按键&#xff0c;实现效果是当按键按下时灯的亮灭转化 #include "stm32f10x.h" #include "bsp_led…

Notion2024年最新桌面端安装+汉化教程,支持MAC和WIN版本

Notion 是一个多功能的协作工具&#xff0c;可以用于个人和团队的知识管理、项目管理、笔记记录和协同编辑等。它提供了灵活的页面和数据库功能&#xff0c;可以根据不同需求进行自定义和组织。Notion 能够帮助用户更高效地组织和共享信息&#xff0c;提升工作效率和团队合作。…

ThingsBoard通过服务端获取客户端属性或者共享属性

MQTT基础 客户端 MQTT连接 通过服务端获取属性值 案例 1、首先需要创建整个设备的信息&#xff0c;并复制访问令牌 ​2、通过工具MQTTX连接上对应的Topic 3、测试链接是否成功 4、通过服务端获取属性值 5、在客户端查看对应的客户端属性或者共享属性的key 6、查看整个…

改进YOLOv8系列:结合自研注意力模块MultiScaleAttentiveConv (MSAConv)

改进YOLOv8注意力系列七:结合空间关系增强注意力SGE、SKAttention动态尺度注意力、全局上下文信息注意力Triplet Attention 代码MultiScaleAttentiveConv (MSAConv)本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代码和包含多种更…

蓝桥杯嵌入式(G431)备赛笔记——DMA+ADC(单通道+多通道)

单通道&#xff1a; 开启循环模式&#xff0c;两个参数设为word u32 adc_tick0; u32 r37_value0; u32 r38_value0; float r37_volt0; float r38_volt0;//DMAADCvoid DMA_ADC() {if(uwTick-adc_tick<100) return;adc_tick uwTick;HAL_ADC_Start_DMA(&hadc2, &r37_v…

vivado ila 运行触发器、停止触发器、使用自动重新触发

运行触发器 您可在 2 种不同模式下运行或装备 ILA 核触发器 &#xff1a; • “ Run Trigger ” &#xff1a; 选择要装备的 ILA 核 &#xff0c; 然后单击“ ILA 仪表板 (ILA Dashboard) ”窗口或“硬件 (Hardware) ”窗口 工具栏上的“ Run Trigger ”按钮即可装备 IL…

013:vue3 Pinia详解使用详解

文章目录 1. Pinia 是什么2. Pinia 功能作用3. 手动添加Pinia到Vue项目4. Pinia基础使用5. getters实现6. action异步实现7. storeToRefs工具函数8. Pinia的调试9. 总结 1. Pinia 是什么 Pinia 是 Vue 的专属的 最新状态管理库是 Vuex 状态管理工具的替代品和 Vuex 一样为 Vue…

Django处理枚举(枚举模型)以及source的使用

Django处理枚举-枚举模型 1、定义模型类、序列化器类2、对上面这些场景使用source参数3、支持连表查询4、自定义序列化输出方法5、案例5 1、定义模型类、序列化器类 定义模型类models.py&#xff1b;项目模型类、接口模型类、用例模型类 from django.db import modelsclass T…

选择自动化工具是一个关键的决策过程

好的自动化软件测试工具&#xff0c;不仅可以有效的缩短全生命周期的交付周期&#xff0c;还可以提高测试的有效性&#xff0c;还可以保证更好的高质量的交付。工具的选型是一项重要的决策过程&#xff0c;工具的采用涉及到企业的效率、成本和长期发展。 1、需求分析 确组织希…

08 Php学习:if语句、Switch语句

PHP 条件语句 当您编写代码时&#xff0c;您常常需要为不同的判断执行不同的动作。您可以在代码中使用条件语句来完成此任务。 在 PHP 中&#xff0c;提供了下列条件语句&#xff1a; if 语句 - 在条件成立时执行代码 if…else 语句 - 在条件成立时执行一块代码&#xff0c;…

Python学习笔记22 - 文件操作

文件读写的原理 文件读写的操作 常用的文件打开模式 文件对象的常用方法 with语句&#xff08;上下文管理器&#xff09;

码蹄集部分题目(2024OJ赛11期)

1&#x1f40b;&#x1f40b;&#x1f40b;银行账户&#xff08;黄金&#xff1b;模拟&#xff09; 时间限制&#xff1a;1秒 占用内存&#xff1a;128M &#x1f41f;题目描述 据说对银行账户进行盗窃时&#xff0c;如果只盗取小数点下的数值&#xff0c;就不容易引起注意…

【vue】Vue3开发中常用的VSCode插件

Vue - Official&#xff1a;vue的语法特性&#xff0c;如代码高亮&#xff0c;自动补全等 Vue VSCode Snippets&#xff1a;自定义一些代码片段 v3单文件组件vdata数据vmethod方法 别名路径跳转 参考 https://www.bilibili.com/video/BV1nV411Q7RX