效果展示

刮刮卡是⼀种常见的网页交互元素,通过模拟物理世界的刮涂层来揭示下方的内容。这种效果主要依赖于HTML5的 元素来实现。以下是⼀个基于TypeScript的刮刮卡实现示例,包括配置项、初始化方法和核心的刮开逻辑。下面是展示的效果
在这里插入图片描述
部分刮开效果:
在这里插入图片描述

具体实现

配置项

  1. 蒙层图片:可以是纯色或图片。
  2. 刮卡画笔半径:控制刮除区域的大小。
  3. 显示全部的比例:当刮除面积达到⼀定比例时,自动显示全部内容。
  4. 淡出时间:刮开涂层后的淡出动画时间。
  5. Canvas元素:用于绘制刮刮卡的HTML5 元素。

代码实现

首先,我们创建⼀个 ScratchCard.ts 文件,并定义 ScratchCard 类及其配置项接口。

interface ScratchCardConfig {canvas: HTMLCanvasElement; //传⼊的元素showAllPercent: number; //1. 到达什么⽐例之后展示全部coverImg?: string; //蒙层的图⽚coverColor?: string; //纯⾊蒙层doneCallback?: () => void; //完成之后的回调radius: number; //1. 刮卡画笔的半径fadeOut: number; //淡出时间
}
class ScratchCard {private config: ScratchCardConfig;private canvas: HTMLCanvasElement;private ctx: CanvasRenderingContext2D | null;private offsetX: number;private offsetY: number;private isDown: boolean;private done: boolean;constructor(config: Partial<ScratchCardConfig>) {const defaultConfig: ScratchCardConfig = {canvas: config.canvas!,showAllPercent: 65,coverImg: undefined,coverColor: undefined,doneCallback: undefined,radius: 30,fadeOut: 2000,};this.config = { ...defaultConfig, ...config };this.canvas = this.config.canvas;this.ctx = null;this.offsetX = 0;this.offsetY = 0;this.isDown = false;this.done = false;}
}
init实现

然后就是要写我们的init方法,在init⾥我们需要初始化数据并且把蒙层先画出来

重点解析

drawImage 用于绘制图像。
fillRect 用于绘制矩形,并且通过fillStyle属性设置绘制的颜色。
并且在绘制之前先充值了⼀下操作模式
globalCompositeOperation 用于标识要使用哪种合成或混合模式操作。
'source-over' 是默认设置,在现有画布内容上绘制新形状
'destination-out' 是将现有内容将保留在不与新形状重叠的位置,具体来说,它会在源图形和目标图形相交的区域,将目标图形的颜色变为透明。这里我们设置的蒙层就是目标图形
为了确保我们的刮刮卡生效,防止操作模式干扰,所以在init时直接将 globalCompositeOperation 重置为 'source-over'

class ScratchCard {private _init(): void {this.ctx = this.canvas.getContext('2d');this.offsetX = this.canvas.offsetLeft;this.offsetY = this.canvas.offsetTop;this._addEvent();if (this.config.coverImg) {const coverImg = new Image();// 添加跨域设置coverImg.crossOrigin = 'anonymous';coverImg.src = this.config.coverImg;coverImg.onload = () => {if (this.ctx) {this.ctx.globalCompositeOperation = 'source-over'; // 重置组合操作
模式this.ctx.drawImage(coverImg, 0, 0);this.ctx.globalCompositeOperation = 'destination-out';}};// 添加错误处理coverImg.onerror = (e) => {console.error('Image loading error:', e);// 加载失败时使⽤纯⾊背景作为后备⽅案if (this.ctx) {this.ctx.fillStyle = this.config.coverColor || '#CCCCCC';this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);this.ctx.globalCompositeOperation = 'destination-out';}};} else if (this.ctx && this.config.coverColor) {this.ctx.fillStyle = this.config.coverColor;this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);this.ctx.globalCompositeOperation = 'destination-out';}}
}
scratch实现

在实现刮刮卡的刮开效果时,关键在于通过 scratch 方法来模拟真实的刮除体验。在此过程中,一个至关重要的细节是正确处理触摸事件中的坐标获取。并且绘制鼠标划过的地方。

