【小程序】如何手动绘制分享用的图片

news2024/11/20 18:46:14

上一篇【小程序】如何实现滑动翻页中介绍了如何在小程序中实现上下滑动翻页的效果。

如果要给这个产品增加一个生成图片用于分享到朋友圈的功能,又该如何实现呢?

先来看一下最终的效果图:

首先,新建一个页面(page),布局文件如下:

<!-- pages/biases/share.wxml -->
<view class="container">
  <canvas id="myCanvas" type="2d" />
  <view class="save-btn" bind:tap="onSavePicture">
    <text>Save to Album</text>
  </view>
</view>

这里 id=myCanvascanvas 就是我们需要绘制图片用的画布。

它的 CSS 属性如下:

#myCanvas {
  margin: 40rpx 0;
  width: calc(100% - 80rpx);
  height: 80%;
  box-shadow: 2px 2px 16px 4px rgba(0, 0, 0, 0.2);
}
CSS 属性含义
margin: 40rpx 0;设置元素上下的边距为40rpx(rpx是微信小程序中的响应式像素单位),左右边距为0
width: calc(100% - 80rpx);将元素的宽度设置为其父容器宽度减去80rpx
height: 80%;将元素的高度设置为其父容器高度的80%
box-shadow: 2px 2px 16px 4px rgba(0, 0, 0, 0.2);给元素应用一个阴影效果,水平偏移量为2px,垂直偏移量为2px,模糊半径为16px,扩展半径为4px,颜色为rgba(0, 0, 0, 0.2)(表示稍微透明的黑色)

布局和样式都非常简单,难点是用 js 代码绘制图片,由于篇幅有限,这里只分享技术要点,完整版代码可参考 => pages/biases/share.js

获取 canvas 实例

const query = wx.createSelectorQuery()
query.select('#myCanvas')
  .fields({ node: true, size: true })
  .exec(res => { 
    //... 
  })

这段代码是微信小程序中使用 wx.createSelectorQuery 来选取ID为 myCanvas 的元素,并获取其节点信息和尺寸的示例代码。

具体解释如下:

  • const query = wx.createSelectorQuery(): 创建一个选择器查询实例。
  • query.select('#myCanvas'): 通过选择器 #myCanvas 选取ID为 myCanvas 的元素。
  • .fields({ node: true, size: true }):指定要获取的字段,包括节点信息和尺寸信息。
  • .exec(res => { ... }):执行查询操作,并在回调函数中处理查询结果。查询结果会作为参数 res 传递给回调函数。

然后我们可以根据实际需求,在回调函数中对获取的节点信息和尺寸进行处理,例如进行布局调整或其他操作。

其中 res 是一个数组,其中包含了查询结果的信息。

在执行 query.exec() 方法后,查询结果会作为一个数组传递给回调函数。数组的每个元素对应一个查询操作的结果,如果在 query.select() 中指定了多个选择器,那么数组中就会有多个元素。

每个元素都包含了指定字段的查询结果,可以通过索引来访问每个结果。例如,如果只有一个选择器查询的结果,可以通过 res[0] 来访问。

以上示例代码中,res 数组中只有一个元素,即 res[0],该元素包含了 #myCanvas 元素的节点信息和尺寸信息。我们可以通过 res[0].node 来访问节点信息,通过 res[0].widthres[0].height 来访问宽度和高度的尺寸信息。

要注意,如果在 query.select() 中指定了多个选择器,那么数组中会有多个元素,每个元素对应一个选择器的查询结果。你可以根据自己的需求使用循环或条件语句来处理多个查询结果。

继续看代码,我们在 exec 的回调函数中,保存 canvas 和它的尺寸信息,因为后面会用到。

const canvas = res[0].node
const width = res[0].width
const height = res[0].height
this.canvas = canvas

然后通过 canvas.getContext('2d') 来获取 2D 绘图上下文对象。这个上下文对象(通常称为 ctx)提供了一系列方法和属性,用于在 Canvas 上进行绘图和图形操作。

const ctx = canvas.getContext('2d')

这里需要指出的是,wx.createSelectorQuery() 返回的 size 信息是基于逻辑尺寸的,而不是物理尺寸

也就是说,size 属性表示节点的尺寸信息,但它是相对于小程序逻辑尺寸而言的,而非设备的物理尺寸。

小程序的逻辑尺寸是指开发者在编写小程序时使用的尺寸单位,通常是以 rpx(响应式像素)为单位。而物理尺寸则是指设备的实际像素尺寸,通常以设备的物理像素为单位(如像素)。

