实现移动端刮刮卡
实现移动端刮刮卡
原理说明:在现有想要展示的奖券上,绝对定位覆盖一层canvas,canvas显示内容为设计给的蒙层图片,当用户手机滑动屏幕,监听
touchmove事件,获取用户手指相对于canvas左上角的坐标,不停地绘制圆,并且将globalCompositeOperation属性设置为destination-out(表示新绘制的图像和canvas原图像相交的地方变为透明)。如果想实时获取已绘制区域面积占总canvas面积大小,需要获取canvas全部的像素点,然后比对透明度是否为0,算出透明度为零的像素点和总像素点的比例。
1.实现基本的html结构
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
html,body {
margin: 0;
padding: 0;
}
.card {
width: 200px;
height: 80px;
position: relative;
}
.coupon {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
background: red;
}
#canvas {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 2;
display: block;
}
</style>
</head>
<body>
<div class="card">
<div class="coupon"></div>
<canvas id="canvas"></canvas>
</div>
</body>
</html>
2.绘制canvas封面
const img = new Image();
img.src = './lake.jpg';
img.onload = function () {
config.context.drawImage(img, 0, 0, img.width, img.height, 0, 0, config.width, config.height);
}
context.drawImage(img, sx, sy, swidth, sheight, x, y, width, height);
| 参数 | 描述 |
|---|---|
| img | 要使用的图片源 |
| sx | 开始剪切图片的x坐标位置 |
| sy | 开始剪切图片的y坐标位置 |
| swidth | 被剪切的图片的宽度 |
| sheight | 被剪切的图片的高度 |
| x | 在画布上放置图片x坐标位置 |
| y | 在画布上放置图片y坐标的位置 |
| width | 要使用canvas画布的宽度 |
| height | 要使用canvas画布的高度 |
3.画小圆
config.context.beginPath();
config.context.fillStyle = '#FFFFFF';
config.context.globalCompositeOperation = "destination-out";
config.context.arc(x, y, config.radius, 0, 2 * Math.PI);
config.context.fill();
4.计算已经画的面积
const imageData = config.context.getImageData(0, 0, config.width, config.height).data;
const len = imageData.length;
let transparentPix = 0;
for (let i = 3; i < len; i += 4) {
if (imageData[i] === 0) {
transparentPix++;
}
}
return (4 * transparentPix)/len;
context.getImageData(x,y,width,height);
| 参数 | 描述 |
|---|---|
| x | 开始获取像素点左上角位置的x坐标 |
| y | 开始获取像素点左上角位置的y坐标 |
| width | 开始获取像素点矩形区域的宽度 |
| height | 开始获取像素点举行区域的高度 |
getImageData会返回ImageData对象,该对象包含画布指定矩形像素数据和指定举行的宽高,对于ImageData.data中的数据,每四个一组分别表示为RGBA,比如:
第一个像素点的RGBA分别为:
R: ImageData.data[0];
G: ImageData.data[1];
B: ImageData.data[2];
A: ImageData.data[3];
第二个像素点的RGBA分别为:
R: ImageData.data[4];
G: ImageData.data[5];
B: ImageData.data[6];
A: ImageData.data[7];
以此类推
当在canvas上画圆,跟canvas画布重叠的部分由于我们设置了globalCompositeOperation=destination-out,所以重叠部分会变透明,也就是RGBA中的A会变成零,我们只需要找到画布中A=0的像素点,占总像素点的比例,也就找到了已经刮开的面积了
5.性能优化
一个画布的像素点可能会特别多,会有十几万个点,所以比较挨个遍历是比较耗费性能的,我们可以优化一下,目前我个人有两个优化方案:
5.1 节流执行
touchmove事件可能会执行的特别多,我们可以将执行间隔通过节流,控制在500ms左右,
5.2 缩小计算像素点面积
大部分用户刮卡的时候,可能会从中间开始刮,然后范围逐渐扩大,我们可以通过这个特性来进一步优化,把计算像素点点是否透明的范围缩小至用户touchmove的点的最左边最右最上最下来计算。
6.最终代码
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
html,body {
margin: 0;
padding: 0;
}
.con {
width: 200px;
height: 80px;
position: relative;
}
.ch {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
background: red;
}
#canvas {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 2;
display: block;
}
</style>
</head>
<body>
<div class="con">
<div class="ch"></div>
<canvas id="canvas"></canvas>
</div>
<script>
const config = {
context: null,
canvas: null,
width: 200,
height: 80,
left: 0,
top: 0,
radius: 10,
delay: 500,
percent: 0.5
};
function setInitialInfo() {
config.canvas = document.getElementById('canvas');
config.context = config.canvas.getContext('2d');
const canvasPosition = getCanvasPosition();
config.left = canvasPosition.x;
config.top = canvasPosition.y;
}
function setCanvasInfo() {
config.canvas.width = config.width;
config.canvas.height = config.height;
}
function buildCoverMask() {
const img = new Image();
img.src = './lake.jpg';
img.onload = function () {
config.context.drawImage(img, 0, 0, img.width, img.height, 0, 0, config.width, config.height);
}
}
function drawCircle(x, y) {
config.context.beginPath();
config.context.fillStyle = '#FFFFFF';
config.context.globalCompositeOperation = "destination-out";
config.context.arc(x, y, config.radius, 0, 2 * Math.PI);
config.context.fill();
}
function bindEvent() {
config.canvas.addEventListener('touchmove', handleTouchMove);
}
function handleTouchMove(e) {
const x = e.touches[0].pageX;
const y = e.touches[0].pageY;
drawCircle(x - config.left, y - config.top);
throttle(handleComputedPercentage, config.delay);
}
function handleComputedPercentage() {
const percent = computedPercentage();
if (percent >= config.percent) {
config.canvas.style.display = 'none';
console.log('开奖了');
}
}
function computedPercentage() {
const imageData = config.context.getImageData(0, 0, config.width, config.height).data;
const len = imageData.length;
let transparentPix = 0;
for (let i = 3; i < len; i += 4) {
if (imageData[i] === 0) {
transparentPix++;
}
}
return (4 * transparentPix)/len;
}
function throttle(func, during, context) {
if (!func.timer) {
func.timer = setTimeout(function() {
func();
func.timer = undefined;
}, during);
}
}
function getCanvasPosition() {
const info = config.canvas.getBoundingClientRect();
return {
x: info.x,
y: info.y
};
}
function init() {
setInitialInfo();
setCanvasInfo();
buildCoverMask();
bindEvent();
}
init();
</script>
</body>
</html>
浙公网安备 33010602011771号