当前位置: 首页 > news >正文

ScratchCard刮刮卡交互元素的实现

效果展示

刮刮卡是⼀种常见的网页交互元素,通过模拟物理世界的刮涂层来揭示下方的内容。这种效果主要依赖于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.dtcms.com/a/282794.html

相关文章:

  • 列车调度(vector)
  • 前端vue3获取excel二进制流在页面展示
  • 【unity知识点】已知发射的初始位置和目标位置,计算发射初速度,实现投掷物品或者弓箭手做抛体线运动精准的击中目标
  • C语言 --- 函数递归
  • Python编程基础(六)| 用户输入和while循环
  • 康华生物:以创新疫苗书写国产突围之路​​​
  • 李宏毅2025《机器学习》第七讲-推理模型:从原理、流派到未来挑战
  • 2025年自动化工程、物联网与计算机应用国际会议(AEITCA 2025)
  • 【时序数据库-iotdb】时序数据库iotdb的可视化客户端安装部署--dbeaver
  • 基于Spring AI Alibaba的智能知识助手系统:从零到一的RAG实战开发
  • 最细,Web自动化测试入门到精通整理,一套打通自动化测试...
  • ASP .NET Core 8集成Swagger全攻略
  • 从零开发足球比分APP:REST API与WebSocket的完美搭配
  • HAProxy简介及配置文件详解
  • ESP‑IDF 默认的连接流程是
  • 2_概要设计编写提示词_AI编程专用简化版
  • 快速开发汽车充电桩的屏幕驱动与语音提示方案
  • __is_constexpr(x)宏介绍---max()宏扩展
  • Linux 常用指令
  • 信而泰×DeepSeek:AI推理引擎驱动网络智能诊断迈向 “自愈”时代
  • Java基础语法补充v2
  • C# --- 单例类错误初始化 + 没有释放资源导致线程泄漏
  • The 2024 ICPC Asia Shenyang Regional Contest B. Magical Palette
  • Docker容器技术讲解
  • Liunx练习项目6-创建dns服务器
  • 主机安全---开源wazuh安装
  • 深入理解概率图模型:贝叶斯网络因子分解、d-分离与马尔可夫毯
  • 基于用户空间操作IIC接口调试云台电机
  • 7.16 Java基础 | 集合框架(上)
  • 微服务架构中实现跨服务的字段级权限统一控制