因此,在使用 wx.createSelectorQuery() 获取节点的尺寸信息时,需要根据实际场景进行适当的转换,例如根据设备的像素比 pixelRatio 进行逻辑尺寸到物理尺寸的转换,以便正确地进行绘制或布局操作。

注意:在使用 size 属性时,需要考虑到小程序的视口、屏幕方向以及可能的样式布局等因素,以确保得到准确的尺寸信息。

const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = width * dpr
canvas.height = height * dpr
ctx.scale(dpr, dpr)

通过以上代码,我们首先计算出 canvas 的物理尺寸并赋值给 canvas,然后通过调用 ctx.scale(dpr, dpr) 修改画布的分辨率。

这样一来,我们后续的绘制可以基于逻辑像素(rpx)来计算尺寸。

逻辑像素和物理像素的转换关系可以用下面这个公式来计算:

rpx = dpr * px

画布准备就绪之后,就可以进行绘制了。

绘制矩形

绘制一个白色的矩形,并将其填充整个 Canvas 区域,用作背景色。

ctx.fillStyle = 'white'
ctx.fillRect(0, 0, width, height)

绘制多行文本

绘制单行文本很简单,多行文本稍微复杂一些,因为 canvas 并不支持自动换行,我们只有手动换行。

首先设置基础样式:

ctx.fillStyle = 'black'
ctx.font = 'bold 30px sans-serif'

然后将长文本按照指定的最大宽度进行分行处理。

我将这个功能封装成了一个小函数:

_splitTextToLines(ctx, maxWidth, text) {
  let words = text.split(' ');
  let totalWidth = 0;
  let lineIndex = 0;
  let lineWords = [];
  const lines = [];
  words.forEach(word => {
    const wordWidth = ctx.measureText(word).width;
    totalWidth = totalWidth + wordWidth;
    if (totalWidth >= maxWidth) {
      totalWidth = 0;
      lines[lineIndex] = lineWords;
      lineIndex++;
      lineWords = [word];
    } else {
      lineWords.push(word);
    }
  });
  if (lineWords.length > 0) {
    lines[lineIndex] = lineWords;
  }
  return lines;
}

以上代码定义了一个名为 _splitTextToLines 的函数,接受三个参数:

  • ctx 是绘图上下文对象
  • maxWidth 是指定的最大宽度
  • text 是待分行处理的文本。

在函数内部,首先将文本按照空格进行分割,得到一个单词数组 words。然后,定义了一些变量用于跟踪总宽度、行索引和当前行的单词。

接下来,通过遍历单词数组,使用 ctx.measureText(word).width 获取每个单词的宽度,并累加到总宽度 totalWidth 上。

如果累加的总宽度超过了最大宽度 maxWidth,则表示当前行已满,需要将当前行的单词数组 lineWords 添加到结果数组 lines 中,并重置总宽度和当前行的单词数组。

如果累加的总宽度未超过最大宽度,则将当前单词添加到当前行的单词数组 lineWords 中。

遍历结束后,如果当前行的单词数组 lineWords 不为空,则将其添加到结果数组 lines 中。

最后,返回包含分行结果的数组 lines

这段代码的作用是将给定的文本按照最大宽度进行分行处理,确保每行的宽度不超过最大宽度,并返回分行结果。

然后我们就可以绘制返回的多行文本:

_drawLines(ctx, lines, yOffset, canvasWidth, lineHeight) {
  let height = 0;
  lines.forEach((line, idx) => {
    let text = line.join(' ');
    let textWidth = ctx.measureText(text).width;
    height = yOffset + lineHeight * idx;
    ctx.fillText(text, parseInt((canvasWidth - textWidth) / 2), height);
  });
  return lines;
}

这段代码定义了一个名为 _drawLines 的函数,接受五个参数:

  • ctx 是绘图上下文对象
  • lines 是包含分行文本的数组
  • yOffset 是垂直偏移量
  • canvasWidth 是画布宽度
  • lineHeight 是行高。

在函数内部,首先定义了一个变量 height,用于跟踪每行文本的垂直位置。

然后,通过遍历 lines 数组,对于每一行文本,将单词数组 line 使用空格连接成一个字符串 text

使用 ctx.measureText(text).width 获取字符串 text 的宽度,以便进行水平居中。

计算当前行的垂直位置,使用公式 height = yOffset + lineHeight * idx

最后,使用 ctx.fillText 在画布上绘制文本,传入文本字符串 text、水平位置 parseInt((canvasWidth - textWidth) / 2) 和垂直位置 height

遍历结束后,返回分行文本的数组 lines

这段代码的作用是在画布上绘制多行文本,确保每行文本居中显示,并返回分行文本的数组。