重点解析

这里在获取的时候不能直接获取 touch.clientXtouch.clientY ,因为Canvas 元素的实际
像素尺寸(往往与其在页面上的显示尺寸(通过 CSS 设置宽度和高度)不同步。如果直接使用未
缩放的坐标绘制内容,可能不会在正确的位置渲染,尤其是在高分辨率屏幕或有缩放的页面布局
中。通过这种方式,我们确保了绘制坐标的准确性。
绘制时我们先通过 beginPath 绘制⼀条路径,然后通过 arc 和之前传⼊的半径值来创建一个圆,最后使用 fill 方法进行填充,在混合透明的前提下,这里就会展示出被擦除的效果

class ScratchCard {private _scratch(e: MouseEvent | TouchEvent): void {e.preventDefault();if (!this.done && this.isDown && this.ctx) {let eventX: number;let eventY: number;const rect = this.canvas.getBoundingClientRect();if ('changedTouches' in e) {const touch = e.changedTouches[0];eventX = (touch.clientX - rect.left) * (this.canvas.width / rect.w
idth);eventY = (touch.clientY - rect.top) * (this.canvas.height / rect.h
eight);} else {eventX = (e.clientX + document.body.scrollLeft || e.pageX) - this.
offsetX || 0;eventY = (e.clientY + document.body.scrollTop || e.pageY) - this.o
ffsetY || 0;}//开始绘制this.ctx.beginPath();this.ctx.arc(eventX, eventY, this.config.radius, 0, Math.PI * 2);this.ctx.fill();// 如果透明的元素⽐例⼤于设置的值,则全部展现if (this._getFilledPercentage() > this.config.showAllPercent) {this._scratchAll();}}}
}

getFilledPercentage实现

然后就需要计算已经被擦除的比例,这里通过计算透明的像素的占比来确定擦除的比例

class ScratchCard {private _getFilledPercentage(): number {if (!this.ctx) return 0;// 获取画布的像素数据const imgData = this.ctx.getImageData(0, 0, this.canvas.width, this.ca
nvas.height);const pixels = imgData.data;const transPixels: number[] = [];// 遍历像素数据(⼀组像素有四个值RGBA所以需要+4)for (let i = 0; i < pixels.length; i += 4) {// 计算透明度是否⼩于128(128是0~255的中间值,低于128就被认为是半透明或透明
的)if (pixels[i + 3] < 128) {transPixels.push(pixels[i + 3]);}}// 返回百分⽐数据return Number(((transPixels.length / (pixels.length / 4)) * 100).toFixed(2));}
}

scratchAll实现

然后就是全部刮开的方法,这⾥需要处理⼀下淡出以及剩余的元素变透明的逻辑

class ScratchCard {private _scratchAll(): void {this.done = true;// 需要渐隐就添加渐隐效果,不需要就直接clearif (this.config.fadeOut > 0) {this.canvas.style.transition = `all ${this.config.fadeOut / 1000}s l
inear`;this.canvas.style.opacity = '0';setTimeout(() => {this._clear();}, this.config.fadeOut);} else {this._clear();}}private _clear(): void {if (this.ctx) {// destination-out 模式下,它会变成透明this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);}// 如果有传⼊的回调就执⾏if (this.config.doneCallback) {this.config.doneCallback();}}
}

事件处理

最后我们只需要再加上事件监听,就可以实现刮刮卡的效果咯

重点解析

在addEventListener的option里,默认passive是false。但是如果事件是 touchstarttouchmove的话,passive的默认值则会变成true(所以preventDefault就会被忽略了),所以这里单独给他写出来

class ScratchCard {private _addEvent(): void {this.canvas.addEventListener('touchstart',this._eventDown.bind(this),{
passive: false });this.canvas.addEventListener('touchend',this._eventUp.bind(this), { pa
ssive: false });this.canvas.addEventListener('touchmove',this._scratch.bind(this), { p
assive: false });this.canvas.addEventListener('mousedown',this._eventDown.bind(this), {
passive: false });this.canvas.addEventListener('mouseup', this._eventUp.bind(this), { pa
ssive: false });this.canvas.addEventListener('mousemove',this._scratch.bind(this), { p
assive: false });}private _eventDown(e: MouseEvent | TouchEvent): void {e.preventDefault();this.isDown = true;}private _eventUp(e: MouseEvent | TouchEvent): void {e.preventDefault();this.isDown = false;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/91740.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/91740.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/91740.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python LeetCode 专题】热题 100,重在思路

哈希1. 两数之和49. 字母异位词分组128. 最长连续序列双指针283. 移动零11. 盛最多水的容器15. 三数之和42. 接雨水滑动窗口3. 无重复字符的最长子串438. 找到字符串中所有字母异位词子串560. 和为 K 的子数组239. 滑动窗口最大值普通数组53. 最大子数组和56. 合并区间189. 轮转…

openEuler 22.03 LTS Rootless Docker 安装指南

openEuler 22.03 LTS Rootless Docker 安装指南 1.创建普通用户&#xff08;用于无根模式&#xff09; sudo useradd -m docker-user sudo passwd docker-user # 设置密码 sudo usermod --add-subuids 100000-165535 docker-user sudo usermod --add-subgids 100000-165535 do…

CMake指令:常见内置命令行工具( CMake -E )

目录 1.简介 2.核心作用 3.常用命令介绍 3.1.文件操作命令 3.2.系统命令执行 3.3.校验与哈希 3.4.流程控制与等待 3.5.路径与文件处理 3.6.归档与压缩 3.7.网络与下载 3.8.实用工具 4.使用示例 5.与 shell 命令的对比 6.在 CMake 脚本中使用 7.总结 相关链接 1…

YOLO融合CAF-YOLO中的ACFM模块

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《CAF-YOLO: A Robust Framework for Multi-Scale Lesion Detection in Biomedical Imagery》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org…

Webpack 项目构建优化详解

1. 相关面试题 1.1. 做过哪些Webpack打包构建优化? 代码分割:使用 Webpack 的 SplitChunksPlugin 进行代码分割,将第三方库、公共代码与业务代码分离,提高缓存利用率和加载速度。 Tree Shaking:通过配置 mode: production 或使用 TerserPlugin,移除未引用的代码,减少…

【深度学习基础】张量与Tensor的区别?从标量到深度学习的多维世界

目录引言一、张量&#xff08;Tensor&#xff09;的定义与特性1. 数学中的张量2. 深度学习中的Tensor二、标量&#xff08;Scalar&#xff09;是什么&#xff1f;三、深度学习中的其他核心量1. 向量&#xff08;Vector&#xff09;2. 矩阵&#xff08;Matrix&#xff09;3. 高阶…

设计模式一: 模板方法模式 (Template Method Pattern)

模板方法模式是一种行为设计模式&#xff0c;它通过定义一个算法的骨架&#xff0c;而将一些步骤延迟到子类中实现。Template Method 使得子类可以不改变&#xff08;复用&#xff09;一个算法结构 即可重定义&#xff08;override 重写&#xff09;该算法的某些特定步骤。基本…

Linux驱动学习day24(UART子系统)

一、UART硬件理论1.1 作用及功能UART&#xff1a;通用异步收发传输器&#xff0c;简称串口。功能&#xff1a;移植u-boot、内核时&#xff0c;主要使用串口查看打印信息。外接各种模块&#xff0c;比如蓝牙GPS模块。使用UART的时候&#xff0c;要注意1. 波特率 2. 格式&#xf…

NFS共享服务器

目录 任务要求 思路总结 1.NFS共享服务 服务端 (ip 192.168.48.128) 客户端 (ip 192.168.48.130) 2.配置autofs自动挂载 任务要求 1.NFS服务器,可以让PC将网络中的NFS服务器共享的目录挂载到本地端的文件系统中,而在本地端的系统中看来&#xff0c;那个远程主机的目…

FreeRTOS学习笔记之队列

小编正在学习嵌入式软件&#xff0c;目前建立了一个交流群&#xff0c;可以留下你的评论&#xff0c;我拉你进群一、简介队列是为了任务与任务、任务与中断之间的通信而准备的&#xff0c;可以在任务与任务、任务与中断之间消息传递&#xff0c;队列中可以存储有限的、大小固定…

垃圾收集器-ZGC

前言在Java开发中&#xff0c;垃圾收集器的选择对系统性能有着致命的影响。Java 8后&#xff0c;虽然G1 GC成为默认&#xff0c;但是它在延迟性控制上仍有限。ZGC作为最新一代高性能低延迟垃圾收集器&#xff0c;解决了CMS和G1在延迟、垃圾堆容量和吞吐量方面的重大突破。本文将…

计算机“十万个为什么”之跨域

计算机“十万个为什么”之跨域 本文是计算机“十万个为什么”系列的第五篇&#xff0c;主要是介绍跨域的相关知识。 作者&#xff1a;无限大 推荐阅读时间&#xff1a;10 分钟 一、引言&#xff1a;为什么会有跨域这个“拦路虎”&#xff1f; 想象你正在参观一座戒备森严的城堡…

C语言:20250719笔记

字符数组在C语言中&#xff0c;支持字符串常量&#xff0c;不支持字符串变量。如果想要实现类似的字符串变量&#xff0c;C语言提供了两种实现方式&#xff1a;字符数组&#xff1a;char name[] “哪吒”&#xff1b;字符指针&#xff1a;char *name "娜吒"&#x…

decltype是什么,什么作用?

基本概念decltype 是 C11 引入的关键字&#xff0c;用于推导表达式的类型&#xff0c;且会完整保留类型的细节&#xff08;包括 const、引用 &、指针 * 等&#xff09;。语法:decltype(表达式) 变量名核心特点1.推导依据是表达式本身&#xff0c;而非表达式的结果&#xff…

RPC 与 Feign 的区别笔记

一、基本概念 1.1 RPC&#xff08;Remote Procedure Call&#xff09; 定义&#xff1a;远程过程调用&#xff0c;允许像调用本地方法一样调用远程服务的方法。 本质&#xff1a;跨进程通信&#xff0c;隐藏了底层网络通信的复杂性。 常见实现&#xff1a; Java 原生 RMIDub…

高防IP能够防御CC攻击吗?它具备哪些显著优势?

摘要&#xff1a; 面对日益复杂的网络攻击&#xff0c;高防IP作为重要的安全工具&#xff0c;不仅能防御常见的DDoS攻击&#xff0c;还能有效应对CC攻击。本文将解析高防IP防御CC攻击的原理及其核心优势&#xff0c;帮助读者了解其在网络安全中的关键作用。一、高防IP能否防御C…

TypeScript 类型注解(一)

一、TypeScript 类型注解1、什么是TpyeScript类型注解- 是否还记得TypeScript的两个重要特性&#xff1f;- 类型系统、适用于任何规模- 可以说&#xff0c;TS的类型系统是TS最重要的功能&#xff1b;那么什么是类型注解呢&#xff1f;其实就是在声明变量时&#xff0c;将变量的…

弗兰肯斯坦式的人工智能与GTM策略的崩溃

2025 年上半年已经明确了一件事&#xff1a;B2B 市场营销团队被工具淹没&#xff0c;但缺乏策略。人工智能无处不在。收入领导者在进行无休止的试点。营销团队拼凑各种点解决方案&#xff0c;希望能实现规模扩张。然而&#xff0c;销售线索的增长停滞不前。信誉正在受损。曾经承…

NAND闪存(NAND Flash)是什么?

NAND闪存(NAND Flash)是什么? NAND闪存(NAND Flash)详解 NAND闪存是一种非易失性存储介质(断电不丢失数据),广泛应用于SSD、U盘、手机存储等设备中。NAND Flash 的全称是 “Negative-AND Flash”(与非型闪存),其名称源自其底层存储单元的电路结构——基于**“与非门…

Android性能优化之UI渲染优化

一、UI渲染核心瓶颈深度解析 1. 渲染管线关键阶段阶段CPU工作GPU工作潜在卡顿点Measure计算View尺寸-嵌套布局多次测量Layout计算View位置-频繁重排(Relayout)Draw构建DisplayList指令集-复杂自定义View.onDraw()Sync & Upload资源上传到GPU内存纹理上传大图/未压缩资源Ras…