You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
7.6 KiB
280 lines
7.6 KiB
<template>
|
|
<view class="scraping-happy" id="container">
|
|
<canvas canvas-id="scraping-happy" class="scraping__canvas" :disable-scroll="true" @touchstart="touchstart"
|
|
@touchmove="touchmove" @touchend="touchend" />
|
|
<cover-view class="scraping__view">
|
|
{{ showText }}
|
|
</cover-view>
|
|
<slot></slot>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
/** @name 水印配置默认值 **/
|
|
const WATERMARK = {
|
|
text: '刮一刮',
|
|
fontSize: 14,
|
|
color: '#C5C5C5',
|
|
}
|
|
/** @name 标题配置默认值 **/
|
|
const TITLE = {
|
|
text: '刮一刮',
|
|
fontSize: 16,
|
|
color: '#333',
|
|
}
|
|
/**
|
|
* @name 涂层配置默认值
|
|
* @property { string } color 涂层颜色
|
|
* @property { number } drawSize 清除涂层的画笔大小
|
|
*/
|
|
const MASK = {
|
|
color: '#DDDDDD',
|
|
drawSize: 20,
|
|
}
|
|
/** @name 容错值,解决部分机型涂层没有覆盖满的情况,主要原因是由于像素尺寸不同导致的,应尽可能让width与height保持整数 **/
|
|
const TOLERANT = 3;
|
|
|
|
let ctx = null;
|
|
|
|
export default {
|
|
props: {
|
|
/** @name 涂层设置 **/
|
|
mask: {
|
|
type: [String, Object],
|
|
},
|
|
/** @name 水印设置 **/
|
|
watermark: {
|
|
type: [String, Object],
|
|
},
|
|
/** @name 提示文字 **/
|
|
title: {
|
|
type: [String, Object],
|
|
},
|
|
/** @name 刮开百分之多少直接消除图层,为0的时候不消除 **/
|
|
percentage: {
|
|
type: Number,
|
|
default: 40,
|
|
},
|
|
result: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
width: 0,
|
|
height: 0,
|
|
touchX: 0,
|
|
touchY: 0,
|
|
showText: ''
|
|
}
|
|
},
|
|
computed: {
|
|
maskSetting() {
|
|
return {
|
|
...MASK,
|
|
...(typeof this.mask === 'object' ? this.mask : {
|
|
text: this.mask
|
|
}),
|
|
}
|
|
},
|
|
watermarkSetting() {
|
|
return {
|
|
...WATERMARK,
|
|
...(typeof this.watermark === 'object' ? this.watermark : {
|
|
text: this.watermark
|
|
}),
|
|
};
|
|
},
|
|
titleSetting() {
|
|
return {
|
|
...TITLE,
|
|
...(typeof this.title === 'object' ? this.title : {
|
|
text: this.title
|
|
}),
|
|
};
|
|
}
|
|
},
|
|
mounted() {
|
|
// 获取画布实例
|
|
ctx = uni.createCanvasContext('scraping-happy', this);
|
|
this.init();
|
|
},
|
|
methods: {
|
|
/** @name 初始化 **/
|
|
init() {
|
|
const query = uni.createSelectorQuery().in(this);
|
|
query
|
|
.select('#container')
|
|
.boundingClientRect(({
|
|
width,
|
|
height
|
|
}) => {
|
|
this.width = width;
|
|
this.height = height;
|
|
setTimeout(() => {
|
|
this.initCanvas();
|
|
this.showText = this.result
|
|
}, 20)
|
|
})
|
|
.exec();
|
|
},
|
|
/** @name 初始化canvas状态 **/
|
|
initCanvas() {
|
|
const {
|
|
width,
|
|
height
|
|
} = this;
|
|
// 清空矩形内容
|
|
ctx.clearRect(0, 0, width, height);
|
|
// 设置画笔颜色
|
|
ctx.setFillStyle(this.maskSetting.color);
|
|
// 绘制矩形
|
|
ctx.fillRect(0, 0, width, height + TOLERANT);
|
|
// 绘制水印
|
|
this.drawWatermark();
|
|
// 绘制提示文字
|
|
this.drawTitle();
|
|
// 绘制到canvas身上
|
|
ctx.draw();
|
|
},
|
|
/** @name 绘制水印 **/
|
|
drawWatermark() {
|
|
if (!this.watermarkSetting.text) return;
|
|
// 保存当前的绘图上下文
|
|
ctx.save();
|
|
// 旋转
|
|
ctx.rotate((-10 * Math.PI) / 180);
|
|
// 水印具体绘制过程
|
|
const {
|
|
width,
|
|
height
|
|
} = this;
|
|
const watermarkWidth = this.watermarkSetting.text.length * this.watermarkSetting.fontSize;
|
|
let x = 0;
|
|
let y = 0;
|
|
let i = 0;
|
|
while ((x <= width * 5 || y <= height * 5) && i < 300) {
|
|
ctx.setFillStyle(this.watermarkSetting.color);
|
|
ctx.setFontSize(this.watermarkSetting.fontSize);
|
|
ctx.fillText(this.watermarkSetting.text, x, y);
|
|
x += watermarkWidth + watermarkWidth * 1.6;
|
|
if (x > width && y <= height) {
|
|
x = -Math.random() * 100;
|
|
y += this.watermarkSetting.fontSize * 3;
|
|
}
|
|
i++;
|
|
}
|
|
ctx.restore();
|
|
},
|
|
/** @name 绘制提示文字 **/
|
|
drawTitle() {
|
|
if (!this.titleSetting.text) return;
|
|
ctx.setTextAlign('center');
|
|
ctx.setTextBaseline('middle');
|
|
ctx.setFillStyle(this.titleSetting.color);
|
|
ctx.setFontSize(this.titleSetting.fontSize);
|
|
ctx.fillText(this.titleSetting.text, this.width / 2, this.height / 3);
|
|
},
|
|
/** @name 触摸事件 **/
|
|
touchstart(e) {
|
|
this.touchX = e.touches[0].x;
|
|
this.touchY = e.touches[0].y;
|
|
},
|
|
async touchmove(e) {
|
|
// 把画笔到画布中的指定点
|
|
ctx.moveTo(this.touchX, this.touchY);
|
|
// 清除涂层
|
|
ctx.clearRect(this.touchX, this.touchY, this.maskSetting.drawSize, this.maskSetting.drawSize);
|
|
ctx.draw(true);
|
|
// 记录移动点位
|
|
this.touchX = e.touches[0].x;
|
|
this.touchY = e.touches[0].y;
|
|
|
|
// if (this.percentage > 0) {
|
|
// const clearPercent = await this.getClearMaskPercent();
|
|
// }
|
|
},
|
|
async touchend() {
|
|
if (this.percentage > 0) {
|
|
const clearPercent = await this.getClearMaskPercent();
|
|
if (clearPercent >= this.percentage) {
|
|
ctx.moveTo(0, 0);
|
|
ctx.clearRect(0, 0, this.width, this.height);
|
|
ctx.stroke();
|
|
ctx.draw(true);
|
|
this.$emit('complete')
|
|
}
|
|
}
|
|
},
|
|
/** @name 计算被清除的涂层百分比 **/
|
|
getClearMaskPercent() {
|
|
return new Promise(resolve => {
|
|
uni.canvasGetImageData({
|
|
canvasId: 'scraping-happy',
|
|
x: 0,
|
|
y: 0,
|
|
width: this.width,
|
|
height: this.height,
|
|
success: res => {
|
|
// 区域内所有点的像素信息,它是一个数组,数组中每 "4" 项表示一个点的 rgba 值
|
|
const allPointPixels = res.data;
|
|
// 储存被清除的点-点的透明度
|
|
const clearPoint = [];
|
|
// 取透明度来判断,如果透明度值小于一半,则判断为该点已经被清除
|
|
for (let i = 0; i < allPointPixels.length; i += 4) {
|
|
if (allPointPixels[i + 3] < 128) {
|
|
clearPoint.push(allPointPixels[i + 3]);
|
|
}
|
|
}
|
|
// 已被清除的百分比 = 清除的点 / 全部的点
|
|
const percent = (
|
|
(clearPoint.length / (allPointPixels.length / 4)) *
|
|
100
|
|
).toFixed(2);
|
|
resolve(percent);
|
|
},
|
|
fail: e => {
|
|
console.log('canvasGetImageData', e);
|
|
},
|
|
}, this);
|
|
});
|
|
},
|
|
/** @name 重置 **/
|
|
reset() {
|
|
this.initCanvas();
|
|
this.touchX = 0;
|
|
this.touchY = 0;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style>
|
|
.scraping-happy {
|
|
width: 100%;
|
|
height: 200rpx;
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.scraping__canvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: absolute;
|
|
z-index: 10;
|
|
/* background-color: red; */
|
|
display: inline-block;
|
|
}
|
|
|
|
.scraping__view {
|
|
position: absolute;
|
|
z-index: 1;
|
|
color: #f29100;
|
|
font-size: 20px;
|
|
font-weight: bold;
|
|
letter-spacing: 8px;
|
|
}
|
|
</style> |