微信小程序基于Canvas实现头像图片裁剪(上)
序言
嘿,打工人混迹职场这么久,图片处理肯定都没少碰。不过咱说实话,大部分时候都是直接 “抄近道”,用现成的三方组件😏。就像我,主打一个会用工具,毕竟善用工具可是咱人类的 “超能力”,不会用那可就真成 “原始人” 啦🤣。最近在搞微信小程序开发时,遇到个需求:用户上传图片得能裁剪。我瞅了瞅 UI 给的效果图,好家伙,找遍了都没发现合适的组件,这下没办法,社畜的 “使命感” 上身,只能自己动手,丰衣足食咯😅。
UI图

咱来拆解一下在微信小程序里实现这功能的流程。首先从上个页面调用小程序选择图片的 API——chooseMedia,这就好比打开了一个 “图片宝库”,通过它能拿到用户选好图片后的临时路径(下面咱就简称 path 啦)。接着带着这个 path “奔赴” 当前页面,然后用Canvas这个神奇的 “画布” 把图片画出来,之后还要让图片能实现移动、缩放这些 “炫酷操作”。
使用 Canvas 画出图片
选择图片这块咱就不细究啦,不是本文的 “主角”,咱直接从拿到 path 之后讲起。
第一步得初始化 Canvas,给它设定宽高。在页面加载完毕时,利用createSelectorQuery这个 “小助手”,拿到 Canvas 的宽高以及 node,然后把这些数据存好,后续其他功能计算时可得靠它们 “大显身手”。
初始化 Canvas
<canvas id="canvas" type="2d" style="width: 100%; height: 100%;"></canvas>
initCanvas() {return new Promise((resolve, reject) => {const query = wx.createSelectorQuery()query.select('#canvas').fields({ node: true, size: true }).exec((res) => {const { node, width, height } = res[0]node.width = widthnode.height = heightconst ctx = node.getContext('2d')this.setData({ 'canvas.target': node,'canvas.ctx': ctx,'canvas.width': width,'canvas.height': height})resolve()})})},
可能有的小伙伴要问了:已经设了宽高 100%,为啥还要再设置 node 呢🧐?这是因为不明确设置宽高属性,它就会用默认值,这就像你没给导航设定目的地,它可能就带你去 “奇怪的地方”,绘图效果自然就 “跑偏” 了。但 Canvas 尺寸又不是固定不变的,没法直接简单设置,所以初始化时这一步很关键哦。
初始化图片到Canvas
接下来把图片 “请” 到 Canvas 上。注意啦,图片得在 Canvas 正中间,还得完整地展示在可见区域,可不能按原图尺寸随意 “摆放”,所以得做些简单的 “数学运算”。
initImage() {return new Promise((resolve, reject) => {const { path, canvas: { target: canvas } } = this.datalet image = canvas.createImage();image.src = pathimage.onload = () => {const { ctx, width: canvasWidth, height: canvasHeight } = this.data.canvasconst scale = image.width / image.heightlet width = canvasWidthlet height = canvasWidth / scaleif (height > canvasHeight) {height = canvasHeightwidth = canvasHeight * scale}const x = (canvasWidth - width) / 2;const y = (canvasHeight - height) / 2;ctx.drawImage(image, x, y, width, height);this.setData({'image.target': image,'image.width': width,'image.height': height,'image.x': x,'image.y': y,'image.scale': scale})resolve()}})},
先用 Canvas 对象创建一个图片对象,再把 path “赋予” 它。等图片加载好,就能获取到图片尺寸,进而算出宽高比。因为要完整展示图片,所以图片高度最大就是 Canvas 的高度,宽度最大是 Canvas 的宽度。通过宽高比,就能算出当绘制宽度是 Canvas 宽度时对应的高度。然后把这个计算出的高度和 Canvas 高度对比,如果超了,就以 Canvas 高度为绘制高度,再算出相应宽度。
尺寸确定好后,最后算图片绘制的中心位置。以 Canvas 左上角为 (0,0),X 轴坐标就是 Canvas 宽度减去图片宽度再除以 2,Y 轴坐标同理。最后调用drawImage方法,图片就 “乖乖” 初始化绘制好啦。别忘了把图片相关数据存起来,后续功能还得靠它们 “帮忙” 呢。
绘制裁剪区域
再接着就是绘制裁剪区域,从 UI 图能看出,裁剪区域是正中心的一个长方形。咱先确定好这个长方形尺寸并存起来,然后利用这个尺寸在 Canvas 四周画上四个黑色遮罩长方形,这招就像 “围点打援”,简单又有效😎。
// 裁剪区域的相关数据
crop: {scale: 5 / 7,width: 200,height: 0,x: 0,y: 0,
},
// 裁剪区域尺寸等信息
getCropInfo() {const {canvas: { width: canvasWidth, height: canvasHeight },crop: { width: cropWidth, scale: cropScale }} = this.dataconst result = {scale: cropScale,width: cropWidth,height: cropWidth / cropScale,x: (canvasWidth - cropWidth) / 2,y: (canvasHeight - cropWidth / cropScale) / 2}this.setData({'crop.height': cropWidth / cropScale,'crop.x': (canvasWidth - cropWidth) / 2,'crop.y': (canvasHeight - cropWidth / cropScale) / 2,})return result},// 绘制裁剪区域drawCrop() {const { ctx, width: canvasWidth, height: canvasHeight } = this.data.canvasconst { width: cropWidth, height: cropHeight, x, y} = this.data.crop// 该方式会出现 一次绘制成功一次绘制不成功的情况// ctx.rect(0, 0, canvasWidth, canvasHeight);// ctx.rect(x, y, cropWidth, cropHeight);// ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';// ctx.fill('evenodd');ctx.rect(0, 0, canvasWidth, y);ctx.rect(0, y + cropHeight, canvasWidth, y);ctx.rect(0, y, x, cropHeight);ctx.rect(x + cropWidth, y, x, cropHeight);ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';ctx.fill()},
到这儿,基本雏形已经有啦。不过关于拖拽、缩放这些更精彩的操作,由于篇幅限制,咱下篇再继续 “揭秘”😜。

🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。
诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ !
👉点我
感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 Star ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!
Nova UI组件库:https://github.com/gmingchen/nova-ui- 基于 Vue3 + Element-plus 管理后台基础功能框架
- 预览:https://admin.gumingchen.icu
- Github:https://github.com/gmingchen/agile-admin
- Gitee:https://gitee.com/shychen/agile-admin
- 基础版后端:https://github.com/gmingchen/java-spring-boot-admin
- 文档:http://admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即时聊天系统
- 预览:https://chatterbox.gumingchen.icu/
- Github:https://github.com/gmingchen/chatterbox
- Gitee:https://gitee.com/shychen/chatterbox
- 基于 node 开发的后端服务:https://github.com/gmingchen/node-server
相关文章:
微信小程序基于Canvas实现头像图片裁剪(上)
序言 嘿,打工人混迹职场这么久,图片处理肯定都没少碰。不过咱说实话,大部分时候都是直接 “抄近道”,用现成的三方组件😏。就像我,主打一个会用工具,毕竟善用工具可是咱人类的 “超能力”&…...
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
一、Logback 在 Spring Boot 中,日志框架默认使用的是 Logback,Spring Boot 提供了对日志配置的简化 Spring Boot 默认会将日志输出到控制台,并且日志级别为 INFO 可以在 application.yaml 或 application.properties 文件中进行日志配置 …...
基于VMware的Cent OS Stream 8安装与配置及远程连接软件的介绍
1.VMware Workstation 简介: VMware Workstation(中文名“威睿工作站”)是一款功能强大的桌面虚拟计算机软件,提供用户可在单一的桌面上同时运行不同的操作系统,和进行开发、测试 、部署新的应用程序的最佳解决方案。…...
Ubuntu环境基于Ollama部署DeepSeek+Open-Webui实现本地部署大模型-无脑部署
Ollama介绍 Ollama是一款简单好用的模型部署工具,不仅可以部署DeepSeek,市面上开源模型大部分都可以一键部署,这里以DeepSeek为例 官网 DeepSeek 版本硬件要求 安装Ollama 环境 sudo apt update sudo apt install curl sudo apt install lsof1.命令一键安装 在官网点击…...
goto在Java中的用法
说明:goto 在一些编程语言(如C语言)中,是用来表示跳转的,即代码执行到此处跳转到对应位置继续执行。 举例 举个例子,如下,是一个双层嵌套循环。现在我需要代码在内层循环符合某条件时跳出双层…...
Vue3+Vite+TypeScript+Element Plus开发-03.主页设计与router配置
系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 文章目录 目录 系列文档目录 文章目录 前言 一、主页设计 二、配置代替别名 三、配置router 四、运行效果 五、参考文献 前言 本文将重点介绍如何使用…...
linux gcc
一、常用编译选项 基本编译 gcc [input].c -o [output] 示例: gcc hello.c -o hello # 将 hello.c 编译为可执行文件 hello ./hello # 运行程序 分步编译 预处理:-E(生成 .i 文件) gcc -E hello.c -o hello…...
无限滚动(Infinite Scroll)页面谷歌不收录!必须改回分页吗?
近三年,全球超过58%的网站采用无限滚动设计(数据来源:PageTraffic 2023) 谷歌官方数据显示,动态加载内容的索引失败率高达73%(Google Webmaster Report 2022),而采用纯无限滚动的页…...
出现次数超过一半的数(信息学奥赛一本通-1186)
【题目描述】 给出一个含有n(0 < n < 1000)个整数的数组,请找出其中出现次数超过一半的数。数组中的数大于-50且小于50。 【输入】 第一行包含一个整数n,表示数组大小; 第二行包含n个整数,分别是数组…...
Git相关笔记1 - 本地文件上传远程仓库
Git相关笔记 目录 Git相关笔记Git上传相关文件第一步创建一个仓库:第二步本地创建空文件夹:第三步开始在gitbush上传文件:解决外网网络连接的问题:中文文件的编码问题:参考资料 Git上传相关文件 第一步创建一个仓库&a…...
Linux_4
开始学习ssh工具 在做开发的时候,肯定不止一台服务器,那么假设每台服务器都是Linux服务器,要在服务器上操作就需要登入终端,即Terminal。ssh的作用就是可以通过一个服务器登陆上其他的服务器。 登陆到哪个服务器看到的就是哪个服务器的终端terminal。 ssh登陆 ssh user@…...
如何计算财富自由所需要的价格?
写在前面:【财富自由计算器】已上线,快算算财富自由要多少 多少钱,才能实现你的财富梦想? 需要多少,才能实现财务安全、财务独立,甚至财务自由? 看到结尾,你会很清楚地看到&…...
thinkphp每条一级栏目中可自定义添加多条二级栏目,每条二级栏目包含多个字段信息
小程序客户端需要展示团购详情这种结构的内容,后台会新增多条套餐,每条套餐可以新增多条菜品信息,每条菜品信息包含菜品名称,价格,份数等字段信息,类似于购物网的商品多规格属性,数据表中以json类型存储,手写了一个后台添加和编辑的demo 添加页面 编辑页面(json数据…...
Perl语言的文件系统
Perl语言中的文件系统操作 引言 在软件开发中,文件系统操作是一个不可或缺的部分。无论是简单的文件读取、写入,还是复杂的文件管理,合理的文件系统操作都能极大提升程序的效率和可维护性。Perl语言是一种强大的文本处理语言,凭…...
深入解析ARM与RISC-V架构的Bring-up核心流程
深入解析ARM与RISC-V架构的Bring-up核心流程 作者:嵌入式架构探索者 | 2023年10月 引言 在嵌入式开发中,处理器的Bring-up(启动初始化)是系统运行的第一道门槛。ARM和RISC-V作为两大主流架构,其Bring-up流程既有共性…...
关于UDP端口扫描概述
尽管互联网上大多数流行服务都基于 TCP 协议运行,但 UDP 服务也广泛部署。DNS、SNMP 和 DHCP(注册端口 53、161/162 和 67/68)是最常见的服务之一。 由于 UDP 扫描通常比 TCP 扫描更慢、更困难,一些安全审计人员可能会忽略这些端…...
C语言的操作系统
C语言的操作系统 引言 操作系统是一种系统软件,它管理计算机硬件和软件资源,并为计算机程序提供公共服务。在现代计算机科学中,操作系统是不可或缺的组成部分,而C语言则是实现高效操作系统的主要编程语言之一。本文将探讨C语言在…...
洛谷题单3-P5725 【深基4.习8】求三角形-python-流程图重构
题目描述 模仿例题,打印出不同方向的正方形,然后打印三角形矩阵。中间有个空行。 输入格式 输入矩阵的规模,不超过 9 9 9。 输出格式 输出矩形和正方形 输入输出样例 输入 4输出 01020304 05060708 09101112 13141516010203040506 …...
Lettuce与Springboot集成使用
一、Lettuce核心优势与Spring Boot集成背景 Lettuce特性 基于Netty的非阻塞I/O模型,支持同步/异步/响应式编程线程安全:共享单连接实现多线程并发操作,性能衰减低原生支持Redis集群、哨兵、主从架构,自动重连机制保障高可用Spring…...
C# 类库生成后自动复制到指定目录
C# 类库生成后自动复制到指定目录 在C#中,当你开发了一个类库项目(通常是.NET Core或.NET Framework项目),你可能会希望在构建(Build)完成后自动将生成的DLL文件复制到指定的目录。有几种方法可以实现这个需求,下面是一些常用的方法: 方法1:使用MSBuild的AfterBuild…...
《系统分析师-基础篇-1-6章总结》
第1章 绪论 系统分析师角色 职责:需求分析、系统设计、项目管理、技术协调。 能力要求:技术深度(架构设计、开发方法) 业务理解(企业流程、行业知识) 沟通能力。 系统开发生命周期 传统模型…...
一个完整的 HTTP/HTTPS 请求流程
HTTP 一个完整的 HTTP 请求流程可以分为以下几个步骤: 1. DNS 解析 当客户端(通常是浏览器)输入 URL(例如 https://www.example.com)时,它首先需要解析出对应的 IP 地址。这个过程涉及 DNS(域…...
go游戏后端开发25:红中麻将规则介绍
一、游戏基础规则介绍 在开发红中麻将游戏之前,我们需要先了解其基础规则。红中麻将的牌面由 a、b、c、d 四种花色组成,其中 a、b、c 分别代表万、条、筒,每种花色都有 1 - 9 的九种牌,每种牌各有四张,总计 36 张 4 …...
【YOLO系列(V5-V12)通用数据集-电梯内电动车检测数据集】
YOLO格式的电梯内电动车检测数据集,适用于YOLOv5-v11所有版本,可以用于本科毕设、发paper、做课设等等,有需要的在这里获取: 电梯内电动车检测数据集 数据集专栏地址:https://blog.csdn.net/qq_41304809/category_1290…...
Python每日一题(15)
Python每日一题2025.4.4 一、题目题目描述输入格式输出格式输入输出样例 #1输入 #1输出 #1 二、分析三、源代码四、deepseek 一、题目 题目描述 您需要写一种数据结构,来维护一些数(都是绝对值 1 0 9 10^9 109 以内的数)的集合,…...
算法题(114):矩阵距离
审题: 本题需要我们找出所有0距离最近的1的曼哈顿距离 思路: 方法一:多源bfs 分析曼哈顿距离: 求法1:公式法,带入题目公式,利用|x1-x2||y1-y2|求出 求法2:曼哈顿距离就是最短距离 本…...
0102-web架构网站搭建-基础入门-网络安全
文章目录 1. 常规2 站库分离3 前后端分离4 集成环境5 docker6 分配站结语 1. 常规 结构:源码数据都在同服务器 影响:无,常规安全测试手法 2 站库分离 结构:源码和数据库不在同服务器 存储:其他服务器上数据库或者…...
Linux系统编程:进程管理、内存对比与树莓派应用
一、认识进程和线程,在Linux系统下查看系统中各进程的编号pid并终止一个进程pid 1.进程和线程 进程:操作系统分配资源(如内存、CPU时间片)的基本单位。每个进程有独立的内存空间,进程间通信需要较复杂的机制…...
ue5 仿鬼泣5魂类游戏角色和敌人没有碰撞
UE5系列文章目录 文章目录 UE5系列文章目录前言一、问题原因二、设置碰撞2.读入数据 总结 前言 ue5 仿鬼泣5魂类游戏角色和敌人没有碰撞 一、问题原因 在UE5中,角色和敌人没有碰撞可能是由多种原因导致的,以下是一些可能的原因及解决方法:…...
基于Flask的MBA考生成绩查询系统设计与实现
基于Flask的MBA考生成绩查询系统设计与实现 序言 2024年吉林大学MBA在职研究生考试成绩公布后,考生收到的成绩单为PDF格式文档。为方便考生快速查询个人成绩及排名信息,笔者基于Python Flask框架开发了本查询系统。该系统支持关键词模糊查询、序号范围…...