OK,我们将以上代码串接起来,就实现了绘制多行文本的功能:

// Draw the title
ctx.fillStyle = 'black'
ctx.font = 'bold 30px sans-serif'
const titleLines = this._splitTextToLines(ctx, width - 100, title)
this._drawLines(ctx, titleLines, y, width, 35)

绘制图片

以下代码的作用是在画布上绘制一个二维码图片,并确保图片加载完成后进行绘制操作。

let qrImg = canvas.createImage();
const qrImgSrc = `../../images/qrcode.jpg`;
const qrImgWidth = qrCodeImgSize;
const qrImgHeight = qrCodeImgSize;
qrImg.src = qrImgSrc;
qrImg.onload = () => {
  ctx.drawImage(qrImg, horizontalMargin, height - qrCodeImgSize - verticalMargin, qrImgWidth, qrImgWidth);
};

代码解释如下:

首先,通过 canvas.createImage() 创建了一个图片对象 qrImg

定义了变量 qrImgSrc 存储二维码图片的路径。

定义了变量 qrImgWidthqrImgHeight 来指定绘制的二维码图片的宽度和高度,其中 qrCodeImgSize 是二维码图片的大小。

qrImg.src 设置为二维码图片的路径,以加载图片。

使用 qrImg.onload 事件处理程序,在图片加载完成后执行绘制操作。

qrImg.onload 事件处理程序中,使用 ctx.drawImage 方法在画布上绘制二维码图片。参数依次为:

  • 绘制的图片对象 qrImg
  • 绘制的水平位置 horizontalMargin
  • 绘制的垂直位置 height - qrCodeImgSize - verticalMargin
  • 绘制的宽度 qrImgWidth
  • 绘制的高度 qrImgWidth

将绘制结果保存为图片

首先,保存画布内容为临时文件:

onSavePicture() {
  let that = this
  setTimeout(() => {
    wx.canvasToTempFilePath({
      canvas: this.canvas,
      success: res => {
        const tempFilePath = res.tempFilePath
        that.shareImage(tempFilePath)
      },
      fail: res => {
        console.log(res)
      }
    })
  }, 100)
},

在函数内部,使用 setTimeout 延迟执行一段代码。这里延迟了 100 毫秒执行。

在延迟执行的代码块中,调用了微信小程序的 wx.canvasToTempFilePath 方法。该方法将画布内容转换为临时文件。

通过 canvas: this.canvas 指定要转换的画布对象。

在转换成功时,通过 success 回调函数获取到转换后的临时文件路径 res.tempFilePath

然后调用 that.shareImage(tempFilePath) 将临时文件路径传递给 shareImage 函数进行处理。

在转换失败时,通过 fail 回调函数打印错误信息到控制台。

这段代码的作用是在一定延时后将当前画布内容转换为临时文件,并将临时文件路径传递给 shareImage 函数进行处理。

async shareImage(url) {
  let that = this
  // 验证用户是否拥有保存到相册的权限
  wx.getSetting({
    success: res => {
      if (res.authSetting['scope.writePhotosAlbum']) {
        // 用户已授权
        that.saveImage(url);
      } else if (res.authSetting['scope.writePhotosAlbum'] !== undefined) {
        // 用户首次进入还未调起权限相关接口
        that.openSetting();
      } else {
        // 用户首次进入
        that.saveImage(url);
      }
    },
    fail: () => {
      wx.showModal({ content: '获取授权信息失败' })
    }
  })
},

shareImage 是一个异步函数,在函数内部,通过 wx.getSetting 方法验证用户是否拥有保存到相册的权限。

getSetting 方法的 success 回调函数中,根据用户的授权情况进行不同的处理。

如果用户已经授权保存到相册,即 res.authSetting['scope.writePhotosAlbum'] 返回 true,则调用 that.saveImage(url) 方法保存图片。

如果 res.authSetting['scope.writePhotosAlbum'] 不为 undefined,表示用户首次进入小程序还未调起权限相关接口,此时调用 that.openSetting() 方法引导用户打开权限设置页面。

如果以上两种情况都不满足,表示用户首次进入小程序,直接调用 that.saveImage(url) 方法保存图片。

getSetting 方法的 fail 回调函数中,如果获取授权信息失败,则通过 wx.showModal 方法显示一个模态框提示用户。

这段代码的作用是验证用户的保存到相册权限,并根据授权情况进行不同的处理。

如果权限获取成功,则将图片保存到相册:

