- [开盖即食]基于canvas的“刮刮乐”刮奖组件
[图片] 工作中有时候会遇到一些关于“抽奖”的需求,这次以“刮刮乐项目”举例,分享一个实战抽奖功能。 本人对之前网上流传的一些H5刮刮乐JS插件版本进行了一些改造,使其能适用于实际项目,并且支持小程序canvas 2D的新API,这里顺便提下2D API和实际H5 canvas中JS写法非常类似,只有少数不同。 [图片] 1、方法介绍: 1.1 刮刮乐JS组件 [代码]class Scratch { constructor(page, opts) { opts = opts || {}; this.page = page; this.canvasId = opts.canvasId || 'canvas'; this.width = opts.width || 300; this.height = opts.height || 300; this.bgImg = opts.bgImg || ''; //覆盖的图片 this.maskColor = opts.maskColor || '#edce94'; this.size = opts.size || 15, //this.r = this.size * 2; this.r = this.size; this.area = this.r * this.r; this.showPercent = opts.showPercent || 0.2; //刮开多少比例显示全部 this.rpx = wx.getSystemInfoSync().windowWidth / 750; //设备缩放比例 this.scale = opts.scale || 0.5; this.totalArea = this.width * this.height; this.startCallBack = opts.startCallBack || false; //第一次刮时触发刮奖效果 this.overCallBack = opts.overCallBack || false; //刮奖完触发 this.init(); } init() { let self = this; this.show = false; this.clearPoints = []; const query = wx.createSelectorQuery(); //console.log(this.canvasId); query.select(this.canvasId) .fields({ node: true, size: true }) .exec((res) => { //console.log(res); this.canvas = res[0].node; this.ctx = this.canvas.getContext('2d') this.canvas.width = res[0].width; this.canvas.height = res[0].height; //const dpr = wx.getSystemInfoSync().pixelRatio; //this.canvas.width = res[0].width * dpr; //this.canvas.height = res[0].height * dpr; self.drawMask(); self.bindTouch(); }) } async drawMask() { let self = this; if (self.bgImg) { //判断是否是网络图片 let imgObj = self.canvas.createImage(); if (self.bgImg.indexOf("http") > -1) { await wx.getImageInfo({ src: self.bgImg, //服务器返回的图片地址 success: function (res) { imgObj.src = res.path; //res.path是网络图片的本地地址 }, fail: function (res) { //失败回调 console.log(res); } }); } else { imgObj.src = self.bgImg; //res.path是网络图片的本地地址 } imgObj.onload = function (res) { self.ctx.drawImage(imgObj, 0, 0, self.width * self.rpx, self.height * self.rpx); //方法不执行 } imgObj.onerror = function (res) { console.log('onload失败') //实际执行了此方法 } } else { this.ctx.fillStyle = this.maskColor; this.ctx.fillRect(0, 0, self.width * self.rpx, self.height * self.rpx); } //this.ctx.draw(); } bindTouch() { this.page.touchStart = (e) => { this.eraser(e, true); } this.page.touchMove = (e) => { this.eraser(e, false); } this.page.touchEnd = (e) => { if (this.show) { //this.page.clearCanvas(); if (this.overCallBack) this.overCallBack(); this.ctx.clearRect(0, 0, this.width * this.rpx, this.height * this.rpx); //this.ctx.draw(); } } } eraser(e, bool) { let len = this.clearPoints.length; let count = 0; let x = e.touches[0].x, y = e.touches[0].y; let x1 = x - this.size; let y1 = y - this.size; if (bool) { this.clearPoints.push({ x1: x1, y1: y1, x2: x1 + this.r, y2: y1 + this.r }) } for (let item of this.clearPoints) { if (item.x1 > x || item.y1 > y || item.x2 < x || item.y2 < y) { count++; } else { break; } } if (len === count) { this.clearPoints.push({ x1: x1, y1: y1, x2: x1 + this.r, y2: y1 + this.r }); } //添加计算已清除的面积,达到标准值后,设置刮卡区域刮干净 let clearNum = parseFloat(this.r * this.r * len) / parseFloat(this.scale * this.totalArea); if (!this.show) { this.page.setData({ clearNum: parseFloat(this.r * this.r * len) / parseFloat(this.scale * this.totalArea) }) }; if (this.startCallBack) this.startCallBack(); //console.log(clearNum) if (clearNum > this.showPercent) { //if (len && this.r * this.r * len > this.scale * this.totalArea) { this.show = true; } this.clearArcFun(x, y, this.r, this.ctx); } clearArcFun(x, y, r, ctx) { let stepClear = 1; clearArc(x, y, r); function clearArc(x, y, radius) { let calcWidth = radius - stepClear; let calcHeight = Math.sqrt(radius * radius - calcWidth * calcWidth); let posX = x - calcWidth; let posY = y - calcHeight; let widthX = 2 * calcWidth; let heightY = 2 * calcHeight; if (stepClear <= radius) { ctx.clearRect(posX, posY, widthX, heightY); stepClear += 1; clearArc(x, y, radius); } } } } export default Scratch [代码] 1.2 JS 调用方法 [代码]new Scratch(self, { canvasId: '#coverCanvas', //对应的canvasId width: 600, height: 300, //maskColor:"", //封面颜色 showPercent: 0.3, //刮开多少比例显示全部,比如0.3为 30%面积 bgImg: "./cover.jpg", //封面图片 overCallBack: () => { //刮奖刮完回调函数 }, startCallBack: () => { //当用户触摸canvas板的时候触发回调 } }) [代码] 实际中还支持其他很多的配置项,比如缩放比例,刮开比例,放置区域等等,大家可以根据实际需求设置。 1.3 实际页面中的JS调用方法: [代码]//引入刮刮乐部分 import Scratch from './scratch.js'; const app = getApp() Page({ data: { firstTouch: 0, isOver: 0, }, onLoad() { let self = this; new Scratch(self, { canvasId: '#coverCanvas', width: 600, height: 300, //maskColor:"", //封面颜色 bgImg: "./cover.jpg", //封面图片 overCallBack: () => { this.setData({ isOver: "结束啦" }) //this.clearCanvas(); }, startCallBack: () => { this.setData({ firstTouch: "开始刮啦" }) //this.postScratchSubmit(); } }) }, //刮卡已刮干净 clearCanvas() { let self = this; console.log("over"); }, }) [代码] 1.4 HTML/CSS [代码]<-- html --> <view class="wrap"> <canvas class="cover_canvas" type="2d" disable-scroll="false" id='coverCanvas' bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"></canvas> <image class="img" src="reward.jpg" mode="widthFix" /> </view> /* css */ .wrap { width: 600rpx; height: 300rpx; margin: 100rpx auto; border: 1px solid #000; position: relative; } .cover_canvas { width: 600rpx; height: 300rpx; z-index: 9; } .wrap .img { position: absolute; left: 0; top: 0; z-index: 1; width: 600rpx; height: 300rpx; } [代码] 这里注意 type=“2d” 写法,这里使用的是新的2D canvas。 2、注意事项 canvas一些效果不支持真机调试,直接预览就行了 如果刮奖结果是通过第一次触碰canvas触发的,这里的请求需要写一个同步方法 刮刮乐JS的配置会优先判断bgImg这个属性,再判断maskColor 需要反复刮奖,可以反复new 它。 3、代码片段 地址: https://developers.weixin.qq.com/s/RxiaHam574or 建议将IDE工具升级到 1.03.24以上,避免一些BUG [图片] 觉得有用,请点个赞,这是我继续分享的动力~
2021-02-18 - 5行代码实现微信小程序模版消息推送 (含推送后台和小程序源码)
由于小程序2020年1月10日以后改模板消息为订阅消息,所以我写了一篇新的文章来更新这个知识点 《小程序订阅消息推送(含源码)java实现小程序推送,springboot实现微信消息推送》 我们在做小程序开发时,消息推送是不可避免的。今天就来教大家如何实现小程序消息推送的后台和前台开发。源码会在文章末尾贴出来。 其实我之前有写过一篇:《springboot实现微信消息推送,java实现小程序推送,含小程序端实现代码》 但是有同学反应这篇文章里的代码太繁琐,接入也比较麻烦。今天就来给大家写个精简版的,基本上只需要几行代码,就能实现小程序模版消息推送功能。 老规矩先看效果图 [图片] 这是我们最终推送给用户的模版消息。这是用户手机微信上显示的推送消息截图。 本节知识点 1,java开发推送后台 2,springboot实现推送功能 3,小程序获取用户openid 4,小程序获取fromid用来推送 先来看后台推送功能的实现 只有下面一个简单的PushController类,就可以实现小程序消息的推送 [图片] 再来看下PushController类,你没看错,实现小程序消息推送,就需要下面这几行代码就可以实现了。 [图片] 由于本推送代码是用springboot来实现的,下面就来简单的讲下。我我们需要注意的几点内容。 1,需要在pom.xml引入一个三方类库(推送的三方类库) [图片] pom.xml的完整代码如下 [代码]<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.qcl</groupId> <artifactId>wxapppush</artifactId> <version>0.0.1-SNAPSHOT</version> <name>wxapppush</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--微信小程序模版推送--> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-miniapp</artifactId> <version>3.4.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> [代码] 其实到这里我们java后台的推送功能,就已经实现了。我们只需要运行springboot项目,就可以实现推送了。 下面贴出完整的PushController.java类。里面注释很详细了。 [代码]package com.qcl.wxapppush; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig; import me.chanjar.weixin.common.error.WxErrorException; /** * Created by qcl on 2019-05-20 * 微信:2501902696 * desc: 微信小程序模版推送实现 */ @RestController public class PushController { @GetMapping("/push") public String push(@RequestParam String openid, @RequestParam String formid) { //1,配置小程序信息 WxMaInMemoryConfig wxConfig = new WxMaInMemoryConfig(); wxConfig.setAppid("XXX");//小程序appid wxConfig.setSecret("xxx");//小程序AppSecret WxMaService wxMaService = new WxMaServiceImpl(); wxMaService.setWxMaConfig(wxConfig); //2,设置模版信息(keyword1:类型,keyword2:内容) List<WxMaTemplateData> templateDataList = new ArrayList<>(2); WxMaTemplateData data1 = new WxMaTemplateData("keyword1", "获取老师微信"); WxMaTemplateData data2 = new WxMaTemplateData("keyword2", "2501902696"); templateDataList.add(data1); templateDataList.add(data2); //3,设置推送消息 WxMaTemplateMessage templateMessage = WxMaTemplateMessage.builder() .toUser(openid)//要推送的用户openid .formId(formid)//收集到的formid .templateId("eDZCu__qIz64Xx19dAoKg0Taf5AAoDmhUHprF6CAd4A")//推送的模版id(在小程序后台设置) .data(templateDataList)//模版信息 .page("pages/index/index")//要跳转到小程序那个页面 .build(); //4,发起推送 try { wxMaService.getMsgService().sendTemplateMsg(templateMessage); } catch (WxErrorException e) { System.out.println("推送失败:" + e.getMessage()); return e.getMessage(); } return "推送成功"; } } [代码] 看代码我们可以知道,我们需要做一些配置,需要下面信息 1,小程序appid 2,小程序AppSecret(密匙) 3,小程序推送模版id 4,用户的openid 5,用户的formid(一个formid只能用一次) 下面就是小程序部分,来教大家如何获取上面所需的5个信息。 1,appid和AppSecret的获取(登录小程序管理后台) [图片] 2,推送模版id [图片] 3,用户openid的获取,可以看下面的这篇文章,也可以看源码,这里不做具体讲解 小程序开发如何获取用户openid 4,获取formid [图片] 看官方文档,可以知道我们的formid有效期是7天,并且一个form_id只能使用一次,所以我们小程序端所需要做的就是尽可能的多拿些formid,然后传个后台,让后台存到数据库中,这样7天有效期内,想怎么用就怎么用了。 所以接下来要讲的就是小程序开发怎么尽可能多的拿到formid了 [图片] 看下官方提供的,只有在表单提交时把report-submit设为true时才能拿到formid,比如这样 [代码] <form report-submit='true' > <button form-type='submit'>获取formid</button> </form> [代码] 所以我们就要在这里下功夫了,既然只能在form组件获取,我们能不能把我们小程序里用到最多的地方用form来伪装呢。 下面简单写个获取formid和openid的完整示例,方便大家学习 效果图 [图片] 我们要做的就是点击获取formid按钮,可以获取到用户的formid和openid,正常我们开发时,是需要把openid和formid传给后台的,这里简单起见,我们直接用获取到的formid和openid实现推送功能 下面来看小程序端的实现代码 1,index.wxml [图片] 2,index.js [图片] 到这里我们小程序端的代码也实现了,接下来测试下推送。 [代码]formid: 6ee9ce80c1ed4a2f887fccddf87686eb openid o3DoL0Uusu1URBJK0NJ4jD1LrRe0 [代码] [图片] 可以看到我们用了上面获取到的openid和formid做了一次推送,显示推送成功 [图片] [图片] 到这里我们小程序消息推送的后台和小程序端都讲完了。 这里有两点需要大家注意 1,推送的openid和formid必须对应。 2,一个formid只能用一次,多次使用会报一下错误。 [代码]{"errcode":41029,"errmsg":"form id used count reach limit hint: [ssun8a09984113]"} [代码] 编程小石头,码农一枚,非著名全栈开发人员。分享自己的一些经验,学习心得,希望后来人少走弯路,少填坑。 这里就不单独贴出源码下载链接了,大家感兴趣的话,可以私信我,或者在底部留言,我会把源码下载链接贴在留言区。 单独找我要源码也行(微信2501902696) 视频讲解:https://edu.csdn.net/course/detail/23750 源码链接:https://github.com/qiushi123/wxapppush
2020-01-08