【微信小程序-原生开发】实用教程20 - 生成海报(实战范例为生成活动海报,内含生成指定页面的小程序二维码,保存图片到手机,canvas 系列教程)

news2024/12/23 3:12:51

可在系列教程的基础上继续开发,也可以单独使用

【微信小程序-原生开发】系列教程

效果预览

在这里插入图片描述

代码实现

点击触发生成海报

在活动详情页,指定点击某图标/按钮,触发跳转到生成海报的页面

pages\components\party\detail\index.js

  getPoster() {
    let detail = this.data.detail
    wx.navigateTo({
      url: '/pages/components/poster/index',
      success: function (res) {
        // 跳转页面时,将活动详情传递过去
        res.eventChannel.emit('sendData', {
          data: detail
        })
      }
    })
  },

海报页加载时接收参数,开始生成海报

pages\components\poster\index.js

  onLoad() {
    let that = this
    // 接收列表页传入的复杂数据--对象(详情)
    const eventChannel = this.getOpenerEventChannel()
    eventChannel.on('sendData', function (res) {
      that.setData({
        detail: res.data
      })
      wx.showLoading({
        title: '生成中',
      })
      //开始生成海报
      that.getPoster()
    })
  },

海报的绘制过程

海报通过Canvas进行绘制

1. 获取系统屏幕宽高,确定画布的宽高

  // 生成海报
  getPoster() {
    let that = this
    // 获取屏幕宽高
    wx.getSystemInfo({
      success(res) {
        that.drawCanvas(res.windowWidth, res.windowHeight)
      }
    })
  },

2. 创建Canvas对象,进行绘制,并生成图片

详见代码中的注释

<canvas class="canvasClass" id="mycanvas" type="2d" />
Page {
  background-color: black;
}

.canvasClass {
  width: 80%;
  height: 80vh;
  margin: 40rpx auto
}
  // 绘制海报
  drawCanvas(windowWidth, windowHeight) {
    let that = this;
    let detail = this.data.detail
    // 根据id查找到页面中的 canvas 标签,生成 Canvas 对象
    wx.createSelectorQuery()
    // # 后为页面中的 canvas 标签的 id 属性
      .select('#mycanvas')
      .fields({
        node: true,
        size: true
      })
      .exec(async (res) => {
        // 获取到 Canvas 对象
        const canvas = res[0].node
        // 渲染上下文
        const ctx = canvas.getContext('2d')

        // Canvas 画布的实际绘制宽高
        const width = windowWidth
        const height = windowHeight

        // 获取设备像素比
        const dpr = wx.getWindowInfo().pixelRatio

        // 清空画布
        ctx.clearRect(0, 0, width, height)

        // 初始化画布大小
        canvas.width = width * dpr
        canvas.height = height * dpr
        ctx.scale(dpr, dpr)

        // 绘制画布底色
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // 解析活动封面图片
        let posterImgURL = detail.poster
        // 获取源图片宽高
        let posterImgInfo = await that.getImgInfo(posterImgURL)
        // 计算源图片的宽高比
        let posterImgRate = posterImgInfo.width / posterImgInfo.height
        // 计算出新的图片宽高(此处以宽为基准,按80%的宽等比例缩放图片)
        let newWidth = 0.8 * width
        let newHeight = 0.8 * width / posterImgRate

        // 绘制文本 -- 水平居中(活动名称)
        let text1 = detail.title;
        ctx.fillStyle = "black";
        ctx.font = "bold 25px 微软雅黑";
        let y1 = width / 10 + newHeight + 50
        ctx.fillText(text1, that.getTextxPosition(ctx, width, text1), y1);

        // 绘制文本 -- 水平居中(活动时间)
        let text2 = detail.date + ' ' + detail.week + ' ' + detail.time
        ctx.font = "15px 微软雅黑";
        let y2 = y1 + 30
        ctx.fillText(text2, that.getTextxPosition(ctx, width, text2), y2);

        // 绘制文本 -- 水平居中(活动地点)
        let text3 = detail.placeMark || detail.placeInfo.title
        ctx.font = "15px 微软雅黑";
        let y3 = y2 + 30
        ctx.fillText(text3, that.getTextxPosition(ctx, width, text3), y3);

        // 绘制文本 -- 水平居中
        let codeTips = "长按识别二维码报名";
        ctx.font = "20px bold";
        ctx.fillStyle = "green";
        let y4 = y3 + 40
        ctx.fillText(codeTips, that.getTextxPosition(ctx, width, codeTips), y4);

        // 绘制图片--活动封面图片
        let posterImg = canvas.createImage()
        posterImg.src = posterImgURL
        posterImg.onload = async () => {
          ctx.drawImage(posterImg, (width - newWidth) / 2, width / 10, newWidth, newHeight)

          // 绘制图片--小程序二维码
          const codeImg = canvas.createImage()
          // 异步生成小程序二维码
          codeImg.src = await that.initCodeImg()
          codeImg.onload = () => {
            let y5 = y4 + 20
            ctx.drawImage(codeImg, width / 3, y5, width / 3, width / 3)

            // 生成图片
            wx.canvasToTempFilePath({
              canvas,
              success: res => {
                wx.hideLoading()
                that.setData({
                  // 生成的图片临时文件路径
                  tempFilePath: res.tempFilePath
                })
              },
            })
          }
        }
      })
  },

