使用canvas画布画出刮刮乐要被刮的图片,使用移动清除画布。
当前代码封装为刮刮乐的组件;
vue代码:
<template>
<view class="page" v-if="merchantInfo.cdn_static">
<image class="bg" :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/page_bg.png'" mode="aspectFill"></image>
<view class="content">
<view class="logo">
<image :src="merchantInfo.logo" mode="heightFix"></image>
</view>
<view class="title">
<image :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/title.png'" mode="heightFix"></image>
</view>
<view class="notification">
<view></view>
<text>每日刮卡抽好礼</text>
<view></view>
</view>
<view class="box">
<image class="scrapingBg" :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/scrapingBg.png'"></image>
<view class="scrapingBox">
<view class="scrapingBoxContent">
<!-- 奖品名称 -->
<view>{{ prizeTitle || "" }}</view>
<canvas :style="{'width':width+'px','height':height+'px'}" style="position: absolute; top: 0;" canvas-id="myCanvas" id="myCanvas" @touchstart="touchstart" @touchend="touchend" @touchmove="touchmove"></canvas>
</view>
</view>
</view>
<view class="count">
<view class="tip">您今天还有<text>{{total}}</text>次抽奖机会</view>
</view>
<view class="btns">
<view class="btn" @click="getRule">
<image class="btnImg" :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/rule.png'"></image>
<view class="btnConent">
<image :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/ruleIcon.png'"></image>
<text>查看规则</text>
</view>
</view>
<view class="btn" @click="getResult()">
<image class="btnImg" :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/prize.png'"></image>
<view class="btnConent">
<image :src="merchantInfo.cdn_static +'statistics/luckDrawImg/scratchcard/prizeIcon.png'"></image>
<text>兑换福利</text>
</view>
</view>
</view>
</view>
<view class="win" v-if="rule_show">
<scroll-view scroll-y class="win_box win_box_bg">
<mp-html :content="luckDrawInfo.rule" />
</scroll-view>
<text class="iconfont iconcolseIcon theme-font-white" @click="rule_show=false"></text>
</view>
<view class="win" v-if="result_show">
<view class="win_box1">
<image class="win_bg" :src="merchantInfo.cdn_static +'statistics/luckDrawImg/result_bg.png'" mode=""></image>
<view class="win_content">
<view class="win_tips theme-font-white">{{currentPrize.desc}}</view>
<view class="win_title">{{currentPrize.title}}</view>
<view class="win_btn" @click="choiseAddress()">{{currentPrize.is_address==1?'选择地址':'确定'}}</view>
</view>
</view>
</view>
<view class="win" v-if="prize_show">
<view class="win_tit theme-font-white">我的奖品</view>
<view class="win_box2">
<view class="items">
<view class="left i_title">奖品</view>
<view class="right i_title">中奖时间</view>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="(item,index) in list" :key="index">
<view class="left">{{item.lottery_prize_title}}</view>
<view class="right" v-if="item.is_address==1&&!item.address_id">
<view class="r_btn" @click="choiseAddress1(item)">去领奖</view>
</view>
<view class="right" v-else>{{item.created_time}}</view>
</view>
</scroll-view>
</view>
<text class="iconfont iconcolseIcon theme-font-white" @click="prize_show=false"></text>
</view>
</view>
</template>
<script>
import { luckDrawInfo } from '@/api/luckDraw.js';
import colors from '@/mixins/color';
export default {
mixins: [colors],
data() {
return {//https://cdn.dev.scrm.juplus.cn/InQLzDLoAl2S9LyNJUXQ45gpA.png
mask: true,
wtf:true,
luckDrawInfo: {},
rule_show:false,
result_show:false,
prize_show:false,
total:0,
currentPrize:{},
list:[],
id: "",
prizeTitle: "",
filePath: "",
ctx: null,
width: 0,
height: 0,
disabled: false, // 是否禁止刮卡
readyState: false, // 是否开始绘制
endState: false, // 结束刮卡状态
watermark: '刮一刮', // 水印文字
watermarkColor: '#c5c5c5', // 水印文字颜色
watermarkSize: 14, // 水印文字大小
title: '刮一刮开奖', // 提示文字
titleColor: '#888', // 提示文字颜色
titleSize: 24, // 提示文字大小
startX: 0, // 触摸x轴位置
startY: 0, // 触摸y轴位置
touchSize: 30, // 触摸画笔大小
percentage: 50, // 刮开百分之多少的时候开奖
}
},
props: {
userId: {
type: [Number,String]
},
type:{
type: [Number,String]
}
},
//渲染完了
mounted() {
this.id = this.userId;
this.init();
},
methods: {
drawInit(imgUrl) {
this.endState = false;
this.readyState = false;
this.ctx.clearRect(0, 0, this.width, this.height); // 清除画布上在该矩形区域内的内容(x,y,宽,高)。
// this.ctx.setFillStyle('#ddd'); // 填充颜色
// this.ctx.fillRect(0, 0, this.width, this.height); // 填充区域(x,y,宽,高)
/**
* 绘制文字水印
*/
// var width = this.watermark.length * this.watermarkSize;
// this.ctx.save(); // 保存当前的绘图上下文。
// this.ctx.rotate(-10 * Math.PI / 180); // 以原点为中心,原点可以用 translate方法修改。顺时针旋转当前坐标轴。多次调用rotate,旋转的角度会叠加。
// let x = 0;
// let y = 0;
// let i = 0;
// while ((x <= this.width * 5 || y <= this.height * 5) && i < 300) {
// this.ctx.setFillStyle(this.watermarkColor); // 填充颜色
// this.ctx.setFontSize(this.watermarkSize); // 设置字体的字号
// this.ctx.fillText(this.watermark, x, y); // 填充的文本(文字,x,y)
// x += width + width * 1.6;
// if (x > this.width && y <= this.height) {
// x = -Math.random() * 100;
// y += this.watermarkSize * 3;
// }
// i++;
// }
// this.ctx.restore(); // 恢复之前保存的绘图上下文。
/**
* 绘制标题
*/
// this.ctx.setTextAlign("center"); // 用于设置文字的对齐
// this.ctx.setTextBaseline("middle"); // 用于设置文字的水平对齐
// this.ctx.setFillStyle(this.titleColor); // 填充颜色
// this.ctx.setFontSize(this.titleSize); // 设置字体的字号
// this.ctx.fillText(this.title, this.width / 2, this.height / 2); // 填充的文本(文字,x,y)
/**
* 绘制图片
*/
this.ctx.drawImage(this.filePath, 0, 0, this.width, this.height);
this.ctx.draw(); // 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中。
this.readyState = true; // 完成绘制
},
// 手指触摸动作开始
touchstart(e) {
if (this.disabled || this.endState) {
return;
}
this.startPlay();
this.startX = e.touches[0].x;
this.startY = e.touches[0].y;
},
// 手指触摸后移动
touchmove(e) {
if (this.disabled || this.endState) return;
if (!this.prizeTitle) return;
this.ctx.clearRect(this.startX, this.startY, this.touchSize, this.touchSize); // 清除画布上在该矩形区域内的内容(x,y,宽,高)。
this.ctx.draw(true); // false:本次绘制是否接着上一次绘制,true:保留当前画布上的内容
//记录移动点位
this.startX = e.touches[0].x;
this.startY = e.touches[0].y;
},
// 手指触摸动作结束
touchend(e) {
if (this.disabled || this.endState) {
return;
}
// 返回一个数组,用来描述 canvas 区域隐含的像素数据,在自定义组件下,第二个参数传入自定义组件实例 this,以操作组件内 <canvas> 组件。
uni.canvasGetImageData({
canvasId: 'myCanvas',
x: 0,
y: 0,
width: this.width,
height: this.height,
success: (res) => {
console.log(res);
let pixels = res.data;
let transPixels = [];
for (let i = 0; i < pixels.length; i += 4) {
if (pixels[i + 3] < 128) {
transPixels.push(pixels[i + 3]);
}
}
var percent = (transPixels.length / (pixels.length / 4) * 100).toFixed(2);
if (percent >= this.percentage) {
this.scrapingSuccess();
}
},
fail: (e) => {
console.log(e);
},
}, this);
},
// 成功,清除所有图层
scrapingSuccess(e) {
if (this.endState) {
return;
}
this.endState = true;
this.ctx.moveTo(0, 0); // 把路径移动到画布中的指定点,不创建线条。用 stroke() 方法来画线条。
this.ctx.clearRect(0, 0, this.width, this.height); // 清除画布上在该矩形区域内的内容(x,y,宽,高)。
this.ctx.stroke(); // 画出当前路径的边框。默认颜色色为黑色。
this.ctx.draw(true);
// 弹出奖品
setTimeout(()=>{
this.result_show = true;
this.drawInit();
this.wtf = true;
this.prizeTitle = "";
},800)
},
init(){
if(this.userInfo){
this.getInfo()
}else{
setTimeout(()=>{
this.init()
},500)
}
},
getInfo(){
luckDrawInfo.getDetail({id:this.id}).then(res => {
this.luckDrawInfo=res.data
this.total=res.data.my_can_num
this.action('lottery',this.id,0,2,this.luckDrawInfo.title,'','lottery')
let content = uni.createSelectorQuery().in(this).select(".scrapingBoxContent");
content.boundingClientRect((data) => {
this.width = data.width;
this.height = data.height;
this.ctx = uni.createCanvasContext('myCanvas', this);
uni.getImageInfo({
src: this.merchantInfo.cdn_static + 'statistics/luckDrawImg/scratchcard/scratchingBefore.png',
success: (res) => {
this.filePath = res.path;
this.drawInit();
}
})
}).exec()
})
},
choiseAddress(){
this.currentPrize.is_address==1?uni.navigateTo({
url:'/pages/address/address'
}):''
this.result_show=false
},
choiseAddress1(data){
this.currentPrize=data
uni.navigateTo({
url:'/pages/address/address'
})
this.prize_show=false
},
setAddress(id){
luckDrawInfo.setAddress({address_id:id,history_id:this.currentPrize.history_id||this.currentPrize.id}).then(res => {
uni.showToast({
title:"地址设置成功",
icon:'none'
})
})
},
getRule(){
if (this.prizeTitle) {
this.scrapingSuccess();
setTimeout(()=>{
this.rule_show = true;
},800)
} else {
this.rule_show = true;
}
},
getResult(){
// if(!this.wtf){
// return false
// }
if (this.prizeTitle) {
this.scrapingSuccess();
} else {
luckDrawInfo.getResult({lottery_id:this.id}).then(res => {
this.list=res.data.data
this.prize_show=true
});
}
},
// 点击开始,请求接口抽奖
startPlay(index) {
if(this.luckDrawInfo.is_register==1&&!this.userInfo.type){
uni.navigateTo({
url:'/pages/login/login'
})
return false
}
if(this.luckDrawInfo.is_form==1&&this.luckDrawInfo.user_form_count==0){
uni.navigateTo({
url:'/pages/form/form?id='+this.luckDrawInfo.form_id+'&type_id=' + this.id + '&type=lottery'
})
return false
}
if(!this.wtf){
return false
}
// 活动未开始或活动已结束
let startTimeMs = new Date(this.luckDrawInfo.start_time).getTime();
let endTimeMs = new Date(this.luckDrawInfo.end_time).getTime();
let nowTimeMs = new Date().getTime();
if (nowTimeMs < startTimeMs) {
uni.showToast({
icon: "none",
title: "活动未开始"
})
return false;
}
if (nowTimeMs > endTimeMs) {
uni.showToast({
icon: "none",
title: "活动已结束"
})
return false;
}
this.mask = false;
this.wtf = false;
luckDrawInfo.run({id:this.id}).then(res => {
this.currentPrize = res.data;
this.total = res.data.row_lottery_new.my_can_num;
this.prizeTitle = res.data.title;
}).catch(err => {
this.wtf = true;
});
}
}
}
</script>
<style scoped lang="scss">
@import 'index.scss';
/**/
</style>
scss代码:
.page{
width: 750rpx;
min-height: 100vh;
height: 1448rpx;
position: relative;
}
.bg{
width: 750rpx;
min-height: 100vh;
height: 1448rpx;
}
.content{
width: 750rpx;
min-height: 100vh;
height: 1448rpx;
position: absolute;
top: 0;
left: 0;
}
.logo{
height: 60rpx;
display: flex;
justify-content: center;
margin-top: 90rpx;
image{
height: 60rpx;
}
}
.title{
height: 254rpx;
display: flex;
justify-content: center;
margin-top: 20rpx;
image{
width: 640rpx;
height: 254rpx;
}
}
.notification{
width: 370rpx;
height: 56rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin: -60rpx auto 0 auto;
view{
width: 26rpx;
height: 4rpx;
background-color: #fff;
}
text{
font-size: 38rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FFFFFF;
}
}
.box{
width: 658rpx;
height: 422rpx;
margin: 110rpx auto 0 auto;
position: relative;
.scrapingBg{
width: 100%;
height: 100%;
}
.scrapingBox{
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
padding: 52rpx 62rpx;
.scrapingBoxContent{
width: 536rpx;
height: 318rpx;
z-index: 2;
position: relative;
view {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 52rpx 116rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 72rpx;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #1B9AF9;
text-shadow: 0px 4px 8px rgba(149,216,255,0.5);
}
}
}
}
.count{
display: flex;
justify-content: center;
margin-top: 44rpx;
.tip{
font-size: 28rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #777777;
text{
color: #0039AF;
}
}
}
.btns{
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 26rpx;
}
.btn{
width: 376rpx;
height: 166rpx;
position: relative;
.btnImg{
width: 100%;
height: 100%;
}
.btnConent{
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-100%);
display: flex;
justify-content: center;
align-items: center;
image{
width: 42rpx;
height: 42rpx;
margin-right: 12rpx;
}
text{
font-size: 42rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FFFFFF;
}
}
}
.win{
width: 750rpx;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
position: fixed;
top: 0;
left: 0;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.win_box{
width: 662rpx;
height: 60%;
padding: 40rpx;
box-sizing: border-box;
border-radius: 24rpx;
}
.win_box_bg{
background: #C3E5FE;
}
.bg3{
background: #C3E5FE;
}
.iconcolseIcon{
font-size: 58rpx;
margin-top: 98rpx;
}
.win_box1{
width: 630rpx;
height: 922rpx;
position: relative;
}
.win_bg{
width: 630rpx;
height: 922rpx;
}
.win_content{
width: 630rpx;
height: 922rpx;
position: absolute;
left: 0;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.win_tips{
font-size: 48rpx;
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
font-weight: bold;
margin-top: 290rpx;
}
.win_title{
font-size: 48rpx;
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
font-weight: bold;
color: #FE6631;
margin: 170rpx 0;
}
.win_btn{
width: 280rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
background: #FFE047;
border-radius: 46rpx;
font-size: 32rpx;
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
font-weight: bold;
color: #13112C;
}
.win_tit{
font-size: 48rpx;
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
font-weight: bold;
margin-bottom: 32rpx;
}
.win_box2{
width: 662rpx;
height: 900rpx;
background: #FFFFFF;
border-radius: 24rpx;
display: flex;
flex-direction: column;
}
.items{
width: 662rpx;
height: 108rpx;
background: #C3E5FE;
border-radius: 24rpx 24rpx 0rpx 0rpx;
display: flex;
align-items: center;
flex-shrink:0
}
.left,.right{
width: 50%;
text-align: center;
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
font-weight: bold;
}
.i_title{
font-size: 36rpx;
}
.list{
height: 792rpx;
padding-bottom: 20rpx;
overflow: hidden;
}
.item{
width: 662rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-around;
}
.item:nth-child(2n){
background-color: #F4F4F4;
}
.r_btn{
width: 160rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
background: #FFC659;
border-radius: 46rpx;
font-family: SourceHanSansSC-Medium, SourceHanSansSC;
font-weight: bold;
font-size: 32rpx;
margin:0 auto;
}
效果: