刮樂
分享一段用canvas和JS制作刮刮樂的代碼,JS部分去掉注釋不到20行代碼
效果圖
HTML
CSS
首先 需要繪制一塊Canvas蒙版 遮罩在圖片上
繪制蒙版
效果圖
然后利用canvas的globalCompositeOperation屬性 顯示原來(lái)的不在后來(lái)區(qū)域的部分
消除蒙版
效果圖
那么會(huì)出現(xiàn)一個(gè)問題 用戶需要刮開全部蒙版的話會(huì)很費(fèi)事(強(qiáng)迫癥傷不起 - -), 所以再加一段代碼
添加刮開70% 自動(dòng)刮開全部效果
整體代碼
最終效果
本demo主要運(yùn)用到globalCompositeOperation 畫布的一個(gè)功能 作用是設(shè)置或返回如何將一個(gè)源(新的)圖像繪制到目標(biāo)(已有)的圖像上,還有其余10種寫法
https://jsfiddle.net/jmogkq9d/3/
刮卡是大家非常熟悉的一種網(wǎng)頁(yè)交互元素了。實(shí)現(xiàn)刮涂層的效果,需要借助canvas來(lái)實(shí)現(xiàn),想必每個(gè)前端工程師都清楚。實(shí)現(xiàn)刮刮卡并不難,但其中卻涉及很多知識(shí)點(diǎn),掌握這些知識(shí)點(diǎn),有助于我們更深刻理解原理,對(duì)于提升舉一反三的能力很有幫助。本期以實(shí)現(xiàn)刮刮卡為例,分享下如何科學(xué)合理地封裝函數(shù),并對(duì)涉及的相關(guān)知識(shí)點(diǎn)進(jìn)行講解。
先看下最終效果:
實(shí)現(xiàn)刮刮卡都涉及到哪些知識(shí)點(diǎn)呢?
下面進(jìn)入本期分享的正式內(nèi)容。
為了滿足更多的場(chǎng)景需要,我們盡可能地提供更多的參數(shù),方便使用者。先從產(chǎn)品和UI的角度來(lái)思考下,一個(gè)刮刮卡可能需要哪些配置選項(xiàng)。
接下來(lái)再補(bǔ)充下技術(shù)配置選項(xiàng):
OK,確認(rèn)好以上配置參數(shù)后,就可以正式開工了。
項(xiàng)目目錄結(jié)構(gòu)如下:
頁(yè)面結(jié)構(gòu)很簡(jiǎn)單,div的background顯示結(jié)果,div里的canvas用來(lái)做涂層。
新建index.html,加入以下代碼(HTML模板代碼略過):
HTML代碼:
CSS代碼:
award.jpg用的是2倍圖,因此使用 background-size縮放回1倍顯示大小。
這里可以發(fā)現(xiàn),HTML中canvas的width、height與CSS中的width、height不一致。原因就是要適應(yīng)Retina 2倍屏幕。這里就涉及到了canvas畫布尺寸的知識(shí)點(diǎn)。
現(xiàn)在頁(yè)面顯示效果如下,結(jié)果圖像已顯示出來(lái):
知識(shí)點(diǎn)1:canvas元素尺寸與畫布尺寸
HTML中canvas的width、height是畫布大小,通俗來(lái)講就是canvas畫布的“繪制區(qū)域大小”,一定要跟元素的顯示大小區(qū)別開來(lái)。
我們的結(jié)果圖素材是750x280,所以要讓canvas完全繪制這張圖片,畫布大小也需要是750x280。
那么元素大小,就是canvas在頁(yè)面的“顯示大小”。通過CSS對(duì)canvas元素進(jìn)行寬高設(shè)置,使其正確的顯示。
新建scratchcard.js。
結(jié)合第1章節(jié)的需求分析,類的雛形如下:
使用對(duì)象的方式向函數(shù)傳參有很多優(yōu)點(diǎn):
使用Object.assign方法,可將傳遞進(jìn)來(lái)的config參數(shù)覆蓋默認(rèn)參數(shù)。傳遞的config中沒有的屬性,則使用默認(rèn)配置。
在index.html中引入scratchcard.js,在body最下邊插入script代碼:
刮刮卡的類使用起來(lái)非常方便,僅傳遞不使用默認(rèn)配置的值即可。
4.1 構(gòu)建ScratchCard原型
繼續(xù)編寫scratchcard.js:
這里設(shè)置了constructor: ScratchCard,僅僅是為了顯得更加嚴(yán)謹(jǐn),省略這一行也是沒有問題的。
由代碼中 prototype 和 constructor 引出第2個(gè)知識(shí)點(diǎn)。
知識(shí)點(diǎn)2:prototype、__proto__、constructor
先記住兩點(diǎn):
※由于JS中函數(shù)也是一種對(duì)象,所以函數(shù)也擁有__proto__和constructor屬性。
【__proto__】
__proto__屬性都是由一個(gè)對(duì)象指向一個(gè)對(duì)象,即指向它們的原型對(duì)象(也可以理解為父對(duì)象)。
它的作用就是當(dāng)訪問一個(gè)對(duì)象的屬性時(shí),如果該對(duì)象內(nèi)部不存在這個(gè)屬性,那么就會(huì)去它的__proto__屬性所指向的那個(gè)對(duì)象(父對(duì)象)里找,如果父對(duì)象也不存在這個(gè)屬性,則繼續(xù)在父對(duì)象的__proto__屬性所指向的對(duì)象(爺爺對(duì)象)里找,如果還沒找到,則繼續(xù)往上找,直到原型鏈頂端null。null為原型鏈的終點(diǎn)。
由以上這種通過__proto__屬性來(lái)連接對(duì)象直到null的一條鏈即為所謂的原型鏈。
【prototype】
prototype對(duì)象是函數(shù)所獨(dú)有的,它是從一個(gè)函數(shù)指向一個(gè)對(duì)象。它的含義是函數(shù)的原型對(duì)象,也就是由這個(gè)函數(shù)所創(chuàng)建的實(shí)例的原型對(duì)象。
因此,以上代碼中,demo.__proto__===Demo.prototype。
prototype屬性的作用就是:prototype包含的屬性和方法可被其創(chuàng)建的全部實(shí)例所共用。
【constructor】
constructor屬性也是對(duì)象獨(dú)有的,它是從一個(gè)對(duì)象指向一個(gè)函數(shù)。其含義就是指向該對(duì)象的構(gòu)造函數(shù)。所有函數(shù)最終的構(gòu)造函數(shù)都指向Function。
當(dāng)創(chuàng)建一個(gè)函數(shù)的時(shí)候,會(huì)同時(shí)自動(dòng)創(chuàng)建它的prototype對(duì)象,這個(gè)對(duì)象也會(huì)自動(dòng)獲得constructor屬性,并指向自己。
那么,為什么我們這里還要手動(dòng)設(shè)置constructor: ScratchCard呢?
原因就是我們用這樣的語(yǔ)法:
會(huì)導(dǎo)致自動(dòng)設(shè)置的constructor屬性值被覆蓋。在這種情況下,如果我們不特意設(shè)置constructor: ScratchCard的話,constructor則會(huì)指向Object。
4.2 實(shí)現(xiàn)canvas涂層
先添加以下代碼:
初始化代碼就是實(shí)現(xiàn)涂層的覆蓋。這里的關(guān)鍵邏輯是:如果設(shè)置了圖像涂層,則忽略純色涂層。
涉及到了canvas兩個(gè)API:
drawImage 用于繪制圖像。
fillRect 用于繪制矩形,在繪制之前要先設(shè)置筆刷,即通過fillStyle屬性設(shè)置顏色。
這段代碼是什么意思呢?
globalCompositeOperation就是第3個(gè)知識(shí)點(diǎn)。
知識(shí)點(diǎn)3:canvas的globalCompositeOperation
在w3school上可以查閱到該屬性的詳細(xì)說(shuō)明:
看上去好像有點(diǎn)懵逼難理解,其實(shí)就是類似于指定photoshop里兩個(gè)圖層怎么融合,比如誰(shuí)遮罩誰(shuí)、交叉部分消除、交叉部分顏色融合等等。
可以參看下w3school的圖示,藍(lán)色為目標(biāo)圖像,紅色為源圖像。
回到刮刮卡,圖片涂層是目標(biāo)圖像,目前源圖像還未設(shè)置,所以源圖像為全透明(源圖像的不透明的部分用來(lái)?yè)赋繕?biāo)圖像并呈現(xiàn)透明),所以目標(biāo)圖像(圖片涂層)全部顯示。
現(xiàn)在效果如下圖所示,涂層已經(jīng)覆蓋上了。
4.3 添加涂抹事件
涂抹事件,其實(shí)就是用touchstart、touchmove、touchend事件,為了順便兼容鼠標(biāo)操作,也把mousedown、mousemove、mouseup帶上。
修改代碼:
代碼很好理解,就是添加事件監(jiān)聽。當(dāng)按下的時(shí)候,把isDown設(shè)置為true,當(dāng)抬起的時(shí)候,把isDown設(shè)置為false。
可以看到addEventListener的第3個(gè)參數(shù){ passive: false },這是個(gè)什么鬼?這就是第4個(gè)知識(shí)點(diǎn)。
知識(shí)點(diǎn)4:addEventListener第三個(gè)參數(shù)的passive屬性
最開始,addEventListener() 的參數(shù)約定是這樣的:
三個(gè)屬性的默認(rèn)值都為 false。
為什么會(huì)多出個(gè)passive屬性呢?
為了防止頁(yè)面滾動(dòng),很多移動(dòng)端頁(yè)面都會(huì)監(jiān)聽 touchmove 等 touch 事件,像這樣:
由于 touchmove 事件對(duì)象的 cancelable 屬性為 true,也就是說(shuō)它的默認(rèn)行為可以被監(jiān)聽器通過 preventDefault() 方法阻止。那它的默認(rèn)行為是什么呢,通常來(lái)說(shuō)就是滾動(dòng)當(dāng)前頁(yè)面(還可能是縮放頁(yè)面),如果它的默認(rèn)行為被阻止了,頁(yè)面就必須靜止不動(dòng)。但瀏覽器無(wú)法預(yù)先知道一個(gè)監(jiān)聽器會(huì)不會(huì)調(diào)用 preventDefault(),它能做的只有等監(jiān)聽器執(zhí)行完后再去執(zhí)行默認(rèn)行為,而監(jiān)聽器執(zhí)行是要耗時(shí)的,有些甚至耗時(shí)很明顯,這樣就會(huì)導(dǎo)致頁(yè)面卡頓。即便監(jiān)聽器是個(gè)空函數(shù),也會(huì)產(chǎn)生一定的卡頓,畢竟空函數(shù)的執(zhí)行也會(huì)耗時(shí)。
當(dāng)設(shè)置了passtive為true,則會(huì)忽略代碼中的preventDefault(), 因此頁(yè)面會(huì)變得更流暢。如下演示,右側(cè)手機(jī)的頁(yè)面設(shè)置了passtive為true。
OK,那么問題來(lái)了?既然默認(rèn)是passive: false,為什么代碼里還要再多此一舉寫一遍呢?
答案在這里,來(lái)看chrome的官方說(shuō)明:
https://www.chromestatus.com/feature/5093566007214080
原文如下:
AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored).
意思是:addEventListener的option里,默認(rèn)passive是false。但是如果事件是 touchstart 或 touchmove的話,passive的默認(rèn)值則會(huì)變成true(所以preventDefault就會(huì)被忽略了)。
OK,原理講完了,我們還沒有把頁(yè)面的默認(rèn)滑動(dòng)行為阻止掉。不阻止的話,在滑動(dòng)刮刮卡的時(shí)候,頁(yè)面也會(huì)跟著滾動(dòng)。
4.4 阻止頁(yè)面滾動(dòng)
看完了4.3小節(jié),那么阻止頁(yè)面滾動(dòng)就很簡(jiǎn)單了。在index.html的script里加入以下代碼:
4.5 實(shí)現(xiàn)擦除效果
這里完善下_scratch方法,代碼如下:
邏輯大致如下:
需要說(shuō)明的是,乘以pixelRatio是為了適應(yīng)多倍屏幕。在本示例中,畫布尺寸是2倍尺寸,而坐標(biāo)是按照網(wǎng)頁(yè)元素的尺寸計(jì)算出來(lái)的,正好相差一倍,所以要乘以pixelRatio(pixelRatio=2)。
還記得4.2小節(jié)講的globalCompositeOperation么?當(dāng)設(shè)置為destination-out的時(shí)候,源圖像的非透明部分會(huì)摳去目標(biāo)圖像,因此實(shí)現(xiàn)了刮刮卡的刮涂層效果。
4.6 檢測(cè)涂層的透明部分占比
雖然刮涂層的效果實(shí)現(xiàn)了,但是還要實(shí)時(shí)檢測(cè)刮開了多少,來(lái)判斷是否完成刮刮卡。
繼續(xù)修改代碼:
新增了3個(gè)方法:
_scratchAll: 清空涂層(全部刮開)。如果設(shè)置的fadeOut(淡出時(shí)間),則通過CSS動(dòng)畫,將canvas做淡出效果,然后再清除涂層。如果fadeOut為0,則直接清除涂層。
_clear:清除涂層。很簡(jiǎn)單,直接畫一個(gè)鋪滿畫布的矩形即可。
_getFilledPercentage:計(jì)算刮開區(qū)域的百分比。通過遍歷canvas每個(gè)像素點(diǎn),計(jì)算全透明像素的占比。
這里就涉及到了第5個(gè)知識(shí)點(diǎn)。
知識(shí)點(diǎn)5:canvas的ImageData
利用canvas的getImageData()方法可以獲取到全部的像素點(diǎn)信息,返回?cái)?shù)組格式。數(shù)組中,并不是每個(gè)元素代表一個(gè)像素的信息,而是每4個(gè)元素為一個(gè)像素的信息。例如:
data[0]=像素1的R值,紅色(0-255)
data[1]=像素1的G值,綠色(0-255)
data[2]=像素1的B值,藍(lán)色(0-255)
data[3]=像素1的A值,alpha 通道(0-255; 0 透明,255完全可見)
data[4]=像素2的R值,紅色(0-255)
...
本例的透明度不存在中間值,所以就可以認(rèn)為alpha小于128即為透明。
4.7 注意事項(xiàng)
由于瀏覽器安全限制,Image不能讀取本地圖片,因此需要部署在服務(wù)端,以http協(xié)議瀏覽本項(xiàng)目。
以上就是本期分享的全部?jī)?nèi)容了。完整代碼請(qǐng)前往GitHub:
https://github.com/Yuezi32/scratchcard
看似簡(jiǎn)單的刮刮卡卻隱藏了這么多的知識(shí)點(diǎn),你都掌握了么?
XSS(Cross Site Scripting)攻擊全稱跨站腳本攻擊,為了不與 CSS(Cascading Style Sheets)名詞混淆,故將跨站腳本攻擊簡(jiǎn)稱為 XSS,XSS 是一種常見 web 安全漏洞,它允許惡意代碼植入到提供給其它用戶使用的頁(yè)面中。
引入一下 依賴即可
<!--XSS 安全過濾-->
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-core</artifactId>
<version>2.0.9-GA</version>
</dependency>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-xss</artifactId>
<version>2.0.9-GA</version>
</dependency>
@GetMapping("/xss")
public String xss(String params){
return params;
}
?> ~ curl --location --request GET 'http://localhost:8080/xss?params=%3Cscript%3Ealert(%27xxx%27)%3C/script%3E'
@PostMapping("/xss")
public String xss(String params){
return params;
}
curl --location --request POST 'http://localhost:8080/xss' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'params=<script>alert('\''xxx'\'')</script>'
@PostMapping("/xss")
public String xss(@RequestBody Map<String,String> body){
return body.get("params");
}
curl --location --request POST 'http://localhost:8080/xss' \
--header 'Content-Type: application/json' \
--data-raw '{
"params":"<script>alert('\''XXX'\'')</script>"
}'
可以使用 @XssCleanIgnore 注解對(duì)方法和類級(jí)別進(jìn)行忽略。
@XssCleanIgnore
@PostMapping("/xss")
public String xss(@RequestBody Map<String,String> body){
return body.get("params");
}
目前網(wǎng)上大多數(shù)的方案如下圖,新增 XssFilter 攔截用戶提交的參數(shù),進(jìn)行相關(guān)的轉(zhuǎn)義和黑名單排除,完成相關(guān)的業(yè)務(wù)邏輯。在整個(gè)過程中最核心的是通過包裝用戶的原始請(qǐng)求,創(chuàng)建新的 requestwrapper 保證請(qǐng)求流在后邊的流程可以重復(fù)讀。
Spring WebDataBinder 的作用是從 web request 中把 web 請(qǐng)求里的parameters
綁定到對(duì)應(yīng)的JavaBean
上,在 Controller 方法中的參數(shù)類型可以是基本類型,也可以是封裝后的普通 Java 類型。若這個(gè)普通的 Java 類型沒有聲明任何注解,則意味著它的每一個(gè)屬性都需要到 Request 中去查找對(duì)應(yīng)的請(qǐng)求參數(shù),而 WebDataBinder 則可以幫助我們實(shí)現(xiàn)從 Request 中取出請(qǐng)求參數(shù)并綁定到 JavaBean 中。
SpringMVC 在綁定的過程中提供了用戶自定義編輯綁定的接口,注入即可在參數(shù)綁定 JavaBean 過程中執(zhí)行過濾。
在 Spring Boot 中默認(rèn)是使用 Jackson 進(jìn)行序列化和反序列化 JSON 數(shù)據(jù)的,那么除了可以用默認(rèn)的之外,我們也可以編寫自己的 JsonSerializer 和 JsonDeserializer 類,來(lái)進(jìn)行自定義操作。用戶提交 JSON 報(bào)文會(huì)通過 Jackson 的 JsonDeserializer 綁定到 JavaBean 中。我們只需要自定義 JsonDeserializer 即可完成在綁定 JavaBean 中執(zhí)行過濾。
在 mica-xss 中并未采取上文所述通過自己手寫黑名單或者轉(zhuǎn)義方式的實(shí)現(xiàn)方案,而是直接實(shí)現(xiàn) Jsoup 這個(gè)工具類。
jsoup 實(shí)現(xiàn) WHATWG HTML5 規(guī)范,并將 HTML 解析為與現(xiàn)代瀏覽器相同的 DOM。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。