// 保存图片
saveImage(url) {
  let that = this;
  wx.showLoading({ title: 'Saving...' });  // 显示加载提示框
  wx.saveImageToPhotosAlbum({
    filePath: url,  // 要保存的图片路径
    success: (res) => {
      wx.hideLoading();  // 隐藏加载提示框
      wx.showToast({ icon: 'success', title: 'Saved' });  // 显示保存成功的提示
    },
    fail: (res) => {
      wx.hideLoading();  // 隐藏加载提示框
      wx.showModal({ content: `Failed: ${JSON.stringify(res)}` });  // 显示保存失败的提示,并打印错误信息
    }
  });
},

这段代码的作用是在保存图片到相册过程中显示加载提示框,保存成功后显示一个成功的提示,保存失败后显示一个失败的提示,并打印错误信息。

  • 首先,使用 wx.showLoading 方法显示一个加载提示框,标题为 “Saving…”,提示用户正在保存图片。
  • 然后,使用 wx.saveImageToPhotosAlbum 方法保存图片到相册,传入要保存的图片路径 url
  • success 回调函数中,表示保存图片成功,隐藏加载提示框,并使用 wx.showToast 方法显示一个成功的提示,图标为 ‘success’,标题为 ‘Saved’。
  • fail 回调函数中,表示保存图片失败,隐藏加载提示框,并使用 wx.showModal 方法显示一个模态框,其中的内容是保存失败的提示,并将错误信息以字符串的形式进行显示。

以上就是用 Cavnas 绘制分享图片的核心代码,希望对你有所帮助。

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

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

相关文章

vue+elementui实现英雄联盟道具城

目录 一、效果图 1.首页 2.商品列表、分类 二、实现重点讲解 1.首页轮播图 1.1技术实现&#xff1a; 1.2.鼠标聚焦切换图片事件 2.首页tab切换 3.商品列表实现 三、项目结构说明 四、总结 一、效果图 1.首页 项目与官方效果没有太大差异&#xff1a; 游戏导航&#xff1…

windows上VMware虚拟机彻底卸载详细教程

VMware虚拟机彻底卸载 一、彻底卸载过程1.1 停止VMware服务1.2 结束vmware任务1.3 开始卸载VMware1.4 删除注册表信息1.5 删除安装目录 二、vmware 安装教程三、vmware 使用教程 回到目录   回到末尾 一、彻底卸载过程 卸载之前&#xff0c;需要先关闭VMware相关的后台服务…

高速公路智慧稽核常用技术及发展方向浅析

交通运输部数据显示&#xff0c;截至2021年末&#xff0c;全国收费公路里程达18.76万公里&#xff0c;其中高速公路16.12万公里&#xff0c;占比高达85.9%&#xff0c;高速公路费用收缴的重要性尤为凸显。 收费系统作为高速公路的三大机电系统之一&#xff0c;在高速费用的收取…

【Java面试题】框架篇——Spring

文章目录 什么是Spring框架&#xff1f;Spring框架有哪些主要模块&#xff1f;Spring有几种配置方式&#xff1f;Spring框架中的单例Beans是线程安全的么&#xff1f;Spring 框架中都用到了哪些设计模式&#xff1f;★★★Spring AOP在实际项目中的应用★★★阐述一下Bean的生命…

使用 Transformers 为多语种语音识别任务微调 Whisper 模型

本文提供了一个使用 Hugging Face &#x1f917; Transformers 在任意多语种语音识别 (ASR) 数据集上微调 Whisper 的分步指南。同时&#xff0c;我们还深入解释了 Whisper 模型、Common Voice 数据集以及微调等理论知识&#xff0c;并提供了数据准备和微调的相关代码。如果你想…

django-vue-admin 运行记录

django-vue-admin 运行记录 1. 安装 ubuntu-20.04.6 桌面版 ubuntu-20.04.6-desktop-amd64.iso 桌面版本 桌面版的目的是 有浏览器可以看 django vue 的localhost网页。 用server版&#xff0c;需要用别的机器看&#xff0c;别的机器在权限上可能有问题。 sudo apt install …

ChatGLM2-6B-Int4本地部署

原文链接&#xff1a;http://wangguo.site/posts/9d8c1768.html ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本 GitHub地址&#xff1a;https://github.com/THUDM/ChatGLM2-6B 1、先看效果 2、本地部署 部署环境 wsl2-ubuntu22.04 LTS-----------------------…

计网简答题

答案不保证正确性&#xff0c;仅供参考。 1.有如图所示的以太网&#xff0c;每个交换机的名字及接口号、主机的名字及MAC地址都标明在图中。网络初启动时&#xff0c;两个交换机的转发表都为空&#xff0c;接着先后进行以下MAC帧传输&#xff1a;H1→H5&#xff0c;H3→H2&…

