公司國慶搞了個集卡、抽獎小活動。抽獎需要刮刮卡的效果,感覺 css 是實現不了。看我使用 canvas 如何實現刮刮卡效果。
廢話不多說,線上效果 jsrun-測試地址 、 lilnong.top-測試地址
實現方案都有什麼
-
clearRect
這是我第一個找到的 API,作用是清除一個矩形區域內的內容。缺點是矩形 -
globalCompositeOperation
基於上面的缺點,我發現了他destination-out
是我需要的,作用是改變canvas圖形的混合模式
我們期望有什麼功能
- 底部內容自定義(我們將 canvas 浮在內容上即可)
- 自定義蒙版顏色(canvas 繪製畫布設置一下顏色即可
context.fillStyle = color;
) - 自定義蒙版圖片(使用
drawImage
來繪製畫布) -
自定義筆觸
-
clearRect
爲矩形 -
arc
配合globalCompositeOperation
來實現圓形 -
drawImage
配合globalCompositeOperation
來自定義筆觸
-
- 自動刮開&自動刮開軌道生成器
- 刮開面積佔比
底部內容自定義
兩個方案的差距不大,根據個人喜好選擇即可。
-
方案一 DOM 層級(擁有更多的能力)效果查看-傳送門
<style type="text/css"> #app1{width:300px;height:100px;position: relative;border: 1px solid #f00;} #app1Content{width:300px;height:100px;position: absolute;left:0;top:0;z-index: 1} #app1Mark{width:300px;height:100px;position: absolute;left:0;top:0;z-index: 2} </style> <div id="app1"> <div id="app1Content" style="background:url(https://www.lilnong.top/static/img/ml-btn6.png) left center no-repeat">我是自定義內容</div> <canvas id="app1Mark"></canvas> </div>
-
方案二 canvas 背景圖(使用簡單)效果查看-傳送門
<style type="text/css"> #app2{width:300px;height:100px;position: relative;border: 1px solid #f00;} #app2Mark{width:300px;height:100px;position: absolute;left:0;top:0;z-index: 2} </style> <div id="app2"> <canvas id="app2Mark" style="background:url(https://www.lilnong.top/static/img/ml-btn6.png) left center no-repeat"></canvas> </div>
蒙版顏色
ctx.fillStyle = '#0cc';
主要通過這個來設置填充樣式。效果查看-傳送門
感興趣可以去看看,我這裏沒用到就不細說了。傳送門 - Canvas API中文網
-
色值填充
- RGB色值:
RGB(255, 0, 0)
- HSL色值:
HSL(360, 100%, 50%)
- RGBA色值:
RGBA(255, 0, 0, .5)
- HSLA色值:
HSLA(360, 100%, 50%, .5)
- HEX色值:
#FF0000
- RGB色值:
- 線性漸變
createLinearGradient()
- 徑向漸變
createRadialGradient()
- 圖案
createPattern
ctx = app3Mark.getContext('2d');
app3Mark.width = 300;
app3Mark.height = 100;
ctx.moveTo(0, 0);
ctx.lineTo(app3Mark.width, 0);
ctx.lineTo(app3Mark.width, app3Mark.height);
ctx.lineTo(0, app3Mark.height);
ctx.fillStyle = '#0cc';
ctx.fill();
蒙版圖片
drawImage()
使用這個方法來繪製一個圖片,需要等圖片onload之後再使用
效果查看-傳送門
ctx = app4Mark.getContext('2d');
app4Mark.width = 300;
app4Mark.height = 100;
var imageFill = new Image();
imageFill.src = 'https://www.lilnong.top/static/img/defaultmatch.png'
app4MarkCtx = ctx;
imageFill.onload = function(){
app4MarkCtx.drawImage(this,0,0,300,100)
}
刮獎筆觸自定義(重要)
-
clearRect
方案 效果傳送門context.clearRect(x, y, width, height);
將畫布某一塊矩形區域內容清除。
使用起來還是蠻簡單的,糾正一下中心點即可。但是只能是矩形,看上去鋸齒也很嚴重。 -
globalCompositeOperation
方案 效果傳送門
設置繪製時,圖形的混合模式。傳送門-canvas api,其實好多我都沒用過,太難了,PS中的我不怎麼會用。destination-*
區別是動作的主體是新內容還是原內容。source-*
系列是新內容,而destination-*
系列動作主體是原內容。-
source-over
默認值,表現爲覆蓋。純視覺覆蓋。 -
source-in
表現爲在原內容上繪製,顯示重疊部分,透明度疊加。 -
source-out
與source-in
對應,表現爲減去原內容。 -
source-atop
表現爲在原內容上繪製,使用原內容透明度。 -
destination-over
表現爲原內容在上方,新內容在下方繪製。 -
destination-in
同理,表現爲透明度疊加,顯示重疊部分。 -
destination-out
同理,隱藏原內容和新內容重疊的部分。 -
destination-atop
原內容只顯示和新內容重疊的部分,同時新內容在下方顯示 -
lighter
自然光混合效果 -
copy
只顯示新內容 -
xor
重疊區域爲透明 -
multiply
正片疊底。頂層的像素與底層的對應像素相乘。結果是一幅更黑暗的圖畫 -
screen
濾色。像素反轉,相乘,然後再反轉。最終得到更淡的圖形(和 multiply 相反) -
overlay
疊加。 multiply 和 screen 組合效果。基礎圖層上暗的部分更暗,亮的部分更亮 -
darken
變暗。保留原內容和新內容中最暗的像素 -
lighten
變亮。保留原內容和新內容中最亮的像素 -
color-dodge
顏色減淡。底部圖層色值除以頂部圖層的反相色值 -
color-burn
顏色加深。底部圖層的色值除以頂部圖層色值,得到的結果再反相 -
hard-light
強光。類似 overlay,是 multiply 和 screen 組合效果。只不過底層和頂層位置交換下 -
soft-light
柔光。hard-light 的柔和版本。純黑色或白色不會生成爲純黑色或白色。 -
difference
差異。頂層色值減去底層色值的絕對值。如果都是白色,則最後是黑色,因爲值爲0;什麼時候是白色呢,例如RGB(255,0,0)和RGB(0,255,255),色值相減後絕對值是RGB(255,255,255)。 -
exclusion
排除。類似difference,不過對比度較低。 -
hue
色調。最終的顏色保留底層的亮度和色度,同時採用頂層的色調。 -
saturation
飽和度。最終的顏色保留底層的亮度和色調,同時採用頂層的色度。 -
color
色值。最終的顏色保留底層的亮度,同時採用頂層的色調和色度。 -
luminosity
亮度。最終的顏色保留底層的色調和色度,同時採用頂層的亮度。
-
軌道生成器
先來看我們刪除的代碼。其實依靠的就是x
和y
的座標值。那麼我們抽一個clear的方法,傳遞座標。把每次單擊的點都記錄一下,然後循環把點推進去不就是自動的了嗎?
app6Mark.addEventListener('mousedown', function(e){
app6MarkMouseDownTag = 1
app6MarkCtx.fillText('lilnong.top', e.offsetX - 50, e.offsetY+10);
})
刮開開面積佔比
-
getImageData
返回一個ImageData
對象,其中包含Canvas畫布部分或完整的像素點信息
。
四個值標識一個像素點信息分別爲 (r,g,b,a)。這裏我們只判斷透明通道即可 - 之後我們統計
通道透明數/總循環數
就是刮開面積佔比
var imageData = ctx.getImageData(0, 0, 300, 200).data;
var eraseTotal = 0;
var step = 1 * 4;//步長精度
for (var i = 3, len = imageData.length; i < len; i += step) {
if (imageData[i] === 0) {
eraseTotal++;
}
}
eraseRate.innerText = eraseTotal/(len/step)
微信公衆號:前端linong
總結
- 這個可以考慮不兼容canvas的情況,單擊時直接將上面的層透明掉。
-
mousemove
要注意節流 - 使用透明圖片自定義筆觸還是蠻有意思的。
- 好了,今天就到這裏吧。