微信小程序实现刮一刮效果
wxml代码
html
<view class="container">
<view class="scratch-wrapper">
<view class="zj" wx:if="{{prizeShow}}">
恭喜你中奖了
</view>
<canvas type="2d" id="scratchCanvas" class="scratch-canvas" disable-scroll="{{true}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" />
</view>
<button bindtap="reset">重置</button>
</view>wxss代码
css
.container {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 100rpx;
}
.scratch-wrapper {
position: relative;
width: 600rpx;
height: 400rpx;
}
.prize-text {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #ff4444;
font-weight: bold;
background: #fff3cd;
}
.scratch-canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 10;
}
.zj{
width: 100%;
height: 100%;
background-color: red;
text-align: center;
line-height: 400rpx;
color: #fff;
position: absolute;
top: 0;
left: 0;
}js代码
js
Page({
data: {
// ===== 响应式数据 =====
scratchImageSrc: 'https://dop.x.digitalyili.com/shortVideo/img/bg/guajiang.png', // 刮层图地址,不传地址会有默认显示
ready: false, // canvas 是否可用
threshold: 0.5, // 40% 区域就全部展示
checked: false, // 避免重复统计
width: 0, // 画布宽(px)
height: 0, // 画布高(px)
brushSize: 10, // ← 笔粗细(像素)
prizeShow: false, // 控制奖品显示,防止一直点重置导致底下奖品闪烁
},
// ===== 非响应式原生引用 =====
canvas: null,
ctx: null,
onReady() {
this.initCanvas();
},
initCanvas() {
wx.createSelectorQuery()
.select('#scratchCanvas')
.fields({
node: true,
size: true
})
.exec((res) => {
if (!res[0]) return;
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
const dpr = wx.getSystemInfoSync().pixelRatio;
const width = res[0].width;
const height = res[0].height;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
/* 刮层逻辑:有图用图,无图自画 */
const src = this.data.scratchImageSrc || '';
if (src) {
const img = canvas.createImage();
img.src = src;
img.onload = () => {
ctx.drawImage(img, 0, 0, width, height);
this.setData({
ready: true
});
};
} else {
ctx.fillStyle = '#888';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = '#fff';
ctx.font = 'bold 32rpx sans-serif';
ctx.textAlign = 'center';
ctx.fillText('刮开有惊喜', width / 2, height / 2 + 10);
this.setData({
ready: true
});
}
// 保存原生引用
this.canvas = canvas;
this.ctx = ctx;
this.setData({
width,
height,
checked: false,
});
});
},
onTouchStart(e) {
if (!this.data.ready) return;
// 显示底部奖品
this.setData({
prizeShow: true
})
const p = this.getPos(e);
this.scratch(p.x, p.y);
},
onTouchMove(e) {
if (!this.data.ready) return;
const p = this.getPos(e);
this.scratch(p.x, p.y);
},
onTouchEnd() {
if (!this.data.ready || this.data.checked) return;
setTimeout(() => this.checkArea(), 150);
},
getPos(e) {
return {
x: e.touches[0].x,
y: e.touches[0].y
};
},
scratch(x, y) {
this.ctx.globalCompositeOperation = 'destination-out';
this.ctx.beginPath();
this.ctx.arc(x, y, this.data.brushSize, 0, Math.PI * 2); // 用变量
this.ctx.fill();
},
checkArea() {
if (this.data.checked) return;
this.setData({
checked: true
});
const ctx = this.ctx;
const width = ~~this.data.width; // 强制整数
const height = ~~this.data.height;
const imageData = ctx.getImageData(0, 0, width, height);
const pixels = imageData.data;
let transparent = 0;
for (let i = 3; i < pixels.length; i += 4) {
if (pixels[i] === 0) transparent++;
}
const ratio = transparent / (width * height);
if (ratio >= this.data.threshold) {
ctx.clearRect(0, 0, width, height);
} else {
this.setData({
checked: false
});
}
},
reset() {
this.setData({
ready: false,
prizeShow: false
});
this.initCanvas();
}
});