整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

50行代码实现H5刮刮乐效果(兼容PC及移动端)

50行代码实现H5刮刮乐效果(兼容PC及移动端)

刮乐

前言

分享一段用canvas和JS制作刮刮乐的代码,JS部分去掉注释不到20行代码

效果图

HTML

HTML

CSS

CSS

JS

首先 需要绘制一块Canvas蒙版 遮罩在图片上

绘制蒙版

效果图

然后利用canvas的globalCompositeOperation属性 显示原来的不在后来区域的部分

消除蒙版

效果图

那么会出现一个问题 用户需要刮开全部蒙版的话会很费事(强迫症伤不起 - -), 所以再加一段代码

添加刮开70% 自动刮开全部效果

整体代码

最终效果

需要注意的是

本demo主要运用到globalCompositeOperation 画布的一个功能 作用是设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上,还有其余10种写法

在线DEMO及源码

https://jsfiddle.net/jmogkq9d/3/

刮卡是大家非常熟悉的一种网页交互元素了。实现刮涂层的效果,需要借助canvas来实现,想必每个前端工程师都清楚。实现刮刮卡并不难,但其中却涉及很多知识点,掌握这些知识点,有助于我们更深刻理解原理,对于提升举一反三的能力很有帮助。本期以实现刮刮卡为例,分享下如何科学合理地封装函数,并对涉及的相关知识点进行讲解。

先看下最终效果:

实现刮刮卡都涉及到哪些知识点呢?

  • 知识点1:canvas元素尺寸与画布尺寸
  • 知识点2:prototype、__proto__、constructor
  • 知识点3:canvas的globalCompositeOperation
  • 知识点4:addEventListener第三个参数的passive属性
  • 知识点5:canvas的ImageData

下面进入本期分享的正式内容。

1 需求分析

为了满足更多的场景需要,我们尽可能地提供更多的参数,方便使用者。先从产品和UI的角度来思考下,一个刮刮卡可能需要哪些配置选项。

  • 涂层样式(图片 or 纯色)
  • 涂抹画笔半径
  • 涂抹到百分之多少时,直接刮开全部涂层
  • 刮开全部涂层的效果(淡出 or 直接消除)

接下来再补充下技术配置选项:

  • canvas元素
  • 屏幕像素显示倍数(适应Retina等高倍屏)
  • 淡出效果的过渡动画时间

OK,确认好以上配置参数后,就可以正式开工了。

2 页面构建

项目目录结构如下:

页面结构很简单,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元素进行宽高设置,使其正确的显示。

3 构建类的雏形

新建scratchcard.js。

结合第1章节的需求分析,类的雏形如下:

使用对象的方式向函数传参有很多优点:

  1. 参数语义化,方便理解
  2. 不用在意参数顺序
  3. 传参的增删和顺序调整不会影响业务代码的使用

使用Object.assign方法,可将传递进来的config参数覆盖默认参数。传递的config中没有的属性,则使用默认配置。

在index.html中引入scratchcard.js,在body最下边插入script代码:

刮刮卡的类使用起来非常方便,仅传递不使用默认配置的值即可。

4 实现ScratchCard

4.1 构建ScratchCard原型

继续编写scratchcard.js:

这里设置了constructor: ScratchCard,仅仅是为了显得更加严谨,省略这一行也是没有问题的。

由代码中 prototype constructor 引出第2个知识点。

知识点2:prototype、__proto__、constructor

先记住两点:

  1. __proto__和constructor属性是对象所独有的(函数也是对象)。
  2. prototype属性是函数所独有的。

※由于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() 的参数约定是这样的:

  • el:事件对象
  • type:事件类型,click、mouseover 等
  • listener:事件处理函数,也就是事件触发后的回调
  • useCapture:布尔值,是否是捕获型,默认 false(冒泡)
    2015年底,为了扩展新的选项,DOM 规范做了修订:
  • 三个属性的默认值都为 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方法,代码如下:

    逻辑大致如下:

    1. 判断刮刮卡还没刮完(this.done为false),并且处于按下状态(this.isDown为true)。
    2. 如果存在多个触点,则使用最后一个触点。通过e.changedTouches获取最后一个触点。
    3. 计算触点在canvas里的相对坐标。
    4. 在canvas中的触点位置绘制圆形。

    需要说明的是,乘以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

    看似简单的刮刮卡却隐藏了这么多的知识点,你都掌握了么?

    SS 是什么

    XSS(Cross Site Scripting)攻击全称跨站脚本攻击,为了不与 CSS(Cascading Style Sheets)名词混淆,故将跨站脚本攻击简称为 XSS,XSS 是一种常见 web 安全漏洞,它允许恶意代码植入到提供给其它用户使用的页面中。

    xss 攻击流程

    简单 xss 攻击示例

    • 若网站某个表单没做相关的处理,用户提交相关恶意代码,浏览器会执行相关的代码。

    解决方案

    XSS 过滤说明

    • 对表单绑定的字符串类型进行 xss 处理。
    • 对 json 字符串数据进行 xss 处理。
    • 提供路由和控制器方法级别的放行规则。

    使用 mica-xss

    引入一下 依赖即可

    <!--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>

    测试 XSS 过滤

    测试 GET 参数过滤

    • 创建目标接口,模拟 get 提交
    @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'

    测试 POST form 参数过滤

    • 创建目标接口,模拟 post form 提交
    @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>'

    测试 POST body 参数过滤

    • 创建目标接口,模拟 post body 提交
        @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 保证请求流在后边的流程可以重复读

    mica-xss 实现

    1. 自定义 WebDataBinder 编辑器支持 form 过滤

    Spring WebDataBinder 的作用是从 web request 中把 web 请求里的parameters绑定到对应的JavaBean上,在 Controller 方法中的参数类型可以是基本类型,也可以是封装后的普通 Java 类型。若这个普通的 Java 类型没有声明任何注解,则意味着它的每一个属性都需要到 Request 中去查找对应的请求参数,而 WebDataBinder 则可以帮助我们实现从 Request 中取出请求参数并绑定到 JavaBean 中。

    SpringMVC 在绑定的过程中提供了用户自定义编辑绑定的接口,注入即可在参数绑定 JavaBean 过程中执行过滤。

    2. 自定义 JsonDeserializer 反序列化支持 Json 过滤

    在 Spring Boot 中默认是使用 Jackson 进行序列化和反序列化 JSON 数据的,那么除了可以用默认的之外,我们也可以编写自己的 JsonSerializer 和 JsonDeserializer 类,来进行自定义操作。用户提交 JSON 报文会通过 Jackson 的 JsonDeserializer 绑定到 JavaBean 中。我们只需要自定义 JsonDeserializer 即可完成在绑定 JavaBean 中执行过滤。

    1. 核心过滤逻辑

      在 mica-xss 中并未采取上文所述通过自己手写黑名单或者转义方式的实现方案,而是直接实现 Jsoup 这个工具类。

      jsoup 实现 WHATWG HTML5 规范,并将 HTML 解析为与现代浏览器相同的 DOM。

      • 从 URL,文件或字符串中刮取和解析 HTML
      • 使用 DOM 遍历或 CSS 选择器查找和提取数据
      • 操纵 HTML 元素,属性和文本
      • 清除用户提交的内容以防止安全白名单,以防止 XSS 攻击
      • 输出整洁的 HTML