微信小程序基于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

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

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

相关文章

[特殊字符] 使用 Handsontable 构建一个支持 Excel 公式计算的动态表格

在 Web 应用中&#xff0c;处理表格数据并提供 Excel 级的功能&#xff08;如公式计算、数据导入导出&#xff09;一直是个挑战。今天&#xff0c;我将带你使用 React Handsontable 搭建一个强大的 Excel 风格表格&#xff0c;支持 公式计算、Excel 文件导入导出&#xff0c;并…

0302useState-hooks-react-仿低代码平台项目

文章目录 1 useState1.1 说明返回 1.2 示例1.3 数据类型 2 state2.1 概述2.2 state特点 3 state重构问卷4 immer结语 1 useState useState 是一个 React Hook&#xff0c;它允许你向组件添加一个 状态变量。 1.1 说明 语法 const [state, setState] useState(initialState…

前端实现单点登录(SSO)的方案

概念&#xff1a;单点登录&#xff08;Single Sign-On, SSO&#xff09;主要是在多个系统、多个浏览器或多个标签页之间共享登录状态&#xff0c;保证用户只需登录一次&#xff0c;就能访问多个关联应用&#xff0c;而不需要重复登录。 &#x1f4a1; 方案分类 1. 前端级别 SS…

zabbix监控网站(nginx、redis、mysql)

目录 前提准备&#xff1a; zabbix-server主机配置&#xff1a; 1. 安装数据库 nginx主机配置&#xff1a; 1. 安装nginx redis主机配置&#xff1a; 1. 安装redis mysql主机配置&#xff1a; 1. 安装数据库 zabbix-server&#xff1a; 1. 安装zabbix 2. 编辑配置文…

无人机等非合作目标公开数据集2025.4.3

一.无人机遥感数据概述 1.1 定义与特点 在遥感技术的不断发展中&#xff0c;无人机遥感数据作为一种新兴的数据源&#xff0c;正逐渐崭露头角。它是通过无人驾驶飞行器&#xff08;UAV&#xff09;搭载各种传感器获取的地理空间信息&#xff0c;具有 覆盖范围大、综合精度高、…

大数据时代的隐私保护:区块链技术的创新应用

一、引言 在当今数字化时代&#xff0c;大数据已经成为推动社会发展的关键力量。从商业决策到社会治理&#xff0c;从医疗健康到金融服务&#xff0c;数据的价值日益凸显。然而&#xff0c;随着数据的大量收集和广泛使用&#xff0c;隐私保护问题也日益突出。如何在充分利用大…

LeetCode 2442:统计反转后的不同整数数量

目录 核心思想&#xff1a;数字的“拆分”与“重组” 分步拆解&#xff08;以输入 123 为例&#xff09; 关键操作详解 为什么能处理中间或末尾的0&#xff1f; 数学本质 总结 题目描述 解题思路 代码实现 代码解析 复杂度分析 示例演示 总结 核心思想&#xff1a;…

Python爬虫第3节-会话、Cookies及代理的基本原理

目录 一、会话和Cookies 1.1 静态网页和动态网页 1.2 无状态HTTP 1.3 常见误区 二、代理的基本原理 2.1 基本原理 2.2 代理的作用 2.3 爬虫代理 2.4 代理分类 2.5 常见代理设置 一、会话和Cookies 大家在浏览网站过程中&#xff0c;肯定经常遇到需要登录的场景。有些…

Flutter项目之登录注册功能实现

目录&#xff1a; 1、页面效果2、登录两种状态界面3、中间按钮部分4、广告区域5、最新资讯6、登录注册页联调6.1、网络请求工具类6.2、注册页联调6.3、登录问题分析6.4、本地缓存6.5、共享token6.6、登录页联调6.7、退出登录 1、页面效果 import package:flutter/material.dart…

木马学习记录

一句话木马是什么 一句话木马就是仅需要一行代码的木马&#xff0c;很简短且简单&#xff0c;木马的函数将会执行我们发送的命令 如何发送命令&#xff06;发送的命令如何执行? 有三种方式&#xff1a;GET&#xff0c;POST&#xff0c;COOKIE&#xff0c;一句话木马中用$_G…