PG系列5:PG体系结构

文章目录 一. PG体系结构1.1 PG的体系结构概述1.2 PostgreSQL进程概述 二. PG内存结构三. PostgreSQL进程3.1 后台进程3.2 后端进程(backend)或服务器进程3.3 用户进程或客户端进程3.4 数据库服务器启动流程 四. PG逻辑结构4.1 PostgreSQL cluster4.2 database和cluster的关系4…

DevExpress WPF Scheduler组件,快速构建性能优异的调度管理器!(上)

无论您在WPF项目中是需要Outlook样式的调度程序&#xff0c;还是需要时间表或议程视图来向最终用户展示信息&#xff0c;DevExpress WPF Scheduler都提供了数十个选项&#xff0c;如集成的日程对话框等&#xff0c;因此用户可以快速构建下一个伟大的调度管理器。 DevExpress W…

抖音本地生活团购服务商

抖音本地生活团购服务商市场前景非常广阔。随着移动互联网的普及和人们对本地生活服务需求的增加&#xff0c;本地生活团购行业已成为一个快速增长的市场。而抖音平台拥有庞大的用户基础和强大的社交媒体传播力&#xff0c;为本地生活团购服务商提供了巨大的发展机遇。 抖音…

刷题日记《链表02》

题目描述 给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 可以假设除了数字 0 之外&#xff0c;这两个数字都不会以零开头。 解题思路 面对这种求和相加的题目&#xff0c;不知道…

windows 下安装 mysql-8.0.25 解压版

介绍 此文介绍 mysql-8.0.25-winx64 的 zip 解压版&#xff0c;在 windows 下的安装与配置过程。 官方下载 官网下载页&#xff1a; https://downloads.mysql.com/archives/community/ 进入官网&#xff0c;选择默认版本就行&#xff0c;不需要包含测试工具套件的版本 本地解…

C++ day40

1、思维导图 2、定义一个命名空间Myspace&#xff0c;包含以下函数&#xff1a;将一个字符串中的所有单词进行反转&#xff0c;并输出反转后的结果。例如&#xff0c;输入字符串为"Hello World"&#xff0c;输出结果为"olleH dlroW"&#xff0c;并在主函数…

0基础入门---第四章---误差反向传播法

&#x1f31e;欢迎来到深度学习的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f64f;作者水平很有限&#xff0c;如果发现错误&#xff…

Java性能权威指南-总结19

Java性能权威指南-总结19 Java EE性能调优JVM线程调优调节线程栈大小偏向锁自旋锁线程优先级 小结 Java EE性能调优Web容器的基本性能 Java EE性能调优 JVM线程调优 JVM的某些调优策略可以影响线程和同步的性能。 调节线程栈大小 当空间非常珍贵时&#xff0c;可以调节线程…

Day6——Web安全基础

网络安全学习笔记Day6 Web安全基础 一.Web简介什么是Web&#xff1f;什么是因特网&#xff1f;互联网&#xff0c;因特网&#xff0c;万维网的关系万维网构想的诞生http协议URL 二.Web发展史Web1.0Web2.01.0与2.0的区别Web1.0的安全漏洞Web2.0的安全漏洞 三.杂项门户网站静态页…

戴尔笔记本如何用U盘重装Win10系统?

戴尔笔记本如何用U盘重装Win10系统&#xff1f;很多使用戴尔笔记本的用户&#xff0c;都想知道如何用U盘来重装Win10系统&#xff0c;用户首先要确认自己的戴尔笔记本电脑能不能联网&#xff0c;然后再准备一个8G以上的U盘&#xff0c;最后根据小编分享的戴尔笔记本用U盘重装Wi…

Springboot Mybatis 不存在插入数据,存在则更新数据

前言 是不是经常看到代码&#xff0c; 查一下数据库&#xff0c;如果存在数据&#xff0c;就做更新语句调用&#xff1b; 如果不存在&#xff0c;就插入。 今天该篇介绍的 是使用 INSERT INTO ON DUPLICATE KEY UPDATE 来实现我们上述的场景&#xff0c; 不需要…

不能真“生成代码”的“低代码”平台,不可能获得程序员的认可

目录 前言 思考 解决问题 基本现状 发现亮点 前言 >前几天我和一个好友聊天的时候&#xff0c;他是这么评价低代码平台的&#xff1a;“想证明程序员都是傻X&#xff0c;又想让程序员买单&#xff01;程序员本身心里就不爽... ” 那么&#xff0c;低代码发展势头迅猛的…