ant-mini-scratch-card
Version:
支付宝小程序刮刮卡组件
211 lines (200 loc) • 7.67 kB
JavaScript
Component({
data: {
pr: 1,
scraping: true, // 正在刮奖
animationClass: ''
},
props: {
id: 'scratch-canvas',
width: 300, // 容器宽度 px
height: 150, // 容器宽度 px
tipText: '刮刮我,有惊喜', // 提示文字
tipColor: '#aaa', // 提示文字颜色
tipSize: 20, // 提示文字大小
lineWidth: 25, // 橡皮檫粗细
activePercent: 0.4, // 擦除一定比例区域后背景自动消失,值为小数,0-1
autoFadeOut: true, // 是否开启背景自动消失
coverColor: '#dbdbdb', // 背景颜色
resultText: '谢谢参与', // 抽奖结果文字
onFinish: function onFinish() {} // 抽奖结束回调,当擦除比例达到activePercent时触发
},
didMount: function didMount() {
// var toast = function(title) {
// my.showToast({
// type: 'success',
// content: title,
// duration: 1000,
// });
// }
// my.getSystemInfo({
// success: res => {
// // 根据rpx 宽高比计算出实际 px 宽高比
// const pr = res.screenWidth / 750;
// this.setData({
// pr,
// });
// // toast(`${res.pixelRatio}, screenWidth: ${res.screenWidth}, pr:${pr}`);
// this.ctx = my.createCanvasContext('scratch-canvas');
// this.draw(); // 刮刮卡容器初始化;
// this.area = pr * this.props.width * pr * this.props.height; // canvas 面积
// this.clearPercent = 0; // 被清除像素的所有选区占 canvas 面积的百分比
// }
// })
var pr = 1;
this.ctx = my.createCanvasContext('scratch-canvas');
this.draw(); // 刮刮卡容器初始化;
this.area = pr * this.props.width * pr * this.props.height; // canvas 面积
this.clearPercent = 0; // 被清除像素的所有选区占 canvas 面积的百分比
},
methods: {
onTouchStart: function onTouchStart(e) {
// 在真机上该事件没有changedTouches属性
if (e.touches && e.touches[0]) {
var point = e.touches[0];
this.lastPoint = point;
}
},
onTouchMove: function onTouchMove(e) {
var point = (e.changedTouches || e.touches || [])[0];
if (point) {
this.refresh(point);
this.lastPoint = point;
}
},
onTouchEnd: function onTouchEnd(e) {
if (!this.data.scraping) return;
var point = (e.changedTouches || e.touches || [])[0];
if (!point) {
// 没有拿到point直接完成
this.onFinish();
this.setData({ scraping: false });
this.props.autoFadeOut && this.fadeOut();
}
this.lastPoint = null;
if (this.clearPercent > this.props.activePercent) {
this.onFinish();
this.setData({ scraping: false });
this.props.autoFadeOut && this.fadeOut();
}
},
draw: function draw() {
var ctx = this.ctx;
var props = this.props;
var pr = this.data.pr;
ctx.fillStyle = props.coverColor;
ctx.fillRect(0, 0, props.width * pr, props.height * pr);
// 绘制logo背景图
if (props.ctxLogoUrl) {
ctx.drawImage(props.ctxLogoUrl, 0, 0, props.width * pr, props.height * pr);
}
// 绘制提示文字
// 设置字体样式
ctx.font = props.tipSize + 'px Courier New';
// 设置字体颜色
ctx.fillStyle = props.tipColor;
// 绘制文字垂直居中
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(props.tipText, props.width * pr / 2, props.height * pr / 2);
ctx.strokeStyle = 'white';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = props.lineWidth;
ctx.draw();
},
refresh: function refresh() {
var point = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
/*
小程序 canvas 不支持 globalCompositeOperation 属性
this.ctx.globalCompositeOperation = "destination-out"; // 无效
所以要很 hack 的根据屏幕滑动始末两端点连成粗线条选区,再自己实现清除选区像素
*/
var pr = this.data.pr;
var ctx = this.ctx;
var props = this.props;
var r = props.lineWidth / 2;
var x1 = this.lastPoint.x;
var y1 = this.lastPoint.y;
var x2 = point.x;
var y2 = point.y;
// (x1, y1), (x2, y2)分别为线条起始和结尾的两个端点,即粗线条两端点圆弧的圆心 矩形长为手指移动的线条长度,高为线条宽度lineWidth
// 获取两个点之间的剪辑区域四个端点,即矩形边框顶点(x3, y3)..(x6, y6)
var asin = r * Math.sin(Math.atan((y2 - y1) / (x2 - x1)));
var acos = r * Math.cos(Math.atan((y2 - y1) / (x2 - x1)));
var x3 = x1 + asin;
var y3 = y1 - acos;
var x4 = x1 - asin;
var y4 = y1 + acos;
var x5 = x2 + asin;
var y5 = y2 - acos;
var x6 = x2 - asin;
var y6 = y2 + acos;
// 保证线条的连贯,所以在矩形两端画圆
ctx.save();
ctx.beginPath();
ctx.arc(x1, y1, r, 0, 2 * Math.PI);
ctx.arc(x2, y2, r, 0, 2 * Math.PI);
ctx.clip();
ctx.clearRect(0, 0, props.width * pr, props.height * pr);
ctx.restore();
// 清除矩形剪辑区域里的像素
ctx.save();
ctx.beginPath();
ctx.moveTo(x3, y3);
ctx.lineTo(x5, y5);
ctx.lineTo(x6, y6);
ctx.lineTo(x4, y4);
ctx.closePath();
ctx.clip();
ctx.clearRect(0, 0, props.width * pr, props.height * pr);
ctx.restore();
// 清除线条像素方案2
// 在小程序内当滑动很快时会导致页面渲染崩溃白屏
// this._clearCircle(point, r);
// if (this.lastPoint) {
// let posX = point.x - this.lastPoint.x;
// let posY = point.y - this.lastPoint.y;
// let posXY = Math.abs(posX) + Math.abs(posY);
// while(posXY > 6) {
// Math.abs(posX) > 3 && (posX += (posX < 0 ? 3 : -3));
// Math.abs(posY) > 3 && (posY += (posY < 0 ? 3 : -3));
// this._clearCircle({x: point.x - posX, y: point.y - posY}, r);
// console.log(this.lastPoint, point, {x: point.x - posX, y: point.y - posY}, posX, posY)
// posXY = Math.abs(posX) + Math.abs(posY);
// }
// }
ctx.draw(true);
this.calculateClearPercent(x1, y1, x2, y2);
},
// BUG: 由于getImageData接口限制,无法真正获取被刮开的区域占比(当重复刮空白区域时重复计算,目前无法判断)
calculateClearPercent: function calculateClearPercent(x1, y1, x2, y2) {
var lx = x2 - x1;
var ly = y2 - y1;
var l = Math.sqrt(lx * lx + ly * ly);
this.clearPercent += l * this.props.lineWidth / this.area;
},
fadeOut: function fadeOut() {
this.setData({
animationClass: 'fade-out'
});
},
onFinish: function onFinish() {
this.props.onFinish();
}
// _clearCircle(point, r) {
// const r2 = r * r;
// for (let x = 0; x <= r; x++) {
// for (let y =0; y <= r; y++) {
// if (x*x + y*y <= r2) {
// this.ctx.clearRect(point.x + x, point.y + y, 1, 1);
// this.ctx.clearRect(point.x - x, point.y + y, 1, 1);
// this.ctx.clearRect(point.x + x, point.y - y, 1, 1);
// this.ctx.clearRect(point.x - x, point.y - y, 1, 1);
// } else {
// break; // 终止内层循环
// }
// }
// }
// }
}
});