刮乐
分享一段用canvas和JS制作刮刮乐的代码,JS部分去掉注释不到20行代码
效果图
HTML
CSS
首先 需要绘制一块Canvas蒙版 遮罩在图片上
绘制蒙版
效果图
然后利用canvas的globalCompositeOperation属性 显示原来的不在后来区域的部分
消除蒙版
效果图
那么会出现一个问题 用户需要刮开全部蒙版的话会很费事(强迫症伤不起 - -), 所以再加一段代码
添加刮开70% 自动刮开全部效果
整体代码
最终效果
本demo主要运用到globalCompositeOperation 画布的一个功能 作用是设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上,还有其余10种写法
https://jsfiddle.net/jmogkq9d/3/
刮卡是大家非常熟悉的一种网页交互元素了。实现刮涂层的效果,需要借助canvas来实现,想必每个前端工程师都清楚。实现刮刮卡并不难,但其中却涉及很多知识点,掌握这些知识点,有助于我们更深刻理解原理,对于提升举一反三的能力很有帮助。本期以实现刮刮卡为例,分享下如何科学合理地封装函数,并对涉及的相关知识点进行讲解。
先看下最终效果:
实现刮刮卡都涉及到哪些知识点呢?
下面进入本期分享的正式内容。
为了满足更多的场景需要,我们尽可能地提供更多的参数,方便使用者。先从产品和UI的角度来思考下,一个刮刮卡可能需要哪些配置选项。
接下来再补充下技术配置选项:
OK,确认好以上配置参数后,就可以正式开工了。
项目目录结构如下:
页面结构很简单,div的background显示结果,div里的canvas用来做涂层。
新建index.html,加入以下代码(HTML模板代码略过):
HTML代码:
CSS代码:
award.jpg用的是2倍图,因此使用 background-size缩放回1倍显示大小。
这里可以发现,HTML中canvas的width、height与CSS中的width、height不一致。原因就是要适应Retina 2倍屏幕。这里就涉及到了canvas画布尺寸的知识点。
现在页面显示效果如下,结果图像已显示出来:
知识点1:canvas元素尺寸与画布尺寸
HTML中canvas的width、height是画布大小,通俗来讲就是canvas画布的“绘制区域大小”,一定要跟元素的显示大小区别开来。
我们的结果图素材是750x280,所以要让canvas完全绘制这张图片,画布大小也需要是750x280。
那么元素大小,就是canvas在页面的“显示大小”。通过CSS对canvas元素进行宽高设置,使其正确的显示。
新建scratchcard.js。
结合第1章节的需求分析,类的雏形如下:
使用对象的方式向函数传参有很多优点:
使用Object.assign方法,可将传递进来的config参数覆盖默认参数。传递的config中没有的属性,则使用默认配置。
在index.html中引入scratchcard.js,在body最下边插入script代码:
刮刮卡的类使用起来非常方便,仅传递不使用默认配置的值即可。
4.1 构建ScratchCard原型
继续编写scratchcard.js:
这里设置了constructor: ScratchCard,仅仅是为了显得更加严谨,省略这一行也是没有问题的。
由代码中 prototype 和 constructor 引出第2个知识点。
知识点2:prototype、__proto__、constructor
先记住两点:
※由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性。
【__proto__】
__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。
它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,如果父对象也不存在这个属性,则继续在父对象的__proto__属性所指向的对象(爷爷对象)里找,如果还没找到,则继续往上找,直到原型链顶端null。null为原型链的终点。
由以上这种通过__proto__属性来连接对象直到null的一条链即为所谓的原型链。
【prototype】
prototype对象是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是由这个函数所创建的实例的原型对象。
因此,以上代码中,demo.__proto__===Demo.prototype。
prototype属性的作用就是:prototype包含的属性和方法可被其创建的全部实例所共用。
【constructor】
constructor属性也是对象独有的,它是从一个对象指向一个函数。其含义就是指向该对象的构造函数。所有函数最终的构造函数都指向Function。
当创建一个函数的时候,会同时自动创建它的prototype对象,这个对象也会自动获得constructor属性,并指向自己。
那么,为什么我们这里还要手动设置constructor: ScratchCard呢?
原因就是我们用这样的语法:
会导致自动设置的constructor属性值被覆盖。在这种情况下,如果我们不特意设置constructor: ScratchCard的话,constructor则会指向Object。
4.2 实现canvas涂层
先添加以下代码:
初始化代码就是实现涂层的覆盖。这里的关键逻辑是:如果设置了图像涂层,则忽略纯色涂层。
涉及到了canvas两个API:
drawImage 用于绘制图像。
fillRect 用于绘制矩形,在绘制之前要先设置笔刷,即通过fillStyle属性设置颜色。
这段代码是什么意思呢?
globalCompositeOperation就是第3个知识点。
知识点3:canvas的globalCompositeOperation
在w3school上可以查阅到该属性的详细说明:
看上去好像有点懵逼难理解,其实就是类似于指定photoshop里两个图层怎么融合,比如谁遮罩谁、交叉部分消除、交叉部分颜色融合等等。
可以参看下w3school的图示,蓝色为目标图像,红色为源图像。
回到刮刮卡,图片涂层是目标图像,目前源图像还未设置,所以源图像为全透明(源图像的不透明的部分用来抠除目标图像并呈现透明),所以目标图像(图片涂层)全部显示。
现在效果如下图所示,涂层已经覆盖上了。
4.3 添加涂抹事件
涂抹事件,其实就是用touchstart、touchmove、touchend事件,为了顺便兼容鼠标操作,也把mousedown、mousemove、mouseup带上。
修改代码:
代码很好理解,就是添加事件监听。当按下的时候,把isDown设置为true,当抬起的时候,把isDown设置为false。
可以看到addEventListener的第3个参数{ passive: false },这是个什么鬼?这就是第4个知识点。
知识点4:addEventListener第三个参数的passive属性
最开始,addEventListener() 的参数约定是这样的:
三个属性的默认值都为 false。
为什么会多出个passive属性呢?
为了防止页面滚动,很多移动端页面都会监听 touchmove 等 touch 事件,像这样:
由于 touchmove 事件对象的 cancelable 属性为 true,也就是说它的默认行为可以被监听器通过 preventDefault() 方法阻止。那它的默认行为是什么呢,通常来说就是滚动当前页面(还可能是缩放页面),如果它的默认行为被阻止了,页面就必须静止不动。但浏览器无法预先知道一个监听器会不会调用 preventDefault(),它能做的只有等监听器执行完后再去执行默认行为,而监听器执行是要耗时的,有些甚至耗时很明显,这样就会导致页面卡顿。即便监听器是个空函数,也会产生一定的卡顿,毕竟空函数的执行也会耗时。
当设置了passtive为true,则会忽略代码中的preventDefault(), 因此页面会变得更流畅。如下演示,右侧手机的页面设置了passtive为true。
OK,那么问题来了?既然默认是passive: false,为什么代码里还要再多此一举写一遍呢?
答案在这里,来看chrome的官方说明:
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里,默认passive是false。但是如果事件是 touchstart 或 touchmove的话,passive的默认值则会变成true(所以preventDefault就会被忽略了)。
OK,原理讲完了,我们还没有把页面的默认滑动行为阻止掉。不阻止的话,在滑动刮刮卡的时候,页面也会跟着滚动。
4.4 阻止页面滚动
看完了4.3小节,那么阻止页面滚动就很简单了。在index.html的script里加入以下代码:
4.5 实现擦除效果
这里完善下_scratch方法,代码如下:
逻辑大致如下:
需要说明的是,乘以pixelRatio是为了适应多倍屏幕。在本示例中,画布尺寸是2倍尺寸,而坐标是按照网页元素的尺寸计算出来的,正好相差一倍,所以要乘以pixelRatio(pixelRatio=2)。
还记得4.2小节讲的globalCompositeOperation么?当设置为destination-out的时候,源图像的非透明部分会抠去目标图像,因此实现了刮刮卡的刮涂层效果。
4.6 检测涂层的透明部分占比
虽然刮涂层的效果实现了,但是还要实时检测刮开了多少,来判断是否完成刮刮卡。
继续修改代码:
新增了3个方法:
_scratchAll: 清空涂层(全部刮开)。如果设置的fadeOut(淡出时间),则通过CSS动画,将canvas做淡出效果,然后再清除涂层。如果fadeOut为0,则直接清除涂层。
_clear:清除涂层。很简单,直接画一个铺满画布的矩形即可。
_getFilledPercentage:计算刮开区域的百分比。通过遍历canvas每个像素点,计算全透明像素的占比。
这里就涉及到了第5个知识点。
知识点5:canvas的ImageData
利用canvas的getImageData()方法可以获取到全部的像素点信息,返回数组格式。数组中,并不是每个元素代表一个像素的信息,而是每4个元素为一个像素的信息。例如:
data[0]=像素1的R值,红色(0-255)
data[1]=像素1的G值,绿色(0-255)
data[2]=像素1的B值,蓝色(0-255)
data[3]=像素1的A值,alpha 通道(0-255; 0 透明,255完全可见)
data[4]=像素2的R值,红色(0-255)
...
本例的透明度不存在中间值,所以就可以认为alpha小于128即为透明。
4.7 注意事项
由于浏览器安全限制,Image不能读取本地图片,因此需要部署在服务端,以http协议浏览本项目。
以上就是本期分享的全部内容了。完整代码请前往GitHub:
https://github.com/Yuezi32/scratchcard
看似简单的刮刮卡却隐藏了这么多的知识点,你都掌握了么?
XSS(Cross Site Scripting)攻击全称跨站脚本攻击,为了不与 CSS(Cascading Style Sheets)名词混淆,故将跨站脚本攻击简称为 XSS,XSS 是一种常见 web 安全漏洞,它允许恶意代码植入到提供给其它用户使用的页面中。
引入一下 依赖即可
<!--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 注解对方法和类级别进行忽略。
@XssCleanIgnore
@PostMapping("/xss")
public String xss(@RequestBody Map<String,String> body){
return body.get("params");
}
目前网上大多数的方案如下图,新增 XssFilter 拦截用户提交的参数,进行相关的转义和黑名单排除,完成相关的业务逻辑。在整个过程中最核心的是通过包装用户的原始请求,创建新的 requestwrapper 保证请求流在后边的流程可以重复读。
Spring WebDataBinder 的作用是从 web request 中把 web 请求里的parameters
绑定到对应的JavaBean
上,在 Controller 方法中的参数类型可以是基本类型,也可以是封装后的普通 Java 类型。若这个普通的 Java 类型没有声明任何注解,则意味着它的每一个属性都需要到 Request 中去查找对应的请求参数,而 WebDataBinder 则可以帮助我们实现从 Request 中取出请求参数并绑定到 JavaBean 中。
SpringMVC 在绑定的过程中提供了用户自定义编辑绑定的接口,注入即可在参数绑定 JavaBean 过程中执行过滤。
在 Spring Boot 中默认是使用 Jackson 进行序列化和反序列化 JSON 数据的,那么除了可以用默认的之外,我们也可以编写自己的 JsonSerializer 和 JsonDeserializer 类,来进行自定义操作。用户提交 JSON 报文会通过 Jackson 的 JsonDeserializer 绑定到 JavaBean 中。我们只需要自定义 JsonDeserializer 即可完成在绑定 JavaBean 中执行过滤。
在 mica-xss 中并未采取上文所述通过自己手写黑名单或者转义方式的实现方案,而是直接实现 Jsoup 这个工具类。
jsoup 实现 WHATWG HTML5 规范,并将 HTML 解析为与现代浏览器相同的 DOM。
*请认真填写需求信息,我们会在24小时内与您取得联系。