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.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…