近年來由于移動(dòng)設(shè)備對(duì)HTML5的較好支持,經(jīng)常有活動(dòng)用刮獎(jiǎng)的效果,最近也在看H5方面的內(nèi)容,就自己實(shí)現(xiàn)了一個(gè),現(xiàn)分享出來跟大家交流。
1、效果
2、原理
原理很簡(jiǎn)單,就是在刮獎(jiǎng)區(qū)添加兩個(gè)canvas,第一個(gè)canvas用于顯示刮開后顯示的內(nèi)容,可以是一張圖片或一個(gè)字符串,第二個(gè)canvas用于顯示涂層,可以用一張圖片或用純色填充,第二個(gè)canvas覆蓋在第一個(gè)canvas上面。
當(dāng)在第二個(gè)canvas上點(diǎn)擊或涂抹(點(diǎn)擊然后拖動(dòng)鼠標(biāo))時(shí),把點(diǎn)擊區(qū)域變?yōu)橥该?,這樣就可以看到第一個(gè)canvas上的內(nèi)容,即實(shí)現(xiàn)了刮獎(jiǎng)效果。
3、實(shí)現(xiàn)
(1)定義Lottery類
function Lottery(id, cover, coverType, width, height, drawPercentCallback) { this.conId = id; this.conNode = document.getElementById(this.conId); this.cover = cover || '#CCC'; this.coverType = coverType || 'color'; this.background = null; this.backCtx = null; this.mask = null; this.maskCtx = null; this.lottery = null; this.lotteryType = 'image'; this.width = width || 300; this.height = height || 100; this.clientRect = null; this.drawPercentCallback = drawPercentCallback; }
對(duì)參數(shù)解釋一下:
-
id:刮獎(jiǎng)容器的id
-
cover:涂層內(nèi)容,可以為圖片地址或顏色值,可空,默認(rèn)為 #ccc
-
coverType:涂層類型,值為 image 或 color,可空,默認(rèn)為 color
-
width:刮獎(jiǎng)區(qū)域?qū)挾龋J(rèn)為300px,可空
-
height:刮獎(jiǎng)區(qū)域高度,默認(rèn)為100px,可空
-
drawPercentCallback:刮開的區(qū)域百分比回調(diào),可空
然后還定義了幾個(gè)需要用到的變量:
-
background:第一個(gè)canvas元素
-
backCtx:background元素的2d上下文(context)
-
mask:第二個(gè)canvas元素
-
maskCtx:mask元素的2d上下文(context)
-
lottery:刮開后顯示的內(nèi)容,可以為圖片地址或字符串
-
lotteryType:刮開后顯示的內(nèi)容類型,值為 image 或 text,要跟
lottery
匹配 -
clientRect:用于記錄mask元素的 getBoundingClientRect() 值
(2)添加二個(gè)canvas到刮獎(jiǎng)容器,并獲取2d上下文
this.background = this.background || this.createElement('canvas', { style: 'position:absolute;left:0;top:0;' }); this.mask = this.mask || this.createElement('canvas', { style: 'position:absolute;left:0;top:0;' }); if (!this.conNode.innerHTML.replace(/[wW]| /g, '')) { this.conNode.appendChild(this.background); this.conNode.appendChild(this.mask); this.clientRect = this.conNode ? this.conNode.getBoundingClientRect() : null; this.bindEvent(); } this.backCtx = this.backCtx || this.background.getContext('2d'); this.maskCtx = this.maskCtx || this.mask.getContext('2d');
這里用于了createElement工具方法,另外還綁定了事件,后面介紹。
(3)繪制第一個(gè)canvas
第一個(gè)canvas分兩種類型,image 和 string,如果是圖片直接用canvas的drawImage就可以了,如果是string,要先用白色填充,然后在上下左右居中的地方繪制字符串,代碼如下:
if (this.lotteryType == 'image') { var image = new Image(), _this = this; image.onload = function () { _this.width = this.width; _this.height = this.height; _this.resizeCanvas(_this.background, this.width, this.height); _this.backCtx.drawImage(this, 0, 0); } image.src = this.lottery; } else if (this.lotteryType == 'text') { this.width = this.width; this.height = this.height; this.resizeCanvas(this.background, this.width, this.height); this.backCtx.save(); this.backCtx.fillStyle = '#FFF'; this.backCtx.fillRect(0, 0, this.width, this.height); this.backCtx.restore(); this.backCtx.save(); var fontSize = 30; this.backCtx.font = 'Bold ' + fontSize + 'px Arial'; this.backCtx.textAlign = 'center'; this.backCtx.fillStyle = '#F60'; this.backCtx.fillText(this.lottery, this.width / 2, this.height / 2 + fontSize / 2); this.backCtx.restore(); }
(4)繪制第二個(gè)canvas
第二個(gè)canvas也分 image 或 color 填充兩種情況。
這里有一個(gè)難點(diǎn),就是如何把鼠標(biāo)點(diǎn)擊區(qū)域變成透明的呢?答案在這里:developer.mozilla.org/en/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
即我們要把 maskCtx的 globalCompositeOperation 設(shè)置為 destination-out ,詳細(xì)的用法請(qǐng)參考上面給出的鏈接。
因此,繪制第二個(gè)canvas的代碼如下:
this.resizeCanvas(this.mask, this.width, this.height); if (this.coverType == 'color') { this.maskCtx.fillStyle = this.cover; this.maskCtx.fillRect(0, 0, this.width, this.height); this.maskCtx.globalCompositeOperation = 'destination-out'; } else if (this.coverType == 'image'){ var image = new Image(), _this = this; image.onload = function () { _this.maskCtx.drawImage(this, 0, 0); _this.maskCtx.globalCompositeOperation = 'destination-out'; } image.src = this.cover; }
這里resizeCanvas是改變canvas大小的工具方法。
(5)綁定事件
繪制完成后,要給第二個(gè)canvas綁定事件。這里分了移動(dòng)設(shè)備和PC-WEB兩處情況。移動(dòng)設(shè)備是 touchstart 和 touchmove 事件,對(duì)應(yīng)的PC-WEB是keydown 和 mousemove事件,另外PC-WEB方式下,要給document綁定一個(gè)mouseup事件,用來判斷鼠標(biāo)是否按下。代碼如下:
bindEvent: function () { var _this = this; var device = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase())); var clickEvtName = device ? 'touchstart' : 'mousedown'; var moveEvtName = device? 'touchmove': 'mousemove'; if (!device) { var isMouseDown = false; document.addEventListener('mouseup', function(e) { isMouseDown = false; }, false); } this.mask.addEventListener(clickEvtName, function (e) { isMouseDown = true; var docEle = document.documentElement; if (!_this.clientRect) { _this.clientRect = { left: 0, top:0 }; } var x = (device ? e.touches[0].clientX : e.clientX) - _this.clientRect.left + docEle.scrollLeft - docEle.clientLeft; var y = (device ? e.touches[0].clientY : e.clientY) - _this.clientRect.top + docEle.scrollTop - docEle.clientTop; _this.drawPoint(x, y); }, false); this.mask.addEventListener(moveEvtName, function (e) { if (!device && !isMouseDown) { return false; } var docEle = document.documentElement; if (!_this.clientRect) { _this.clientRect = { left: 0, top:0 }; } var x = (device ? e.touches[0].clientX : e.clientX) - _this.clientRect.left + docEle.scrollLeft - docEle.clientLeft; var y = (device ? e.touches[0].clientY : e.clientY) - _this.clientRect.top + docEle.scrollTop - docEle.clientTop; _this.drawPoint(x, y); }, false); }
這里在事件中取出了鼠標(biāo)坐標(biāo),調(diào)用了drawPoint進(jìn)行了繪制,下面會(huì)講到。
(6)繪制點(diǎn)擊和涂抹區(qū)域
這里用到了canvas的徑向漸變,在鼠標(biāo)從標(biāo)處繪制一個(gè)圓形,代碼如下:
drawPoint: function (x, y) { this.maskCtx.beginPath(); var radgrad = this.maskCtx.createRadialGradient(x, y, 0, x, y, 30); radgrad.addColorStop(0, 'rgba(0,0,0,0.6)'); radgrad.addColorStop(1, 'rgba(255, 255, 255, 0)'); this.maskCtx.fillStyle = radgrad; this.maskCtx.arc(x, y, 30, 0, Math.PI * 2, true); this.maskCtx.fill(); if (this.drawPercentCallback) { this.drawPercentCallback.call(null, this.getTransparentPercent(this.maskCtx, this.width, this.height)); } }
(7)涂抹區(qū)域百分比
在很多時(shí)候,我們還需要知道用戶涂抹了多少然后進(jìn)行下一步交互,如當(dāng)用戶涂抹了80%后,才允許下一張顯示。
這個(gè)百分比如何計(jì)算呢?其實(shí)很簡(jiǎn)單,我們可以用getImageData方法到畫布上指定矩形的像素?cái)?shù)據(jù),由于每個(gè)像素都是用rgba表示的,而涂抹過的區(qū)域是透明的,所以我們只需要判斷alpha通道的值就可以知道是否透明。代碼如下:
getTransparentPercent: function(ctx, width, height) { var imgData = ctx.getImageData(0, 0, width, height), pixles = imgData.data, transPixs = []; for (var i = 0, j = pixles.length; i < j; i += 4) { var a = pixles[i + 3]; if (a < 128) { transPixs.push(i); } } return (transPixs.length / (pixles.length / 4) * 100).toFixed(2); }
(8)調(diào)用入口init
最后再提供一個(gè)入口用來進(jìn)行繪制和重置,代碼如下:
init: function (lottery, lotteryType) { this.lottery = lottery; this.lotteryType = lotteryType || 'image'; this.drawLottery(); }
至此,關(guān)鍵代碼全部講解完了。