技术要点一:画布的最终大小需要按设备像素比进行转换

        // Canvas 画布的实际绘制宽高
        const width = windowWidth
        const height = windowHeight

        // 获取设备像素比
        const dpr = wx.getWindowInfo().pixelRatio

        // 初始化画布大小
        canvas.width = width * dpr
        canvas.height = height * dpr
        ctx.scale(dpr, dpr)

技术要点二:需先清空画布后绘制底色

  • 为避免多次渲染导致累积层叠绘制,每次都需清空画布
  • 若未绘制底色,则最终保存的海报会是透明的(若想生成背景透明的海报,请删除绘制画布底色的代码)
        // 清空画布
        ctx.clearRect(0, 0, width, height)

        // 绘制画布底色
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

技术要点三:保持图片的宽高比

        // 解析活动封面图片
        let posterImgURL = detail.poster
        // 获取源图片宽高
        let posterImgInfo = await that.getImgInfo(posterImgURL)
        // 计算源图片的宽高比
        let posterImgRate = posterImgInfo.width / posterImgInfo.height
        // 计算出新的图片宽高(此处以宽为基准,按80%的宽等比例缩放图片)
        let newWidth = 0.8 * width
        let newHeight = 0.8 * width / posterImgRate

此处需要异步解析图片,获取源图片的宽高,来计算宽高比

  // 获取图片信息
  getImgInfo(imgURL) {
    return new Promise((reslove) => {
      wx.getImageInfo({
        src: imgURL,
        success(res) {
          reslove(res);
        }
      })
    })
  },

通过返回 Promise ,方便借助 async和 await 将异步变同步,避免过多的代码嵌套。

技术要点四:绘制水平居中的文字

        // 绘制文本 -- 水平居中(活动名称)
        let text1 = detail.title;
        ctx.fillStyle = "black";
        ctx.font = "bold 25px 微软雅黑";
        let y1 = width / 10 + newHeight + 50
        ctx.fillText(text1, that.getTextxPosition(ctx, width, text1), y1);
  • fillText 的参数依次为:文本内容、文本的 x 坐标,文本的 y 坐标
  • 水平居中的 x 坐标计算方法为:(画布宽度-文本宽度)/ 2
  // 获取居中文本的x坐标
  getTextxPosition(ctx, ctxWidth, text) {
    let textWidth = ctx.measureText(text).width;
    let xPosition = ctxWidth / 2 - textWidth / 2;
    return xPosition
  },

技术要点五:绘制图片

        // 绘制图片--活动封面图片
        let posterImg = canvas.createImage()
        posterImg.src = posterImgURL
        posterImg.onload = () => {
          ctx.drawImage(posterImg, (width - newWidth) / 2, width / 10, newWidth, newHeight)
        }
  • 图片若是网络图片,则需等其异步加载完成后再绘制,所以后续生成图片等操作,都需在图片的 onload 函数中执行