(C语言)单链表(1.0)(单链表教程)(数据结构,指针)

目录 1. 什么是单链表&#xff1f; 2. 单链表的代码表示 3. 单链表的基本操作 3.1 初始化链表 3.2 插入结点&#xff08;头插法&#xff09; 3.3 插入结点&#xff08;尾插法&#xff09; 3.4 遍历链表 4. 单链表的优缺点 代码&#xff1a;*L(LinkList)malloc(sizeof(…

Sentinel-自定义资源实现流控和异常处理

目录 使用SphU的API实现自定义资源 BlockException 使用SentinelResource注解定义资源 SentinelResourceAspect 使用Sentinel实现限流降级等效果通常需要先把需要保护的资源定义好&#xff0c;之后再基于定义好的资源为其配置限流降级等规则。 Sentinel对于主流框架&#…

Linux信号处理解析:从入门到实战

Linux信号处理全解析&#xff1a;从入门到实战 一、初识Linux信号&#xff1a;系统级的"紧急电话" 信号是什么&#xff1f; 信号是Linux系统中进程间通信的"紧急通知"&#xff0c;如同现实中的交通信号灯。当用户按下CtrlC&#xff08;产生SIGINT信号&…

Java的Selenium的特殊元素操作与定位之select下拉框

如果页面元素是一个下拉框&#xff0c;我们可以将此web元素封装为Select对象 Select selectnew Select(WebElement element); Select对象常用api select.getOptions();//获取所有选项select.selectBylndex(index);//根据索引选中对应的元素select.selectByValue(value);//选…

蓝桥云客 刷题统计

刷题统计 问题描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目&#xff0c;周六和周日每天做 b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几天实现做题数大于等于 n 题&#xff1f; 输入格式 输入一行包含三个整数 a, b 和 …

三防笔记本有什么用 | 三防笔记本有什么特别

在现代社会&#xff0c;随着科技的不断进步&#xff0c;笔记本电脑已经成为人们工作和生活的重要工具。然而&#xff0c;在一些特殊的工作环境和极端条件下&#xff0c;普通笔记本电脑往往难以满足需求。这时&#xff0c;三防笔记本以其独特的设计和卓越的性能&#xff0c;成为…

智能体和RPA都需要程序思维,如何使用影刀的变量?

欢迎来到涛涛聊AI&#xff0c; 不管AI还是RPA&#xff0c;都需要用到编程思想才能完成批量工作。今天研究了下影刀的变量。 变量类型 根据变量值选择相应的类型&#xff0c;可选择任意一种影刀所支持的数据类型 变量值 指定变量中保存的值&#xff0c;会根据不同的类型设置…

【蓝桥杯】算法笔记3

1. 最长上升子序列(LIS) 1.1. 题目 想象你有一排数字,比如:3, 1, 2, 1, 8, 5, 6 你要从中挑出一些数字,这些数字要满足两个条件: 你挑的数字的顺序要和原来序列中的顺序一致(不能打乱顺序) 你挑的数字要一个比一个大(严格递增) 问:最多能挑出多少个这样的数字? …

性能测试之jmeter的基本使用

简介 Jmeter是Apache的开源项目&#xff0c;基于Java开发&#xff0c;主要用于进行压力测试。 优点&#xff1a;开源免费、支持多协议、轻量级、功能强大 官网&#xff1a;https://jmeter.apache.org/index.html 安装 安装步骤&#xff1a; 下载&#xff1a;进入jmeter的…

【NLP 面经 7、常见transformer面试题】

目录 1. 为何使用多头注意力机制&#xff1f; 2. Q和K使用不同权重矩阵的原因 3. 选择点乘而非加法的原因 4. Attention进行scaled的原因 5. 对padding做mask操作 6. 多头注意力降维原因 7. Transformer Encoder模块简介 8. 乘以embedding size的开方的意义 9. 位置编码 10. 其…