scratchcard-reactjs
Version:
**Interactive scratch card functionality made simple for your app! ✨**
175 lines (166 loc) • 9.5 kB
JavaScript
import React, { useState, useEffect } from 'react';
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = ".base,\r\n#scratch {\r\n height: 200px;\r\n width: 200px;\r\n position: absolute;\r\n text-align: center;\r\n cursor: grabbing;\r\n border-radius: 1rem;\r\n box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;\r\n}\r\n\r\n.base {\r\n background-color: #ffffff;\r\n font-family: 'Poppins', sans-serif;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 5px 0px,\r\n rgba(0, 0, 0, 0.1) 0px 0px 1px 0px;\r\n}\r\n\r\n.base:hover {\r\n box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px,\r\n rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;\r\n}\r\n\r\n.base h4 {\r\n font-size: 19px;\r\n font-weight: 500;\r\n color: #141414;\r\n padding: 1rem;\r\n}\r\n\r\n#scratch {\r\n -webkit-tap-highlight-color: transparent;\r\n -webkit-touch-callout: none;\r\n -webkit-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNhcmQuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztFQUVFLGFBQWE7RUFDYixZQUFZO0VBQ1osa0JBQWtCO0VBQ2xCLGtCQUFrQjtFQUNsQixnQkFBZ0I7RUFDaEIsbUJBQW1CO0VBQ25CLDhFQUE4RTtBQUNoRjs7QUFFQTtFQUNFLHlCQUF5QjtFQUN6QixrQ0FBa0M7RUFDbEMsYUFBYTtFQUNiLHNCQUFzQjtFQUN0QixtQkFBbUI7RUFDbkIsdUJBQXVCO0VBQ3ZCO3NDQUNvQztBQUN0Qzs7QUFFQTtFQUNFOzBDQUN3QztBQUMxQzs7QUFFQTtFQUNFLGVBQWU7RUFDZixnQkFBZ0I7RUFDaEIsY0FBYztFQUNkLGFBQWE7QUFDZjs7QUFFQTtFQUNFLHdDQUF3QztFQUN4QywyQkFBMkI7RUFDM0IseUJBQXlCO0VBQ3pCLGlCQUFpQjtBQUNuQiIsImZpbGUiOiJjYXJkLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5iYXNlLFxyXG4jc2NyYXRjaCB7XHJcbiAgaGVpZ2h0OiAyMDBweDtcclxuICB3aWR0aDogMjAwcHg7XHJcbiAgcG9zaXRpb246IGFic29sdXRlO1xyXG4gIHRleHQtYWxpZ246IGNlbnRlcjtcclxuICBjdXJzb3I6IGdyYWJiaW5nO1xyXG4gIGJvcmRlci1yYWRpdXM6IDFyZW07XHJcbiAgYm94LXNoYWRvdzogcmdiYSgwLCAwLCAwLCAwLjE5KSAwcHggMTBweCAyMHB4LCByZ2JhKDAsIDAsIDAsIDAuMjMpIDBweCA2cHggNnB4O1xyXG59XHJcblxyXG4uYmFzZSB7XHJcbiAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZmZmZjtcclxuICBmb250LWZhbWlseTogJ1BvcHBpbnMnLCBzYW5zLXNlcmlmO1xyXG4gIGRpc3BsYXk6IGZsZXg7XHJcbiAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcclxuICBhbGlnbi1pdGVtczogY2VudGVyO1xyXG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xyXG4gIGJveC1zaGFkb3c6IHJnYmEoMCwgMCwgMCwgMC4xKSAwcHggMHB4IDVweCAwcHgsXHJcbiAgICByZ2JhKDAsIDAsIDAsIDAuMSkgMHB4IDBweCAxcHggMHB4O1xyXG59XHJcblxyXG4uYmFzZTpob3ZlciB7XHJcbiAgYm94LXNoYWRvdzogcmdiYSg2MCwgNjQsIDY3LCAwLjMpIDBweCAxcHggMnB4IDBweCxcclxuICAgIHJnYmEoNjAsIDY0LCA2NywgMC4xNSkgMHB4IDJweCA2cHggMnB4O1xyXG59XHJcblxyXG4uYmFzZSBoNCB7XHJcbiAgZm9udC1zaXplOiAxOXB4O1xyXG4gIGZvbnQtd2VpZ2h0OiA1MDA7XHJcbiAgY29sb3I6ICMxNDE0MTQ7XHJcbiAgcGFkZGluZzogMXJlbTtcclxufVxyXG5cclxuI3NjcmF0Y2gge1xyXG4gIC13ZWJraXQtdGFwLWhpZ2hsaWdodC1jb2xvcjogdHJhbnNwYXJlbnQ7XHJcbiAgLXdlYmtpdC10b3VjaC1jYWxsb3V0OiBub25lO1xyXG4gIC13ZWJraXQtdXNlci1zZWxlY3Q6IG5vbmU7XHJcbiAgdXNlci1zZWxlY3Q6IG5vbmU7XHJcbn1cclxuIl19 */";
styleInject(css_248z);
const ScratchCard = ({ data, variant, handleCoverScratched }) => {
const [coverRemoved, setCoverRemoved] = useState(false);
useEffect(() => {
let coverScratched = false;
const canvasElement = document.getElementById("scratch");
const canvasContext = canvasElement.getContext("2d");
const coverArea = canvasElement.width * canvasElement.height;
const eventTypes = {
mouse: {
down: "mousedown",
move: "mousemove",
up: "mouseup",
},
touch: {
down: "touchstart",
move: "touchmove",
up: "touchend",
},
};
const initializeCanvas = () => {
const gradient = canvasContext?.createLinearGradient(0, 0, 135, 135);
let color1 = "", color2 = "";
if (variant === "blue") {
color1 = "#2c67f2";
color2 = "#62cff4";
}
else if (variant === "green") {
color1 = "#53db97";
color2 = "#0695b6";
}
else {
color1 = "#d63031";
color2 = "#fdcb6e";
}
gradient?.addColorStop(0, color1);
gradient?.addColorStop(1, color2);
if (canvasContext && gradient) {
canvasContext.fillStyle = gradient;
canvasContext.fillRect(0, 0, canvasElement?.width, canvasElement?.height);
}
};
let mouseX = 0;
let mouseY = 0;
let isDragging = false;
let deviceType = "";
const checkIfTouchDevice = () => {
try {
document.createEvent("TouchEvent");
deviceType = "touch";
return true;
}
catch (e) {
deviceType = "mouse";
return false;
}
};
const getMouseCoordinates = (event) => {
const touch = "touches" in event ? event.touches[0] : event;
mouseX = touch.pageX - canvasElement.getBoundingClientRect().left;
mouseY = touch.pageY - canvasElement.getBoundingClientRect().top;
};
const handleDown = (event) => {
isDragging = true;
getMouseCoordinates(event);
scratch(mouseX, mouseY);
};
const handleMove = (event) => {
if (deviceType === "mouse")
event.preventDefault();
if (isDragging) {
getMouseCoordinates(event);
scratch(mouseX, mouseY);
}
};
const handleUp = () => {
isDragging = false;
checkScratchedPercentage();
};
const checkScratchedPercentage = () => {
if (canvasContext) {
const imageData = canvasContext.getImageData(0, 0, canvasElement?.width, canvasElement?.height).data;
let scratchedPixels = 0;
for (let i = 0; i < imageData.length; i += 4) {
// Check if the pixel is scratched (transparent)
if (imageData[i + 3] === 0) {
scratchedPixels++;
}
}
const currentScratchedPercentage = (scratchedPixels / coverArea) * 100;
if (currentScratchedPercentage >= 60) {
// Remove the cover
setCoverRemoved(true);
if (!coverScratched && handleCoverScratched) {
handleCoverScratched();
coverScratched = true;
}
}
}
};
checkIfTouchDevice();
canvasElement?.addEventListener(eventTypes[deviceType].down, handleDown);
canvasElement?.addEventListener(eventTypes[deviceType].move, handleMove);
document.addEventListener(eventTypes[deviceType].up, handleUp);
const scratch = (x, y) => {
if (canvasContext) {
canvasContext.globalCompositeOperation = "destination-out";
canvasContext.beginPath();
canvasContext.arc(x, y, 22, 0, 2 * Math.PI);
canvasContext.fill();
// Fill in gaps between points for smoother scratching
const lastX = canvasElement?.dataset.lastX ? parseInt(canvasElement?.dataset.lastX) : x;
const lastY = canvasElement?.dataset.lastY ? parseInt(canvasElement?.dataset.lastY) : y;
const dx = x - lastX;
const dy = y - lastY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 1) {
for (let i = 0; i < distance; i++) {
const newX = lastX + (dx * i) / distance;
const newY = lastY + (dy * i) / distance;
canvasContext.beginPath();
canvasContext.arc(newX, newY, 22, 0, 2 * Math.PI);
canvasContext.fill();
}
}
canvasElement.dataset.lastX = x.toString();
canvasElement.dataset.lastY = y.toString();
}
};
initializeCanvas();
return () => {
canvasElement?.removeEventListener(eventTypes[deviceType].down, handleDown);
canvasElement?.removeEventListener(eventTypes[deviceType].move, handleMove);
document.removeEventListener(eventTypes[deviceType].up, handleUp);
};
}, []);
return (React.createElement("div", { className: "container" },
React.createElement("div", { className: "base" },
React.createElement("h4", null, data)),
!coverRemoved && React.createElement("canvas", { id: "scratch", width: "200", height: "200" })));
};
export { ScratchCard };