技术要点六:生成小程序二维码

  // 生成小程序二维码
  async initCodeImg() {
    let {
      codeImgURL
    } = this.data

    if (!codeImgURL) {
      // 生成小程序二维码
      let codeImgID = await this.getCodeImg()
      // 云存储的图片,需要通过云id去换取外网访问链接
      let result = await wx.cloud.getTempFileURL({
        fileList: [codeImgID]
      })
      codeImgURL = result.fileList[0].tempFileURL
    }

    return codeImgURL
  },
  // 通过云函数,生成小程序二维码
  getCodeImg() {
    let detail = this.data.detail
    let params = detail._id
    return new Promise((resolve) => {
      wx.cloud.callFunction({
        name: 'get_codeImg', // 云函数的名称
        data: {
          // 页面路径
          path: 'pages/components/party/detail/index',
          // 参数
          params: params
        }
      }).then(res => {
        // 从云函数返回的结果中提取出目标数据
        resolve(res.result.fileID);
      })
    })
  },

云函数–生成小程序二维码

此处自定义的云函数名称为 get_codeImg

cloudfunctions\get_codeImg\index.js

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
}) // 使用当前云环境

// 云函数入口函数
exports.main = async (event, context) => {
  const wxacodeResult = await cloud.openapi.wxacode.getUnlimited({
    // 页面路径
    page: event.path,
    // 参数
    scene: event.params,
  })
  let suffix = event.path.replace(/\//g, '_') + '_' + event.params
  // 在云存储生成图片
  const uploadResult = await cloud.uploadFile({
    // 图片存储路径
    cloudPath: `codeImgs/wxacode_${suffix}.jpg`,
    fileContent: wxacodeResult.buffer,
  })
  return uploadResult
}
  • 此处需注意,因参数通过 scene 场景值传递(且scene限定了长度,刚好和默认的 _id 值等长),在目标页面需添加代码识别该参数
  onLoad(options) {
    // 通过id获取详情(扫描小程序二维码时,id在scene里 )
    let id = options.id || options.scene
    if (id) {
      this.setData({
        id: id
      })
      this.getDetail()
      return
    }
}

技术要点七:将canvas转换为图片

此步可在 canvas 绘制后执行,也可以在点击保存海报时在执行

            // 生成图片
            wx.canvasToTempFilePath({
              canvas,
              success: res => {
                that.setData({
                  // 生成的图片临时文件路径
                  tempFilePath: res.tempFilePath
                })
              },
            })

保存图片到手机

点击保存按钮时执行

在这里插入图片描述

<view class="btnBanner">
  <t-button style="margin-right: 40rpx;" block theme="light" capture-bind:tap="cancel" size="medium">取消</t-button>
  <t-button block capture-bind:tap="saveImg" theme="primary" size="medium">保存</t-button>
</view>
.btnBanner {
  padding-top: 20rpx;
  display: flex;
  justify-content: space-evenly;
  margin: 0rpx 10%;
}
  //保存到手机相册
  saveImg() {
    wx.saveImageToPhotosAlbum({
      filePath: this.data.tempFilePath,
      success(res) {
        wx.showToast({
          title: '已保存到相册',
          icon: 'success',
          duration: 3000
        })
      }
    })
  },
  // 取消
  cancel() {
    wx.navigateBack()
  },

注意事项

  • 不同的海报,内容和布局不同,需要修改对应的 canvas 绘制代码

更多技巧

canvas 系列教程

微信小程序的 canvas 绘制已与 web 中 canvas 的绘制统一,相关的绘制技术,可参考

  • 01——直线、三角形、多边形、矩形、调色板_canvas调色板
    https://blog.csdn.net/weixin_41192489/article/details/124333306

  • 02——圆、弧线、圆角矩形、曲线(气泡、心形、N叶草)、扇形_canvas 半圆矩形
    https://blog.csdn.net/weixin_41192489/article/details/124339528

  • 03 —— 线的样式、绘制文本、操作图片(图片的渲染、缩放、裁剪、切割、平铺、特效)、变换元素(平移、缩放、旋转)_canvas putimagedata 缩放
    https://blog.csdn.net/weixin_41192489/article/details/124347589

  • 04 —— 渐变、阴影、路径、状态、Canvas对象、图形重叠模式_canvas strokerect
    https://blog.csdn.net/weixin_41192489/article/details/124375153

  • 05 ——交互、动画_canvas 交互
    https://blog.csdn.net/weixin_41192489/article/details/124388436

  • 06 ——边界检测、碰撞检测_canvas碰撞检测
    https://blog.csdn.net/weixin_41192489/article/details/124407221

  • 07 ——捕获、拖拽、抛掷、缓动动画、弹性动画_canvas管道动画
    https://blog.csdn.net/weixin_41192489/article/details/124415085

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

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

相关文章

OpenTex 企业内容管理平台

OpenText 企业内容管理平台 将内容服务与领先应用程序集成&#xff0c;弥合内容孤岛、加快信息流并扩大治理 什么是内容服务集成&#xff1f; 内容服务集成通过将内容管理平台与处于流程核心的独立应用程序和系统连接起来&#xff0c;支持并扩展了 ECM 的传统优势。 最好的内…

【通过Cpython3.9源码看看python字符串对象的创建】

CPython源码解析之PyUnicode_New函数实现 简介 PyUnicode_New是Python源码中用于创建Unicode字符串对象的函数&#xff0c;定义在UnicodeObject.c文件中。该函数接受一个长度参数size和最大字符值参数maxchar(根据传入的最大字符值 maxchar 确定新创建的字符串对象所需的存储…

百度工程师的软件质量与测试随笔

作者 | 百度移动生态质效工程师们 导读 在降本增效、以chatGPT为代表的大模型技术横空出世的背景下&#xff0c;对软件质量和软件测试的领域也带来了巨大冲击&#xff0c;也使得软件质量工作者开始变得焦虑&#xff0c;主要体现在&#xff1a;公司对软件质量从业者的不重视加剧…

SQL Server的执行计划(Execution Plans)

执行计划一、背景二、显示和保存执行计划三、显示估计的执行计划四、显示实际执行计划五、以 XML 格式保存执行计划六、比较和分析执行计划6.1、比较执行计划6.2、分析实际执行计划总结一、背景 为了能够执行查询&#xff0c;SQL Server 数据库引擎必须分析该语句&#xff0c;…

21100颗星的Locust性能测试工具到底有多牛!

一句话&#xff1a;用普通的Python编写可扩展的负载测试&#xff0c;就够了&#xff0c;懂得自然懂&#xff01; Locust是一个易于使用、可编写脚本和可扩展的性能测试工具。你在常规的Python代码中定义你的用户的行为&#xff0c;而不是受制于一个UI或领域特定的语言&#xff…

HTB-Obscurity

HTB-Obscurity信息收集8080端口立足www-data -> robertrobert -> rootsudo 注入hash捕获信息收集 8080端口 ”如果攻击者不知道你在使用什么软件&#xff0c;你就不会被黑客攻击!“&#xff0c;目标对web的指纹做了某些处理。 “‘SuperSecureServer.py’ in the secre…

【从零开始学Skynet】基础篇(六):MySql数据库安装操作

游戏服务端的另一项重要功能是保存玩家数据&#xff0c;Skynet提供了操作MySQL数据库、MongoDB数据库的模块。1、数据库安装 首先安装Mysql服务器&#xff0c;打开终端输入如下指令&#xff1a; sudo apt-get install mysql-server 按下回车&#xff0c;输入密码后开始安装&a…

秒杀架构(二) -- nginx实现限流

限流&#xff08;Rate Limitting&#xff09;是服务降级的一种方式&#xff0c;通过限制系统的输入和输出流量以达到保护系统的目的。比如我们的网站暴露在公网环境中&#xff0c;除了用户的正常访问&#xff0c;网络爬虫、恶意攻击或者大促等突发流量都可能都会对系统造成压力…

OpenCV按指定大小分割图像并保存详细讲解

这几天在忙着整理自己的数据集&#xff0c;使用工业级相机拍了好多高清照片&#xff0c;但是模型训练的时候需要使用512*512像素点大小的图像&#xff0c;而且我的模型设计的时候就已经规定好了训练样本大小。 那就分割呗&#xff0c;把拍的照片按512*512分割一小块一小块的&am…

easyx

普通的画线图什么的 首先我们需要安装一个easyx的图形库&#xff0c;然后把头文件搞出来 #include <stdio.h> #include <easyx.h>//easyx画线啥啥的图形库 #include <graphics.h> #include <math.h> #include <conio.h>//键盘操作的头文件 设…

2023年mathorcupD题航空安全风险分析和飞行技术评估思路分析

2023年mathorcupD题航空安全风险分析和飞行技术评估思路分析 飞行安全是民航运输业赖以生存和发展的基础。随着我国民航业的快 速发展&#xff0c;针对飞行安全问题的研究显得越来越重要。2022 年 3 月 21 日&#xff0c;“3.21” 空难的发生终结了中国民航安全飞行 1 亿零 59…

Android中级——性能优化

性能优化布局优化UI渲染机制避免Overdraw优化布局层级利用<include\>重用Layout使用<ViewStub\>实现View的延迟加载Hierarchy View内存优化获取内存信息ProfilerTraceViewMAT&#xff08;Memory Analyzer Tool&#xff09;dumpsys布局优化 UI渲染机制 画面流畅需…

透过Gartner最新报告,认识“超级边缘”

当下&#xff0c;酝酿能量的超级边缘。最近&#xff0c;我们在谈视频化狂飙、谈AIGC颠覆、谈算力动能不足&#xff0c;很少谈及边缘。但“边缘”恰恰与这一切相关&#xff0c;且越发密不可分&#xff0c;它是未来技术发展的极大影响因子。 “到2025年&#xff0c;超过70%的组织…

Segment Anything Model

论文翻译&#xff1a; 图1&#xff1a;我们旨在通过引入三个相互关联的组件来构建分割的基础模型&#xff1a;即时分割任务、支持数据注释并通过即时工程将零样本传输到一系列任务的分割模型&#xff08;SAM&#xff09;&#xff0c;以及用于收集SA-1B的数据引擎&#xff0c;SA…

MappingGenerator PRO 2023.3 Visual Studio 2019-2022

您的私人编码助手 MappingGenerator 最初是作为 AutoMapper 的设计时替代品创建的。现在它正在演变为编码助手&#xff0c;您可以将最平凡的编码任务委派给它&#xff1a; 生成映射生成显式转换实施克隆生成投影表达式脚手架方法调用脚手架对象创建清理方法调用方便ILogger的使…

探讨Hive是否转为MapReduce程序

目录 前提条件 数据准备 探讨HQL是否转为MapReduce程序执行 1.设置hive.fetch.task.conversionnone 2.设置hive.fetch.task.conversionminimal 3.设置hive.fetch.task.conversionmore 前提条件 Linux环境下安装好Hive&#xff0c;这里测试使用版本为&#xff1a;Hive2.3.…

【结构型模式】适配者模式

文章目录优秀借鉴1、简介2、结构3、实现方式3.1、案例引入3.2、类适配器3.3、对象适配器3.4、接口适配器4、区别对比5、适配者模式优缺点6、应用场景优秀借鉴 黑马程序员Java设计模式详解-适配器模式概述适配器设计模式&#xff08;封装器模式&#xff09;一文彻底弄懂适配器模…

页眉怎么添加【节】,设置不同章节不同页眉

文章目录前言添加【节】&#xff0c;设置不同内容总结前言 大家写文档或者论文的时候可能会需要&#xff1a;不同章节页眉展示不同的内容 然而&#xff0c;在双击页眉进行编辑的时候却发现几个章节的页眉一起被修改了&#xff1a; 会出现文章与页眉不同步的情况&#xff0c…

idea使用Junit

文章目录 idea使用JunitJunit配置常用注解常用于测试的断言方法后续idea使用Junit 对项目使用Junit主要有两个步骤: 添加Junit依赖,即添加Junit jar包使用JunitJunit配置 方法一:idea自带的快捷方法 对要测试的类的方法,在该类中,右键鼠标呼出菜单,选择Generate,快捷…

简单的回顾Linux

linux命令ls会显示出文件的颜色, 系统约定的默认颜色含义如下: 白色&#xff1a;表示普通文件 蓝色&#xff1a;表示目录 绿色&#xff1a;表示可执行文件 红色&#xff1a;表示压缩文件 浅蓝色&#xff1a;链接文件 主要是使用ln命令建立的文件 红色闪烁&#xff1a;表示链接的…